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 that is receiving the file (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.

8 thoughts on “C# – How to send a file with HttpClient”

  1. Thanks, this pointed me in the direction of how to use that MultiPartDataContent object as Microsoft docs was absolutely no help at all 🙂

    • Glad to hear that. Yeah, sometimes the official docs don’t really seem like they were written for real world scenarios.

  2. I’m currently using this approach but I keep getting stuck when it comes to the httpclient.PostAsJson() method

    This is because the endpoint i’m consuming requires a bearer token and i’m not able to add the said token.
    doing: httpClient.DefaultRequestHeaders.Add(“Authorization”, bearerToken); returns a 401,
    so doesmultipartFormContent.Add(new StringContent(bearerToken), name: “Authorization”);
    and finally, fileStreamContent.Headers.Add(“Authorization”, bearerToken); returns
    “Misused header name, ‘Authorization’. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects”

    How do i fix this please

    • Hi Samuel,

      1. Put the auth header as a request header, not as a content header.
      2. Add the word “Bearer”. It looks like you’re missing this. Here’s how to add a bearer token for ALL requests:
      using System.Net.Http.Headers;
      httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(“Bearer”, bearerToken)

      Note: Only add headers to DefaultRequestHeaders if you really want them to go with every request. Don’t put this here if the value changes between requests.

      By the way, take a look at this page if you want more info and examples about how to add request headers: https://makolyte.com/csharp-how-to-add-request-headers-when-using-httpclient/

    • I don’t know since I have never had that requirement. However, it sounds interesting, so I’ll stick it on my todo to look into.

Comments are closed.