One of the key concepts in HTTP API development is the notion of content negotiation (conneg). ASP.NET Web API provided first class support for content negotiation through the use of MediaTypeFormatters.
While MVC 6 is a de facto brand new framework, rebuilt from scratch, the majority of concepts from MVC 5 and Web API 2 have naturally been brought forward, and conneg done through formatters are one of them.
Let’s have a look at formatters in MVC6.
Input and Output formatters 🔗
While in ASP.NET Web API formatters were bi-directional - single object handling both input and output, in MVC 6 the concept of formatters became uni-directional.
This is represented by two interfaces - IOutputFormatter and IInputFormatter.
public interface IOutputFormatter
{
IReadOnlyList<MediaTypeHeaderValue> GetSupportedContentTypes(
Type declaredType,
Type runtimeType,
MediaTypeHeaderValue contentType);
bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType);
Task WriteAsync(OutputFormatterContext context);
}
public interface IInputFormatter
{
bool CanRead(InputFormatterContext context);
Task
<object>
ReadAsync(InputFormatterContext context);<br /> }<br /> ```</p>
<p>
Additionally, just like in Web API there is a base class to make extensibility a little more convenient - it's available for output formatters only though.
</p>
<p>
All in all, building custom formatters is almost identical as it was in Web API - you have to deal with read/write operations (depending on the direction of the formatter), declare the supported content types and define what Types are supported.
</p>
<h3>
What's in the box?
</h3>
<p>
MVC 6 ships with a bunch of formatters defined already.
</p>
<p>
Three input formatters:
</p>
<ul>
<li>
- <em>JsonInputFormatter</em> - based on JSON.NET
</li>
<li>
- <em>XmlSerializerInputFormatter</em> - based on <em>XmlSerializer</em> (in the box, but not registered by default)
</li>
<li>
- <em>XmlDataContractSerializerInputFormatter</em> - based on <em>DataContractSerializer</em>
</li>
</ul>
<p>
Six output formatters:
</p>
<ul>
<li>
- <em>JsonOutputFormatter</em> - based on JSON.NET
</li>
<li>
- <em>XmlSerializerOutputFormatter</em> - based on <em>XmlSerializer</em> (in the box, but not registered by default)
</li>
<li>
- <em>XmlDataContractSerializerOutputFormatter</em> - based on <em>DataContractSerializer</em>
</li>
<li>
- <em>TextPlainFormatter</em> - used to force a string into a text/plain content type
</li>
<li>
- <em>HttpNoContentOutputFormatter</em> - used to force 204 status code for null action return
</li>
<li>
- <em>HttpNotAcceptableOutputFormatter</em> - used to force 406 status code if no appropriate formatter can be selected to handle the request (in the box, but not registered by default)
</li>
</ul>
<p>
This is a slight change from Web API which itself defined four formatters - JSON, XML and two formatter specialized in handling form data.
</p>
<h3>
Customizing formatters
</h3>
<p>
In order to add/remove formatters you have tweak the new <em>MvcOptions</em> object which allows you to control the global configuration of the MVC framework. <em>MvcOptions</em> exposes input/output formatters collections.
</p>
<p>
You get to deal with <em>MvcOptions</em> inside the <em>ConfigureServices</em> method of your <em>Startup</em> class, as you are adding MVC to the service collection. An example below shows getting rid of the XML formatter - we get rid of them twice since obviously we deal with input and output formatters separately.
</p>
<p>
```csharppublic void ConfigureServices(IServiceCollection services)<br /> {<br /> //add other services as you wish, i.e. Identity etc
</p>
<p>
services.AddMvc().Configure<MvcOptions>(options =><br /> {<br /> options.InputFormatters.RemoveAll(formatter => formatter.Instance.GetType() == typeof(XmlDataContractSerializerInputFormatter));<br /> options.OutputFormatters.RemoveAll(formatter => formatter.Instance.GetType() == typeof(XmlDataContractSerializerOutputFormatter));<br /> });<br /> }<br /> ```
</p>
<p>
Similarly, you can customize existing formatters i.e. JSON formatter, and define the relevant serializer settings that would match your application's requirements. For example:
</p>
<p>
```csharp
services.AddMvc().Configure<MvcOptions>(options =><br /> {<br /> options.InputFormatters.Clear();
</p>
<p>
var jsonOutputFormatter = new JsonOutputFormatter();<br /> jsonOutputFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();<br /> jsonOutputFormatter.SerializerSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore;
</p>
<p>
options.OutputFormatters.RemoveAll(formatter => formatter.Instance.GetType() == typeof(JsonOutputFormatter));<br /> options.OutputFormatters.Insert(0, jsonOutputFormatter);<br /> });<br /> ```
</p>
<h3>
HttpNoContentOutputFormatter, TextPlainFormatter and HttpNotAcceptableOutputFormatter
</h3>
<p>
Let's have a closer look at the three new types of output formatters - in order to understand the reason why they have been added in this iteration of MVC.
</p>
<p>
<em>HttpNoContentOutputFormatter</em> converts <em>null</em> responses from your actions into a 204 No Content status code. This is a change from Web API, where a <em>null</em> return would end up being serialized into JSON or XML. Additionally, if you return <em>void</em> or <em>Task</em> from an action, this formatter would also kick in. Functionally, this is the same behavior as in Web API, however there it was not achieved via a formatter, but through a <em>ResultConverter</em>.
</p>
<p>
You can disable the <em>null</em> value handling on the <em>HttpNoContentOutputFormatter</em> by setting its <em>TreatNullValueAsNoContent</em> property to false.
</p>
<p>
<em>TextPlainFormatter</em> converts a <em>string</em> return type into an HTTP response with a body using <em>text/plain</em> content type. Similarly to <em>HttpNoContentOutputFormatter</em> this is a change in comparison to Web API, where a string would get treated just like any other type and get serialized into JSON or XML.
</p>
<p>
Finally, <em>HttpNotAcceptableOutputFormatter</em> is a formatter that allows you to disable the matching on Type when it comes to content negotiation. If a relevant formatter cannot be selected, MVC6 (similarily to Web API) would just default to JSON, because it would select a formatter strictly on the fact that it can serialize the Type being return. This is quite problematic, because you might have a client requesting i.e. <em>text/csv</em> and the framework happily returns response 200 with JSON.
</p>
<p>
In Web API you'd disable matching on Type by customizing <em>DefaultContentNegotiator</em>. In MVC6 it's slightly simpler as you simply need to add the <em>HttpNotAcceptableOutputFormatter</em> to the output formatters collection.
</p>
<p>
```csharp
services.AddMvc().Configure<MvcOptions>(options =><br /> {<br /> options.OutputFormatters.Insert(0, new HttpNotAcceptableOutputFormatter());<br /> }<br /> ```
</p>