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.
Table of Contents
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.
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.
This was exactly what I needed, thank you for sharing it!
I’m happy to hear this helped!
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/
how can we add progress to it while Uploading/Downloading please?
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.