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.

To download the source code for this article, you can visit our GitHub repository.

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? [Ƭ[email protected]
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.