In this article, we will learn what multipart requests are and how we can use them. Also, we will review examples of how to send data to remote servers using multipart form-data with HttpClient in ASP.NET Core.
Let’s start.
What is Multipart Form-Data?
A POST request can carry a body containing information. Usually, the user enters information through an HTML form. We can choose between three possible ways to encode this information:
- text/plain
- application/x-www-form-urlencoded
- multipart/form-data
The text/plain
encoding just carries unstructured information and has uses beyond HTML forms.
The application/x-www-form-urlencoded
is the default encoding for HTML forms. The body of application/x-www-form-urlencoded
requests will be composed of a series of key-value pairs where an equal sign =
separates every key from its value while an ampersand &
separates each pair:
firstName=John&lastName=Doe
Moreover, any non-alphanumeric data in this request body will be URL encoded. Meaning that each byte is represented by a percent sign followed by two hexadecimal digits %HH
. While it is possible to send binary data and files using this encoding, using three bytes to represent a single byte of the original data would be highly inefficient.
On the other hand, multipart/form-data
is the encoding used when an HTML form has a file upload field.
In a multipart/form-data
request, the body is made of a series of payloads called “parts” separated by a specific boundary value. Each part can have its request headers and can declare its name through the Content-Disposition
header. The payload itself doesn’t need to be URL encoded so it can contain raw binary data.
Anatomy of a Multipart Request
A multipart request is defined by the multipart/form-data
content type. The multipart content type requires a boundary directive containing a string of ASCII characters. This string serves as a delimiter of the different parts in the request body.
Optionally, each part can have a different Content-Type
header allowing for the inclusion of different kinds of data, in this case, text, and images:
POST http://localhost:5272/upload-file HTTP/1.1 User-Agent: Mozilla/5.0 Accept: */* Host: localhost:5272 Accept-Encoding: gzip, deflate, br Connection: keep-alive Content-Type: multipart/form-data; boundary=--------------------------688335339700918511956765 Content-Length: 22651 ----------------------------688335339700918511956765 Content-Disposition: form-data; name="name" Content-Type: text/plain; charset=utf-8 John Doe ----------------------------688335339700918511956765 Content-Disposition: form-data; name="position" Content-Type: text/plain; charset=utf-8 Regional Manager ----------------------------688335339700918511956765 Content-Disposition: form-data; name="profile_picture"; filename="jhon_doe.jpg" Content-Type: image/jpeg JFIF ` ` C := un G 3 sE hv*K T 8 EBV- W? [ƬH@ P <7#_ (ۤ sڊ( G ----------------------------688335339700918511956765--
In the context of a multipart request, a Content-Disposition
header with the value form-data
is required for each part along with a name
directive identifying the request part. Optionally, we can add a filename directive.
Send Multipart Requests With HttpClient
Let’s issue a multipart form-data request using HttpClient to a remote server using the MultipartFormDataContent
implementation of the HttpContent
base class:
using MultipartFormDataContent multipartContent = new(); multipartContent.Add(new StringContent("John", Encoding.UTF8, MediaTypeNames.Text.Plain), "first_name"); multipartContent.Add(new StringContent("Doe", Encoding.UTF8, MediaTypeNames.Text.Plain), "last_name"); using var response = await _httpClient.PostAsync("http://localhost:5272/upload-form", multipartContent); if (response.IsSuccessStatusCode) { // Data uploaded successfully. }
In the example, we instantiate MultipartFormDataContent
through its parameterless constructor. Then, we use its Add()
method to include each part of the request.
In this case, we create two new StringContent
instances and add them to the MultipartFormDataContent
specifying the part name as a second parameter. These will conform to the two parts of our multipart request.
Later, we will use the MultipartFormDataContent
instance as the content parameter for a HttpClient.PostAsync()
call as we would do with any other type of HttpContent
.
Include Files in a Multipart Request
However, the main reason we would use a multipart request is to send a file to a remote server in the body of the HTTP request. To achieve that, let’s use the StreamContent
class:
using MultipartFormDataContent multipartContent = new(); var imageContent = new StreamContent(File.OpenRead($".{Path.DirectorySeparatorChar}john_doe.jpg")); imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Image.Jpeg); multipartContent.Add(imageContent, "profile_picture", "john_doe.jpg"); using var response = await _httpClient.PostAsync("http://localhost:5272/upload-image", multipartContent);
The StreamContent
, like StringContent
, inherits from HttpContent
and will use data coming from an underlying Stream
to build a request body. Here, we create a local variable imageContent
as a StreamContent
instance based on a FileStream
reading a jpeg
file.
Next, we set the correct content type for the content using the imageContent.Headers.ContentType
property. Finally, we include our imageContent
in the multipart request by calling the Add()
method in our MultipartFormDatacontent
instance before sending the request.
Add an Array of Bytes to a Multipart Request
Let’s consider a scenario where we store our file data in an array of bytes instead of a stream. In that case, we can use ByteArrayContent
and add it to the MultipartFormDataContent
in the same way, we did with the StreamContent
:
var imageContent = new ByteArrayContent(await File.ReadAllBytesAsync($".{Path.DirectorySeparatorChar}john_doe.jpg")); imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Image.Jpeg); multipartContent.Add(imageContent, "profile_picture", "john_doe.jpg");
Set the Boundary Delimiter
As discussed before, the boundary is a delimiter string that will mark the beginning and the end of each part in the request.
The boundary must be an ASCII string no longer than 70 characters. This value, must not be present in the content of any of the parts.
In most cases, we do not need to set the boundary value explicitly since MultipartFormDataContent
will choose a valid one for us. However, if we want to set our own boundary we can do so by passing it as a parameter to the constructor:
MultipartFormDataContent multipartContent = new("My Custom Boundary");
Conclusion
In this article, we have learned what a multipart request is. We have learned that multipart requests can send files to remote servers. Also, we analyzed what a multipart request looks like internally.
Next, we learned how to send requests containing multiple parts using HttpClient
and MultipartFormDataContent.
Finally, we learned how we can include files as part of our requests and specify the correct Content-Type
for each of them.
Very insightful article, especially part with sending multipart form data through HttpClient!
Thanks!