Search code examples
c#dotnet-httpclient

Uploading multipart form files with HttpClient


I am trying to create an attachment using the Support Bee API as documented here: https://supportbee.com/api#create_attachment

I have written a service that uses an HttpClient to create and send the request using a filename.

If I test in in Postman, it succeeds. I am using form-data for the body and just selecting the file to upload from the UI:

enter image description here

It doesn't work when I try to upload it via my HttpClient Service:

public async Task<string> CreateAttachmentAsync(string fileName)
{
    // "client" is HttpClient provided via D.I.

    MultipartFormDataContent content = new MultipartFormDataContent();
    content.Add(new StreamContent(new FileStream(fileName, FileMode.Open)), "files[]");

    using (HttpResponseMessage response = await client.PostAsync(
        "https://xxx.supportbee.com/attachments?auth_token=xxx",
        content))
    {
        string responseString = await response.Content.ReadAsStringAsync();

        return responseString;
    }
}

This results in a 500 Internal Server Error. Inspecting the MultipartFormDataContent object I can see that it's header values are automatically being set:

{ Content-Type: multipart/form-data; boundary="c9be3778-4de5-4460-9929-adcaa6bdda79" Content-Length: 164 }

I have also tried reading the file to a byte array first and using ByteArrayContent instead of StreamContent to no avail. The response doesn't provide anything helpful, but since the request works in Postman I must have something wrong with my code, but I don't know what else to try.

Edit: I tested with Fiddler to compare the successful Postman request to my code. Here is the request with Postman:

POST https://xxx.supportbee.com/attachments?auth_token=xxx HTTP/1.1 User-Agent: PostmanRuntime/7.22.0 Accept: / Cache-Control: no-cache Postman-Token: f84d22fa-b4b1-4bf5-b183-916a786c6385 Host: xx.supportbee.com Content-Type: multipart/form-data; boundary=--------------------------714700821471353664787346 Accept-Encoding: gzip, deflate, br Content-Length: 241 Connection: close

----------------------------714700821471353664787346 Content-Disposition: form-data; name="files[]"; filename="sample.txt" Content-Type: text/plain

This contains example text. ----------------------------714700821471353664787346--

And the failing request from my code:

POST https://xxx.supportbee.com/attachments?auth_token=xxx HTTP/1.1 Host: xxx.supportbee.com Accept: / Accept-Encoding: gzip, deflate, br Connection: close Content-Type: multipart/form-data; boundary="ea97cbc1-70ea-4cc4-9801-09f5feffc763" Content-Length: 206

--ea97cbc1-70ea-4cc4-9801-09f5feffc763 Content-Disposition: form-data; name="files[]"; filename=sample; filename*=utf-8''sample

This contains example text. --ea97cbc1-70ea-4cc4-9801-09f5feffc763--

The difference I can see is that the individual part in Postman has its own Content-Type: text/plain header for the file, and mine doesn't. I'm unable to add this because if I try content.Headers.Add("Content-Type", "text/plain"); It fails with 'Cannot add value because header 'Content-Type' does not support multiple values.'


Solution

  • First, it's important to note that a 500 response is akin to an unhandled exception, i.e. it's a bug on their end and more or less impossible to know for sure what you did wrong. I would suggest reporting it to them and, although I'm not familiar with Support Bee, I would hope they have good support people who can help you troubleshoot. :)

    But if you want to play the guessing game, I agree that subtle differences between your successful Postman call and your code are a good place to start. For that header, note that content is the MultipartFormDataContent. You actually want to set it on the StreamContent object.

    Also, look at the request headers Postman is sending and see if Content-Disposition includes a filename. You might need to add that to your code too, if the API is expecting it.

    Here's how to do both:

    var fileContent = new StreamContent(File.OpenRead(path));
    fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
    content.Add(fileContent, "files[]", Path.GetFileName(path));
    

    If that's not the problem, look at the "raw" version of the request body in Postman, as well as those 11 request headers, and see if you can spot anything else you might be missing.