ASP NET Core 3 Content Formatting

Understand Content Formatting

In a standard ASP NET Core 3 API Controller you are used to seeing JSON responses, but this is not the only data format that action methods can produce. The content format selected for an action result depends on four factors: 

  • Formats the client will accept
  • Formats that the application can produce
  • The content policy specified by the action method
  • The type returned by the action method

Figuring out how everything fits together can be daunting, but the good news is that the default policy works just fine for most applications, and you only need to understand what happens behind the scenes when you need to make a change or when you are not getting the results in the format you expect.


Understanding the Default Content Policy

The best way to get acquainted with content formatting is to understand what happens when neither the client nor the action method applies any restrictions to the format that can be used. In this situation, the outcome is simple and predictable.

  • 1. If the action method returns a string, the string is sent unmodified to the client, and the Content-Type header of the response is set to text/plain
  • 2. For all other data types, including other simple types such as int, the data is formatted as JSON, and the Content-Type header of the response is set to application/json

Strings get special treatment because they cause problems when they are encoded as JSON. When you encode other simple types, such as the C# int value 2, then the result is a quoted string, such as "2". When you encode a string, you end up with two set of quotes so that "Hello" becomes ""Hello"". Not all clients cope well with this double encoding, so it is more reliable to use the text/plain format and sidestep the issue entirely. This is rarely an issue because few applications send string values; it is more common to send objects in the JSON format. 

[HttpGet("string")]
public string GetString() => "String response";

 The action method above would return the following:

Content-Type: text/plain
Content: String response  

Compared to the action method below:

[HttpGet("object")]
public Foo GetObject() => return new Foo { Name = "Bar" };

Would return the following response:

Content-Type: application/json  
Content: { "Name": "Bar" }

Understanding Content Negotiation

Most clients include and Accept header in a request, which specifies the set of formats that they are willing to receive in the response, expressed as a set of MIME types. Here is the Accept header that Google Chrome sends in requests:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

This header indicates that Chrome can handle the HTML and XHTML formats. The q values in the header specify relative preference, where the value is 1.0 by default. Specifying a q value for 0.9 for application/xml tells the server that Chrome will accept XML data but prefers to deal with HTML or XHTML. The */* item tells the server that Chrome will accept any format, but its q value specifies that it is the lowest preference of the specified types.

However, by default ASP NET Core is configured to only use JSON. Rather then returning an error to the client considering that the client tells the server it cannot process JSON, the MVC Framework will send JSON data in hopes that it can.


Enabling XML formatting

For content negotiation to work, your application must be configured so there is some choice in the formats that can be used. 

Enable XML Formatting in the Startup.cs File

services.AddXmlSerializerFormatters();

Now you will start seeing the content negotiation process working more fully.

[HttpGet("object")]
public Foo GetObject() => return new Foo { Name = "Bar" };

When above action method is called with the Accept header application/xml you should see the following response.

Content-Type: application/xml
Conent: <Foo>
          <Name>Bar</Name>
        </Foo>

Fully respecting Accept Headers

The MVC Framework will always use the JSON format if the Accept header contains */*, indicating any format, even if there are other supported formats with higher preference. This is an odd feature that is intended to deal with requests from browsers consistently, although it can be a source of confusion. 

Two configurations settings are used to tell the MVC Framework to respect the Accept setting sent by the client and not send JSON data by default. To add the configuration, add the below statements to Startup.cs.

services.Configure(opts => { 
    opts.RespectBrowserAcceptHeader = true; 
    opts.ReturnHttpNotAcceptable = true; 
});

Setting RespectBrowserAcceptHeader = true disables the fallback to JSON when the accept header contains */*. 

Setting ReturnHttpNotAcceptable = true disables the fallback to JSON when the client requests an unsupported data format. 

Your clients would now receive (406) Not Acceptable when the content negotiation cannot be resolved.