Recently I've been working on converting an ASP.NET WebAPI service to run on ASP.NET Core instead.

This service's inputs and outputs are all encoded with Google's Protobuf, and for WebAPI I wrote a custom MediaTypeFormatter to deserialize requests and serialize responses into fully-structed objects.

For ASP.NET Core this has been redone to be a lot simpler, but isn't heavily documented - or if it is, I couldn't find much on it.

Creating Custom Formatters

In ASP.NET Core, formatters have been split into two. There is now IInputFormatter and IIOutputFormatter. These each have corresponding abstract classes, InputFormatter and OutputFormatter respectively, that do a lot of the heavy lifting and default HTTP content negotiation.

When subclassing InputFormatter or OutputFormatter, in the constructor, add your supported media types to the SupportedMediaTypes property, then there are a handful of methods that you can override in order to implement the actual deserialization and serialization.

Setting a default Formatter

To set a default input formatter - i.e., the formatter that is used when the request's Content-Type header is blank or undefined - I first tried inserting the formatter at the beginning of the list in the application's Startup procedures:

// This method gets called by the runtime. Use this method to add services to the container.
public virtual void ConfigureServices(IServiceCollection services)
{
    var mvc = services.AddMvc(options =>
    {
        options.InputFormatters.Insert(0, new ProtobufMediaTypeInputFormatter());
        options.OutputFormatters.Insert(0, new ProtobufMediaTypeOutputFormatter());
    });
    
    ...
}

Unfortunately, this didn't help. After digging into the source of InputFormatter[1], I eventually discovered that bool CanRead(...) returns false if Content-Type is null or empty. This was easy enough to fix by overriding CanRead in my ProtobufMediaTypeInputFormatter:

public override bool CanRead(InputFormatterContext context)
{
    if (string.IsNullOrEmpty(context.HttpContext.Request.ContentType))
    {
        return CanReadType(context.ModelType);
    }
    
    return base.CanRead(context);
}

With that in place, and being at the beginning of the list of formatters, ASP.NET Core will try and use it for any request without a Content-Type header.


  1. Hooray for ASP.NET Core being fully open-source! ↩︎