Creating a multipart/related Http Request – Easy when you know how.

When working with a third party EDMS I needed to add a document using a multipart/related request. Having never done a multipart/related request before I did struggle at first, trying all sorts off methods. But then I stumbled across the System.Net.Http.MultipartContent – things often seem so simple when you see the answer.

The Request

Getting straight to it, this is an example of the code I used. I implemented it differently, but I thought this made more sense as a standalone method (well, methods).


public string AddDocumentToEDMS(EDMSObject metadata, string filePath)
{
    string json = Newtonsoft.Json.JsonConvert.SerializeObject(metadata);
    string jsonResult = string.Empty;
    using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
    {
        try
        {
            jsonResult = SubmitDocumentStream(fileStream, json);
        }
        catch (Exception)
        {
            // add more to the error if you want to.
            throw;
        }
        finally
        {
            fileStream.Close();
        }
    }

    return Newtonsoft.Json.JsonConvert.DeserializeObject<EDMSObject>(jsonResult);
}

public string SubmitDocumentStream(Stream fileStream, string jsonMetadata)
{
    var content = new MultipartContent("related"); // set as related
    // add the metadata
    var metadata = new StringContent(jsonMetadata, Encoding.UTF8, "application/json");
    metadata.Headers.Add("Content-ID", "metadata");
    content.Add(metadata);

    // create doc content
    var file = new StreamContent(fileStream);
    file.Headers.Add("Content-Type", "application/octet-stream");
    file.Headers.Add("Content-ID", "content");
    content.Add(file);
    
    var result = CreateClient().PostAsync(url, content).Result;

    if (!result.IsSuccessStatusCode)
        throw new Exception($"Error occurred creating the document. {result.StatusCode}: {result.ReasonPhrase}\nRequest:\n{jsonMetadata}");

    return result.Content.ReadAsStringAsync().Result;
}

private HttpClient CreateClient()
{
    var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{UserName}:{Password}"));
    var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials);

    return client;
}

Explanation

AddDocumentToEDMS calls SubmitDocumentStream. The reason I do this is because it reads better, with taking objects to convert to json, and with releasing the document (important if you want to delete afterwards). In the real world I would expect these to be in different classes or projects.

I set the Content-ID as this is what the documentation wanted (see below)

What was expected

The request was to have the metadata (as json) as well as the document stream, as seen in the below API documentation.

 Content-Type: multipart/related; boundary=samplebdry; start=metadata; type="application/json"
 --samplebdry
 Content-Type: application/json
 Content-ID: metadata
 
 {"name":"my new document","parentId":"fA67","definitionId":"dotdA9","publish":true,"corporateValue":true,"submitApproval":true,"comment":"a comment","approvalComment":"approval comment","applyCorporateValueOnPublication":true,"fileName":"attachment.pdf","catalogues":[{"id":"cA2","fields":[{"id":"tA4","values":[{"value":"zA1"}]}]}],"caveats":{"useInheritedCaveats": true, "groups":[{"type":"request caveat", "id": "gA0"}, {"type":"request caveat", "id":"gA1"}]}}
 --samplebdry
 Content-Type: application/octet-stream
 Content-ID: content
 
 iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAAA3NCSVQICAjb4U
 --samplebdry--

And that is all there is to it. Hope it helps.

Leave a Reply

Your email address will not be published. Required fields are marked *