Content Negotiation in ASP.NET Core Web API

Content Negotiation in ASP.NET Core Web API

In this article, I will discuss How to Implement Content Negotiation in ASP.NET Core Web API Application with Examples. Please read our previous article discussing How to Apply Binding Attributes to Model Properties in ASP.NET Core Web API with Examples.

Why Content Negotiation in Rest Services?

We know that there are three pillars of the Restful Web Service and they are:

  • The Resource
  • The URL
  • The Representation

The first two (i.e., the Resource and the URL) are very straightforward, but the last one (i.e., the Representation) is a little confusing. Representation is very important in the Modern Application. Why? Because, people are currently not only using desktop computers to browse web applications but are also using various types of devices (Tab, Mobile, etc.) to consume web applications. The important and interesting fact is that these devices expect the data in multiple formats.

For example, a few clients want the data in standard HTML, while some want it in a normal text format. Others may need the data in JSON format, and others want the data in XML format. This is where Content Negotiation comes into the picture.

What is Content Negotiation?

One of the Constraints of the REST service is that the client should be able to decide in which format they want the response, i.e., whether they want the response in XML or JSON, etc. This is called Content Negotiation.

In more detail, Content Negotiation in REST services is a mechanism that allows a client and server to agree on the format of the data to be exchanged. This process ensures that the data format is understandable and acceptable to both parties involved in the communication.

How Does the Content Negotiation Work?

Content Negotiation primarily involves two HTTP headers: Accept and Content-Type.

  • Accept Header: Used by the client to specify the media types it is willing and able to understand and process. For instance, a client can use the Accept header to indicate that it prefers to receive data in the JSON format (application/json) or XML format (application/xml).
  • Content-Type Header: Used by the server to specify the media type of the response content it sends back to the client. It tells the client in what format the response data is encoded, ensuring that the client knows how to parse and process the data received.

The process of Content Negotiation in REST services can be outlined as follows:

Client Request

The client initiates the content negotiation process by including specific HTTP headers in its request to the server. The most relevant headers for content negotiation are:

  • Accept: Specifies the media types (MIME types) the client is willing to receive. For example, application/json, application/xml, or text/html.
  • Accept-Language: Indicates the preferred languages for the response, such as en-US or fr-CA.
  • Accept-Charset: Specifies the character sets the client prefers, like UTF-8 or ISO-8859-1.
  • Accept-Encoding: Indicates the encoding schemes (e.g., gzip, deflate) the client can decode.
Server Processing

Upon receiving the request, the server evaluates the client’s preferences against the available representations of the requested resource. The server uses the following logic:

  • Media Type Selection: The server selects the most appropriate media type based on the Accept header. It chooses the Representation that best matches the client’s preferences.
  • Language Selection: The server selects the preferred language based on the Accept-Language header if the resource is available in multiple languages.
  • Character Set and Encoding Selection: Similarly, the server selects a character set and encoding matching the client’s preferences as specified in the Accept-Charset and Accept-Encoding headers.
Server Response

The server responds to the client with the selected Representation of the resource. It includes HTTP headers in the response to indicate the chosen format:

  • Content-Type: Specifies the media type of the returned resource, e.g., application/json or application/xml.
  • Content-Language: Indicates the language of the response.
  • Content-Encoding: Specifies the encoding used for the response data.

If the server cannot serve the request in any of the requested formats, it can respond with a 406 Not Acceptable status code. Alternatively, the server might ignore the client’s preferences and send its default representation of the resource, potentially with a 200 OK status code, indicating that the request has been successfully processed but not according to the client’s detailed preferences.

Implementing Content Negotiation in ASP.NET Core Web API

ASP.NET Core Web API simplifies the implementation of content negotiation through its built-in infrastructure. The framework supports JSON and XML formats out of the box, and additional formats can be supported by configuring custom formatters.

Built-in Support: By default, ASP.NET Core Web API projects are configured to use JSON as the primary data exchange format. XML and other formats can be enabled by configuring Formatters in the Program.cs file.

Custom Formatters: To support additional content types, developers can implement custom Formatters by extending the TextInputFormatter and TextOutputFormatter classes or their binary counterparts for non-text formats.

Configuration: In the Main method of the Program.cs, developers can add or configure Formatters via the AddMvc method or AddControllers method (for API projects). Here, one can specify the supported media types, configure options, and set up custom formatters.

Example to Understand Content Negotiation in ASP.NET Core Web API:

Let us see some examples to understand the concept of Content Negotiation in ASP.NET Core Web API. First, create a class file named Employee.cs and copy and paste the following code. This is the class which we will use to return the response.

namespace ModelBinding.Models
{
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Gender { get; set; }
        public int Age { get; set; }
        public string Department { get; set; }
    }
}
Creating Employee Controller:

Next, create an API Controller named Employee and copy and paste the following code. Here, we have one action method, which will return the list of employees.

using Microsoft.AspNetCore.Mvc;
using ModelBinding.Models;

namespace ModelBinding.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        [HttpGet]
        public ActionResult<List<Employee>> GetEmployees()
        {
            var listEmployees = new List<Employee>()
            {
                new Employee(){ Id = 1001, Name = "Anurag", Age = 28, Gender = "Male", Department = "IT" },
                new Employee(){ Id = 1002, Name = "Pranaya", Age = 28, Gender = "Male", Department = "IT" },
            };

            return Ok(listEmployees);
        }
    }
}
Testing the API using Accept Header:

Now, let us send an HTTP GET Request to the above endpoint using the Accept header and setting its value to application/json, telling the server to send the response in JSON format, as shown in the image below. Please change the Port number where your application is running:

How to Implement Content Negotiation in ASP.NET Core Web API Application with Examples

Now, if you verify the response header, then you will see it also set the Content-Type header and set its value to application/json as shown in the below image:

How to Implement Content Negotiation in ASP.NET Core Web API Application with Examples

Now, let us issue another HTTP GET Request to the above endpoint by setting the Accept header value to application/xml as shown in the below image and see in what format we are getting the response.

How to Implement Content Negotiation in ASP.NET Core Web API

As you can see in the above image, even if we set the Accept header value to application/xml, we get the response in XML format. This is because we have not yet enabled the XML formatter in our application. The only Formatter enabled by default is JSON Formatter, and that JSON Formatter is used to send the response by default irrespective of the Accept header value. Even if you check the response header, the Content-Type header value is set to application/xml.

How do you enable the XML Formatter in ASP.NET Core Web API?

We need to register the XML Formatter service to the dependency injection container, and we can do so in the Program class as follows:

builder.Services.AddControllers()
                .AddXmlSerializerFormatters();

Here, the AddXmlSerializerFormatters() service enabled the XML Formatter for our application. These changes enable both XML and default JSON Formatters for Content Negotiation. Now, if you send an HTTP GET Request with the Accept Header value set to application/xml, then you will get the response in XML format as shown in the below image:

How to Implement Content Negotiation in ASP.NET Core Web API

Note: If you send an HTTP GET Request without specifying the Accept header value or the Accept Header Value to application/json, you will get the response in JSON format.

What happens if we specify both application/json and application/xml in the Accept Header?

While sending an HTTP Request, it is possible to set multiple values separated by a comma for the Accept header. In that case, the first value will take the priority. For example, suppose we set the Accept header value as application/xml,application/json. In that case, the server will give priority to XML and will return the data in XML Format, as shown in the below image:

What happens if we specify both application/json and application/xml in the Accept Header?

Using the Quality Factor (Q Factor) in the Accept header within ASP.NET Core Web API involves setting preferences for different response content types. This is useful when a client can handle multiple content types but prefers them in a specific order. The quality factor, ranging from 0 to 1, indicates this preference, with a higher value signifying higher desirability.

In the example below, JSON has a higher quality factor than XML, so the server will use JSON formatter and formats the data in JSON format.

application/xml;q=0.5,application/json;q=0.8

For a better understanding, please send an HTTP Request using Postman, as shown in the image below:

Quality Factor (Q Factor) in the Accept header within ASP.NET Core Web API

How do you remove the JSON Serializer in ASP.NET Core Web API?

To remove the JSON Formatter, you need to modify the AddControllers or AddMvc method call to customize the options and remove the JSON Formatter as follows:

builder.Services.AddControllers(options =>
{
    // Remove JSON formatter
    options.OutputFormatters.RemoveType<Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter>();
}).AddXmlSerializerFormatters(); //Adding XML Formatter

With the above code in place, the JSON Formatter is removed, and only the XML Formatter is registered to return the Response in XML Format. So, if you send an HTTP Request, the server will return the response in XML format irrespective of the Accept header value.

How do you return 406 Not Acceptable Status Code if the Accepted Formatter is Not Available in ASP.NET Core Web API?

In ASP.NET Core Web API, the 406 Not Acceptable status code is returned when the server cannot produce a response matching the list of acceptable values defined in the request’s headers, particularly in the Accept header. This scenario occurs when the client specifies an Accept header for a media type (e.g., application/xml, application/json) that the server cannot return.

You can use content negotiation to configure an ASP.NET Core Web API application to return a 406 Not Acceptable status code when the accepted formatter is unavailable. ASP.NET Core supports content negotiation but needs explicit configuration to ensure it behaves as expected in specific scenarios, such as returning a 406 Status Code when necessary.

You need to update the configuration of MVC options to add a filter that explicitly checks for acceptable content types and returns a 406 Status Code if none are available. So, add the following line of code. Here, we are using the default JSON Formatter. We are removing the XML formatter. We are also configuring to ignore CamelCase. We are also explicitly setting to return 406 Not Acceptable Status Code:

builder.Services.AddControllers(options =>
{
    // Enable 406 Not Acceptable status code
    options.ReturnHttpNotAcceptable = true;
})
// Optionally, configure JSON options or other Formatter settings
.AddJsonOptions(options =>
{
    // Configure JSON serializer settings if needed
    options.JsonSerializerOptions.PropertyNamingPolicy = null;
});

With the above changes in place, only the Accept header with application/json will work and return the data in JSON format. On the other, if you try to send an HTTP Request with Accept header value set to application/xml, then you will get 406 Not Acceptable Status Code as shown in the below image:

How to Return 406 Not Acceptable Status Code if the Accepted Formatter is Not Available in ASP.NET Core Web API?

How do you customize the 406 status code in ASP.NET Core Web API?

Customizing the HTTP Response Body for a 406 Not Acceptable status code in an ASP.NET Core Web API, when the Accept header specifies a format that is not supported by the server, involves intercepting the content negotiation process or handling the outcome of a failed content negotiation. This can be achieved using a custom middleware component to intercept and modify responses where content negotiation fails.

Creating Custom Middleware Component:

You can create a custom middleware that checks outgoing responses. If a request fails content negotiation and results in a 406 Not Acceptable status, you can customize the response accordingly. So, create a class file named CustomNotAcceptableMiddleware.cs and then copy and paste the following code:

using System.Text.Json;

namespace ModelBinding.Models
{
    public class CustomNotAcceptableMiddleware
    {
        private readonly RequestDelegate _next;

        public CustomNotAcceptableMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            await _next(context);

            if (context.Response.StatusCode == StatusCodes.Status406NotAcceptable)
            {
                // Retrieve the Accept header from the request
                var acceptHeader = context.Request.Headers["Accept"].ToString();

                // Custom response logic here
                context.Response.ContentType = "application/json";
                var response = new 
                { 
                    Code = StatusCodes.Status406NotAcceptable,
                    ErrorMessage = $"The Requested Format {acceptHeader} is Not Supported." 
                };
                await context.Response.WriteAsync(JsonSerializer.Serialize(response));
            }
        }
    }
}
Register the Middleware

After creating your middleware, register it in the application’s request processing pipeline. This is done in the Program.cs class file. So, add the following statement:

app.UseMiddleware<CustomNotAcceptableMiddleware>();

Now, with the above changes in place, if you try to send an HTTP Request with Accept header value set to application/xml, then you will get 406 Not Acceptable Status Code along with the Custom Error Message as shown in the below image:

How to Customize 406 Status Code in ASP.NET Core Web API?

When should you use content negotiation in ASP.NET Core Web API?

Content negotiation in ASP.NET Core Web API is a process where the server selects an appropriate response format based on the client’s request. This mechanism allows a Web API to serve different response data formats (e.g., JSON, XML) depending on the client’s preferences or capabilities. Understanding when to use content negotiation can help design more flexible and client-friendly APIs. Here are key scenarios where content negotiation is particularly beneficial:

  • Supporting Multiple Client Types: When your Web API is consumed by various clients (browsers, mobile apps, other services) with different requirements or preferences for response formats. Content negotiation allows clients to specify their preferred format using the Accept header in their request.
  • Versioning APIs: For APIs that need to support multiple versions, content negotiation can serve different versions of a resource representation without changing the URI. Clients can request a specific version using custom media types or version-specific headers.
  • Resource Optimization: Different response formats can have different sizes and structures. Content negotiation enables serving an optimized format that reduces bandwidth or is easier to parse for the client. For example, a lightweight JSON response might be preferred for mobile clients on a slow network.
  • Internationalization: When your API serves global clients requiring responses in different languages or regional formats, content negotiation can help by selecting the appropriate language or regional format based on the Accept-Language header or other custom headers the client sends.
  • Extensibility: If there’s a possibility of introducing new response formats in the future (e.g., to support new standards or client technologies), content negotiation provides a flexible foundation that allows for these additions without impacting existing clients.
  • Compliance with Standards: Adhering to web standards and protocols. Content negotiation is a standard HTTP feature (defined in RFC 7231) that promotes interoperability and adherence to web standards, enhancing the compatibility of your API with various HTTP clients.
Best Practices
  • Flexibility: Servers should be designed to support multiple representations of a resource to accommodate various client preferences.
  • Transparency: Clients should be aware of the available formats through documentation or other discovery mechanisms.
  • Fallback Mechanisms: Clients should include a broad range of acceptable formats in their Accept header to increase the likelihood of a successful negotiation.

In the next article, I will discuss How to Include and Exclude Properties from Model Binding in ASP.NET Core Web API with Examples. In this article, I try to explain Content Negotiation in ASP.NET Core Web API with Examples, and I hope you enjoy this article, “Content Negotiation in ASP.NET Core Web API.”

1 thought on “Content Negotiation in ASP.NET Core Web API”

Leave a Reply

Your email address will not be published. Required fields are marked *