C# – How to send a file with HttpClient

In order to send a file in a request with HttpClient, add the file into a MultipartFormDataContent object, and send this object as the request content. Here’s an example:

var filePath = @"C:\house.png"; using (var multipartFormContent = new MultipartFormDataContent()) { //Load the file and set the file's Content-Type header var fileStreamContent = new StreamContent(File.OpenRead(filePath)); fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png"); //Add the file multipartFormContent.Add(fileStreamContent, name: "file", fileName: "house.png"); //Send it var response = await httpClient.PostAsync("https://localhost:12345/files/", multipartFormContent); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); }
Code language: C# (cs)

This sends the following multipart/form-data POST request:

POST https://localhost:12345/files/ HTTP/1.1 Host: localhost:12345 Content-Type: multipart/form-data; boundary="44b2ed38-1ac7-4731-b2f4-f84bf159748d" Content-Length: 7279 --44b2ed38-1ac7-4731-b2f4-f84bf159748d Content-Type: image/png Content-Disposition: form-data; name=file; filename=house.png; filename*=utf-8''house.png <bytes>
Code language: plaintext (plaintext)

In this article, I’ll explain a few details about MultipartFormDataContent, and show a few other file-sending scenarios.

MultipartFormDataContent

Add() parameters

When you’re adding a file to MultipartFormDataContent, you use the following Add() method overload:

public void Add(HttpContent content, string name, string fileName);
Code language: C# (cs)

The name parameter is the form field name. Set this to the parameter name defined by the web API (if it’s using automatic mapping).

The fileName parameter is the original file name.

Disposal

When you dispose MultipartFormDataContent, it disposes all of the HttpContent objects you added to it. Furthermore, when you dispose StreamContent, it disposes the underlying file stream. Thanks to this cascading disposal, you only need one using block (or using declaration if you prefer that style).

In short, MultipartFormDataContent disposes the StreamContent object, which disposes the FileStream object.

Sending form data with multiple fields, including a file

When you need to send a file, you’ll probably need to associate it with some entity. In other words, you’ll want to send other fields along with the file. The simplest way to do this is to add everything to MultipartFormDataContent.

For example, let’s say you’re sending a file and need to include a title and user id. Besides adding the file, you can add the title and user id fields to the form data like this:

var filePath = @"C:\house.png"; using (var multipartFormContent = new MultipartFormDataContent()) { //Add other fields multipartFormContent.Add(new StringContent("123"), name: "UserId"); multipartFormContent.Add(new StringContent("Home insurance"), name: "Title"); //Add the file var fileStreamContent = new StreamContent(File.OpenRead(filePath)); fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png"); multipartFormContent.Add(fileStreamContent, name: "file", fileName: "house.png"); //Send it var response = await httpClient.PostAsync("https://localhost:12345/files/", multipartFormContent); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); }
Code language: C# (cs)

This sends the following multipart/form-data request. Notice that it included parts for the Title and UserId fields:

POST https://localhost:12345/files/ HTTP/1.1 Host: localhost:12345 Content-Type: multipart/form-data; boundary="00d335a2-0389-48e1-85d9-0daf70c2879e" Content-Length: 7519 --00d335a2-0389-48e1-85d9-0daf70c2879e Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=UserId 123 --00d335a2-0389-48e1-85d9-0daf70c2879e Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=Title Home insurance --00d335a2-0389-48e1-85d9-0daf70c2879e Content-Type: image/png Content-Disposition: form-data; name=file; filename=house.png; filename*=utf-8''house.png <bytes>
Code language: plaintext (plaintext)

Sending a byte array

If you already have a byte array, and don’t need to load the file as a file stream, then you can use ByteArrayContent instead of StreamContent. Here’s an example:

using (var multipartFormContent = new MultipartFormDataContent()) { //Add the file as a byte array var byteContent = new ByteArrayContent(fileBytesFromDatabase); byteContent.Headers.ContentType = new MediaTypeHeaderValue("image/png"); multipartFormContent.Add(byteContent, name: "file", fileName: "house.png"); //Send it var response = await httpClient.PostAsync("https://localhost:12345/files/", multipartFormContent); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); }
Code language: C# (cs)

This generates the following request:

POST https://localhost:12345/files/ HTTP/1.1 Host: localhost:12345 Content-Type: multipart/form-data; boundary="f4186b10-2cf4-4497-9a65-6e592d6cfce1" Content-Length: 7243 --f4186b10-2cf4-4497-9a65-6e592d6cfce1 Content-Type: image/png Content-Disposition: form-data; name=file; filename=house.png; filename*=utf-8''house.png <bytes>
Code language: plaintext (plaintext)

Sending multiple files

There are two ways to send multiple files:

  • Send multiple files using the same name parameter.
  • Send each file with their own name parameter.

Which option you pick will depend on how the web API is configured. Here’s an example of the first option – sending multiple files using the same name parameter:

var filePaths = new string[] { @"C:\house.png", @"C:\car.png" }; using (var multipartFormContent = new MultipartFormDataContent()) { foreach(var filePath in filePaths) { var fileName = Path.GetFileName(filePath); //Load the file and set the file's Content-Type header var fileStreamContent = new StreamContent(File.OpenRead(filePath)); fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png"); //Add the file multipartFormContent.Add(fileStreamContent, name: "files", fileName: fileName); } //Send it var response = await httpClient.PostAsync("https://localhost:12345/files/", multipartFormContent); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); }
Code language: C# (cs)

This sends the following request:

POST https://localhost:12345/files/ HTTP/1.1 Host: localhost:12345 Content-Type: multipart/form-data; boundary="92f8b9da-896f-41ff-8709-85a0b8d0ef08" Content-Length: 14442 --92f8b9da-896f-41ff-8709-85a0b8d0ef08 Content-Type: image/png Content-Disposition: form-data; name=files; filename=house.png; filename*=utf-8''house.png <bytes> --92f8b9da-896f-41ff-8709-85a0b8d0ef08 Content-Type: image/png Content-Disposition: form-data; name=files; filename=car.png; filename*=utf-8''car.png <bytes>
Code language: plaintext (plaintext)

Notice that each file is put in its own part (separated by the boundary string).

Setting the file’s content type

The image file “house.png” has a content type of “image/png”, which was added as a file content header with the following line:

fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
Code language: C# (cs)

This sets the Content-Type header in the file’s part in the multipart request:

--f4186b10-2cf4-4497-9a65-6e592d6cfce1 Content-Type: image/png Content-Disposition: form-data; name=file; filename=house.png; filename*=utf-8''house.png
Code language: plaintext (plaintext)

If the web API you’re integrating with requires you to set the file’s Content-Type, then you have to set it explicitly (it’s not set automatically). You can set it based on the file’s extension (or hardcode it if appropriate). Here’s an example:

//Cached somewhere var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { [".png"] = "image/png", [".jpg"] = "image/jpeg", [".gif"] = "image/gif" }; var filePath = @"C:\house.png"; var extension = Path.GetExtension(filePath); if (!map.TryGetValue(extension, out string contentType)) { throw new Exception("Can't send this type of file"); } var fileStreamContent = new StreamContent(File.OpenRead(filePath)); fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
Code language: C# (cs)

Refer to the .NET FileExtensionContentTypeProvider source code for a full list of mappings. Since that class is just a wrapper for a Dictionary<string, string>, I suggest adding your own mappings containing only the file extensions relevant to you (as I did above).

Note: If you want to use FileExtensionContentTypeProvider, it’s in the Microsoft.AspNetCore.StaticFiles package.

Leave a Comment