ASP.NET
MultipartFormFormatter
Web API
.NET Framework 4.5
Programming Tutorial

How create a MultipartFormFormatter for ASP.NET 4.5 Web API

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

ASP.NET Web API 4.5 already understands multipart/form-data, so many upload endpoints do not need a custom formatter at all. A custom multipart formatter only makes sense when you want Web API to deserialize the incoming form fields and files into a domain-specific object instead of manually reading a multipart provider in every controller.

When a Custom Formatter Is Worth It

The built-in MultipartFormDataStreamProvider is enough for many cases. A controller can call ReadAsMultipartAsync, inspect provider.FormData, and save files from provider.FileData. That is explicit and reliable.

A custom formatter becomes useful when multiple endpoints share the same upload shape. For example, suppose every request contains a title, a category, and one file. You can centralize the parsing logic in a formatter and let the action receive a strongly typed object.

Start with a simple request model:

csharp
1public class UploadRequest
2{
3    public string Title { get; set; }
4    public string Category { get; set; }
5    public string FileName { get; set; }
6    public byte[] FileBytes { get; set; }
7}

Implementing the Formatter

A formatter derives from MediaTypeFormatter, declares support for multipart/form-data, and overrides ReadFromStreamAsync.

csharp
1using System;
2using System.IO;
3using System.Linq;
4using System.Net.Http;
5using System.Net.Http.Formatting;
6using System.Net.Http.Headers;
7using System.Threading.Tasks;
8
9public class MultipartFormFormatter : MediaTypeFormatter
10{
11    public MultipartFormFormatter()
12    {
13        SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
14    }
15
16    public override bool CanReadType(Type type)
17    {
18        return type == typeof(UploadRequest);
19    }
20
21    public override bool CanWriteType(Type type)
22    {
23        return false;
24    }
25
26    public override async Task<object> ReadFromStreamAsync(
27        Type type,
28        Stream readStream,
29        HttpContent content,
30        IFormatterLogger formatterLogger)
31    {
32        var provider = new MultipartMemoryStreamProvider();
33        await content.ReadAsMultipartAsync(provider);
34
35        var file = provider.Contents.FirstOrDefault(c =>
36            c.Headers.ContentDisposition.FileName != null);
37
38        return new UploadRequest
39        {
40            Title = GetFormValue(provider, "title"),
41            Category = GetFormValue(provider, "category"),
42            FileName = file == null
43                ? null
44                : TrimQuotes(file.Headers.ContentDisposition.FileName),
45            FileBytes = file == null
46                ? null
47                : await file.ReadAsByteArrayAsync()
48        };
49    }
50
51    private static string GetFormValue(MultipartMemoryStreamProvider provider, string name)
52    {
53        var part = provider.Contents.FirstOrDefault(c =>
54            string.Equals(TrimQuotes(c.Headers.ContentDisposition.Name), name, StringComparison.OrdinalIgnoreCase));
55
56        return part == null ? null : part.ReadAsStringAsync().Result;
57    }
58
59    private static string TrimQuotes(string value)
60    {
61        return value == null ? null : value.Trim('"');
62    }
63}

The example uses MultipartMemoryStreamProvider, which is easy to understand and fine for modest payloads. For large files, switch to a disk-backed provider or keep manual controller logic so you can stream directly to storage.

Register the formatter in Web API configuration:

csharp
1using System.Web.Http;
2
3public static class WebApiConfig
4{
5    public static void Register(HttpConfiguration config)
6    {
7        config.Formatters.Insert(0, new MultipartFormFormatter());
8        config.MapHttpAttributeRoutes();
9    }
10}

Then an action can accept the typed model:

csharp
1using System.Net;
2using System.Threading.Tasks;
3using System.Web.Http;
4
5public class UploadController : ApiController
6{
7    [HttpPost]
8    [Route("api/upload")]
9    public async Task<IHttpActionResult> Post(UploadRequest request)
10    {
11        if (request == null || request.FileBytes == null)
12        {
13            return Content(HttpStatusCode.BadRequest, "File is required.");
14        }
15
16        await Task.Yield();
17        return Ok(new
18        {
19            request.Title,
20            request.Category,
21            request.FileName,
22            Size = request.FileBytes.Length
23        });
24    }
25}

Tradeoffs and Alternatives

A custom formatter reduces repetition, but it also hides multipart behavior behind model binding. That can be helpful for consistency, yet harder to debug than explicit provider code in the controller.

If your upload endpoint needs antivirus scanning, streaming to cloud storage, or size-based routing, the controller-plus-provider approach is often clearer. The formatter approach is strongest when the request contract is stable and small enough to fit comfortably in memory.

Common Pitfalls

The most common mistake is building a formatter for large files and then using MultipartMemoryStreamProvider, which loads everything into memory. That is fine for demos, not for big uploads.

Another issue is assuming every multipart section is a file. Form fields and file parts are mixed together, so your parser must inspect ContentDisposition carefully.

Be careful with FileName, because browsers often wrap it in quotes. Trimming those quotes is routine and should not be skipped.

Finally, do not create a custom formatter unless it simplifies multiple endpoints. For a single upload action, direct multipart parsing is usually easier to maintain.

Summary

  • Use a custom multipart formatter only when shared upload parsing justifies it.
  • Derive from MediaTypeFormatter and override ReadFromStreamAsync.
  • Parse form values and file content into a typed request model.
  • Prefer memory-based providers only for small to medium payloads.
  • Fall back to explicit controller parsing when uploads need streaming or complex control.

Course illustration
Course illustration

All Rights Reserved.