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 an ASP.NET Core Web API Application with Examples. Please read our previous article on how to Apply the Binding Attributes to Model Properties in an ASP.NET Core Web API with Examples. Content Negotiation is one of the RESTful constraints that ensure clients receive data in a format they can process. ASP.NET Core Web API provides robust support for content negotiation, allowing developers to seamlessly serve multiple data formats such as JSON and XML.

Why Content Negotiation in Restful Services?

Content Negotiation is essential in RESTful services because it allows the service to serve data in different formats based on client requests. Content Negotiation is crucial in RESTful services for several reasons. Some of them are as follows:

  • Client Flexibility: Different clients may have varying requirements or preferences for data formats. For example, a web application might prefer JSON for its lightweight structure, while a legacy system might require XML.
  • Interoperability: Supporting multiple data formats ensures that many clients can interact with your API without compatibility issues.
  • Performance Optimization: Clients can choose the most efficient format for their needs, potentially reducing bandwidth usage and improving performance.

Consider a server that supports two content types: JSON (default) and XML. Here’s how different clients interact with the server. For a better understanding, please have a look at the following diagram:

Why Content Negotiation in Restful Services?

Example Scenario
  • Client 1: Requests data in JSON format via the Accept: application/json header. The server responds with JSON.
  • Client 2: Requests data in XML format via the Accept: application/xml header. The server responds with XML.
  • Client 3: Requests data in an unsupported format, say application/xyz. The server defaults to JSON, the primary format, or returns a 406 Not Acceptable status if configured to enforce strict content negotiation.
What is Content Negotiation in ASP.NET Core Web API?

Content Negotiation is the mechanism through which a server selects the appropriate representation (data format) of a resource based on the client’s request. When a client makes a request, it typically includes an Accept header listing the media types (e.g., application/json, application/xml) it can handle. ASP.NET Core then attempts to find a suitable Output Formatter that can produce one of the requested media types. If it cannot produce any acceptable type, it may fall back to a default format or return a 406 (Not Acceptable) status.

How Does the Content Negotiation Work in ASP.NET Core Web API?

Content Negotiation primarily relies on HTTP headers, i.e., Accept and Content-Type: The Content Negotiation works as follows:

  • Client Sends a Request: The client includes an Accept header in its HTTP request, specifying the desired media types (e.g., Accept: application/json, application/xml).
  • Server Inspects the Accept Header: Upon receiving the request, the server examines the Accept header to identify the preferred response formats.
  • Server Chooses the Best Media Type: Based on the client’s preferences and the server’s supported media types, the server selects the most suitable formatter to serialize the response content.
  • Response Generation: The server serializes the response using the chosen formatter and sets the Content-Type header accordingly before sending the response back to the client.
  • Handling Unsupported Formats: If none of the client’s requested media types are supported, the server can respond with a 406 Not Acceptable status code, indicating the inability to fulfill the request in the desired format.
Example to Understand Content Negotiation in ASP.NET Core Web API:

ASP.NET Core Web API simplifies the implementation of Content Negotiation through built-in services and middleware. By default, it supports JSON formatting, but you can easily enable additional formats like XML. In the Program class, you can configure Formatters via the AddControllers method.

Let us look at some examples to understand the concept of Content Negotiation in ASP.NET Core Web API. First, create a new ASP.NET Core Web API project named ContentNegotiationDemo.

Creating Employee Model:

First, create a class file named Employee.cs within the Models folder and copy and paste the following code. We will use this class to return the response in different formats like JSON and XML.

namespace ContentNegotiationDemo.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 Empty Controller named EmployeeController within the Controllers folder and copy and paste the following code. Here, we have one action method that returns the list of employees.

using Microsoft.AspNetCore.Mvc;
using ContentNegotiationDemo.Models;

namespace ContentNegotiationDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        //GET api/employee
        [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:

Why Content Negotiation in Restful Services?

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 as the server returns the response in JSON format:

What is Content Negotiation in ASP.NET Core Web API?

HTTP Request to Get Data in XML format:

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 Does the Content Negotiation Work in ASP.NET Core Web API?

As you can see in the above image, we set the Accept header value to application/xml, but 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/json.

How Do We Enable 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 this in the Program class as follows:

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

Here, the AddXmlSerializerFormatters() service enabled the XML Formatter for our application. These changes allow both XML and default JSON Formatters to be used 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 Do We Enable XML Formatter 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. Based on the response, it will also set the Content-Type header value.

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. That means if the client specifies both application/json and application/xml in the Accept header, the server will choose the first media type it can support in the order provided by the client. For instance:

  • If the client sends an Accept: application/json, application/xml header, and if the server supports JSON, JSON will be preferred.
  • If the order is reversed (Accept: application/xml, application/json), XML will be preferred if the server supports it.

For example, 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?

How Do We Remove the JSON Serializer in ASP.NET Core Web API?

JSON Formatter is registered by default in ASP.NET Core as it is the most preferred approach for sending and receiving data nowadays. To remove the JSON Formatter in ASP.NET Core Web API, we need to modify the AddControllers 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. With the above changes in place, send an HTTP Request, and the server will return the response in XML format irrespective of the Accept header value.

How Do We Remove the JSON Serializer in ASP.NET Core Web API?

How Do We Return 406 Not Acceptable Status Code if the Accepted Formatter is Not Available?

In ASP.NET Core Web API, the 406 Not Acceptable status code is returned when the server cannot produce a response based on the HTTP Request Accept header value. 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.

By default, the ASP.NET Core does not return the 406 Not Acceptable status code. Instead, it returns the data using the default configured formatter. To force ASP.NET Core Web API to return a 406 Not Acceptable status code if the requested format is not supported, we need to configure the ReturnHttpNotAcceptable option and set its value to true. 

So, modify the AddControllers service as follows. Here, we are using the default JSON Formatter. We removed the XML formatter and also configured it 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 the data will be returned 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 Do We Return 406 Not Acceptable Status Code if the Accepted Formatter is Not Available?

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

ASP.NET Core doesn’t provide built-in support for customizing the 406 Not Acceptable status code directly. So, to customize the 406 Not Acceptable status code, we need to create a custom middleware component to handle unsupported media types.

So, create a class file named CustomNotAcceptableMiddleware.cs within the Models folder and copy and paste the following code. The following custom middleware checks the outgoing responses. If the response status code is 406 Not Acceptable, it will customize the response accordingly. The following code is self-explained, so please read the comment lines for a better understanding.

// Importing the namespace for working with JSON serialization and deserialization
using System.Text.Json;

namespace ContentNegotiationDemo.Models
{
    // A custom middleware class that handles '406 Not Acceptable' responses
    public class CustomNotAcceptableMiddleware
    {
        // Defining a field to store the next middleware in the pipeline
        private readonly RequestDelegate _next;

        // Constructor that takes in the next middleware in the pipeline
        public CustomNotAcceptableMiddleware(RequestDelegate next)
        {
            // Assigning the injected middleware to the private field
            _next = next;
        }

        // Middleware method called 'Invoke', which is executed for every HTTP request
        public async Task Invoke(HttpContext context)
        {
            // Calling the next middleware in the pipeline and waiting for its completion
            await _next(context);

            // Check if the response status code is 406 Not Acceptable
            if (context.Response.StatusCode == StatusCodes.Status406NotAcceptable)
            {
                // Retrieve the "Accept" header from the request, which indicates the client's preferred response formats
                var acceptHeader = context.Request.Headers["Accept"].ToString();

                // Set the Content-Type of the response to 'application/json' to indicate that the response is in JSON format
                context.Response.ContentType = "application/json";

                // Create an anonymous object containing the status code and a custom error message based on the unsupported format
                var response = new
                {
                    Code = StatusCodes.Status406NotAcceptable, // HTTP status code 406
                    ErrorMessage = $"The Requested Format {acceptHeader} is Not Supported." // Custom error message showing the unsupported format
                };

                // Serialize the anonymous object to JSON and write it to the response body
                await context.Response.WriteAsync(JsonSerializer.Serialize(response));
            }
        }
    }
}
Register the Custom Middleware Component

After creating the Custom Middleware component, we need to register it into the Request Processing Pipeline. This is done in the Program class by adding the following statement before the MVC Middleware Component:

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 Do We Implement Content Negotiation in ASP.NET Core Web API?

Content Negotiation is a powerful feature in ASP.NET Core Web API that enhances the flexibility and interoperability of your Web APIs. By understanding and effectively implementing content negotiation, you can ensure that many clients can consume your APIs. Whether defaulting to JSON, enabling XML, or enforcing strict response formats, ASP.NET Core provides the necessary support to manage content types efficiently and effectively.

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 will try to explain Content Negotiation in ASP.NET Core Web API with examples. I hope you enjoy this article, “Content Negotiation in ASP.NET Core Web API.”

3 thoughts on “Content Negotiation in ASP.NET Core Web API”

Leave a Reply

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