ASP.NET - ASP.NET Core Content Negotiation and Formatters

Content negotiation is an important feature in ASP.NET Core Web API that allows a server and a client to agree on the format in which data should be exchanged. Different clients may prefer different representations of the same data. For example, one client may request JSON while another may require XML. ASP.NET Core uses HTTP headers, media types, and formatters to determine how a response should be represented. By default, JSON is the primary response format in ASP.NET Core Web API. (Microsoft Learn)

1. What Is Content Negotiation?

Content negotiation is the process through which an HTTP client indicates the format it wants to receive and the server determines whether it can provide the response in that format.

For example, suppose an API has the following endpoint:

[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
    var product = new Product
    {
        Id = id,
        Name = "Laptop",
        Price = 55000
    };

    return Ok(product);
}

A client can request the resource using an HTTP Accept header:

Accept: application/json

The server then attempts to produce the response as JSON.

Another client could request:

Accept: application/xml

If XML formatting has been configured, ASP.NET Core can return the same product as XML.

Therefore, the underlying object remains the same, but its representation can change according to the client's requirements. ASP.NET Core performs this process using output formatters. (Microsoft Learn)

2. Understanding Media Types

Media types, also called MIME types, describe the format of data being transferred between a client and a server.

Some common media types are:

application/json
application/xml
text/plain
text/html

For example:

Accept: application/json

means that the client prefers JSON.

Similarly:

Accept: application/xml

means that the client prefers XML.

The Accept header is particularly important for response content negotiation. ASP.NET Core examines the requested media types and attempts to locate a formatter capable of producing one of them. (Microsoft Learn)

It is important to distinguish Accept from Content-Type.

Accept tells the server:

"What format do I want the response in?"

Content-Type tells the server:

"What format is the data I am sending?"

For example:

POST /api/products
Content-Type: application/json

indicates that the request body contains JSON.

A response might contain:

Content-Type: application/json

which indicates that the response body is JSON.

3. What Are Formatters?

Formatters are components responsible for converting between HTTP data and .NET objects.

ASP.NET Core has two major types:

  1. Input formatters

  2. Output formatters

Input formatters process data coming into the application. Output formatters process data going out of the application.

Microsoft's documentation describes input formatters as components that read objects from the request body, while output formatters write objects to the response stream. (Microsoft Learn)

The basic flow can be represented as:

Client Request
      |
      v
Content-Type
      |
      v
Input Formatter
      |
      v
.NET Object
      |
      v
Controller
      |
      v
.NET Object
      |
      v
Output Formatter
      |
      v
HTTP Response

This separation allows ASP.NET Core to support different data formats without requiring the controller to manually serialize or deserialize every request and response.

4. Input Formatters

An input formatter is responsible for converting data from an HTTP request body into a .NET object.

Consider this model:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

A client might send:

{
    "id": 1,
    "name": "Laptop",
    "price": 55000
}

with:

Content-Type: application/json

ASP.NET Core's JSON input formatter reads the JSON and converts it into a Product object.

The controller can then work with the strongly typed object rather than manually processing the JSON string.

ASP.NET Core provides built-in input formatters for JSON and XML, although JSON input formatting is enabled by default in the standard configuration. (Microsoft Learn)

5. Output Formatters

An output formatter performs the reverse operation.

Suppose the controller produces:

var product = new Product
{
    Id = 1,
    Name = "Laptop",
    Price = 55000
};

return Ok(product);

The controller is returning a .NET object.

The output formatter converts that object into a representation suitable for the HTTP response.

For JSON, the response could become:

{
    "id": 1,
    "name": "Laptop",
    "price": 55000
}

If an XML formatter is configured and the client requests XML, the same object can be represented as XML.

Thus, the controller does not necessarily need separate methods for JSON and XML. The formatter system handles the representation of the response. (Microsoft Learn)

6. How ASP.NET Core Selects a Formatter

When a client sends an Accept header, ASP.NET Core examines the requested media types in their preference order.

For example:

Accept: application/xml, application/json

The client is indicating that XML is preferred, followed by JSON.

ASP.NET Core looks for an available formatter capable of producing one of those formats.

If a suitable formatter is found, it is used to produce the response.

If the requested format cannot be produced, the application's configuration determines what happens. With ReturnHttpNotAcceptable enabled, ASP.NET Core can return HTTP 406 Not Acceptable; otherwise, it can fall back to another formatter that can produce the response. (Microsoft Learn)

7. JSON as the Default Format

JSON is the standard format used by most modern Web APIs because it is lightweight, easy for applications to process, and supported by virtually every modern programming language and platform.

For example:

[HttpGet]
public IActionResult GetProducts()
{
    var products = new[]
    {
        new { Id = 1, Name = "Laptop" },
        new { Id = 2, Name = "Tablet" }
    };

    return Ok(products);
}

A normal API client will generally receive JSON.

ASP.NET Core provides JSON formatting through its JSON formatter infrastructure. Current ASP.NET Core documentation also describes configuration options for the System.Text.Json-based formatter. (Microsoft Learn)

8. Adding XML Formatting

Although JSON is the default, an application may need to support XML for legacy systems, enterprise applications, or clients that specifically require XML.

XML formatters can be enabled using:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.MapControllers();

app.Run();

After configuring XML support, a client can request:

Accept: application/xml

and ASP.NET Core can use the XML output formatter to generate the response.

Microsoft specifically documents AddXmlSerializerFormatters() as a way to configure XML formatters. (Microsoft Learn)

9. Input and Output Formatting Are Different

A common misunderstanding is that the same formatter necessarily handles both incoming and outgoing data.

They are separate responsibilities.

For example:

Request
JSON
  |
  v
JSON Input Formatter
  |
  v
Product Object

and:

Product Object
  |
  v
XML Output Formatter
  |
  v
XML Response

This means an application could receive JSON from a client while returning XML to that client, depending on its configuration and requested response format.

Microsoft identifies separate input and output formatter collections within ASP.NET Core MVC. (Microsoft Learn)

10. Configuring JSON Serialization

ASP.NET Core allows developers to configure JSON serialization behavior.

For example:

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNamingPolicy = null;
    });

This changes the naming behavior of JSON properties.

For example, depending on configuration, a C# property such as:

ProductName

may be represented as:

{
    "productName": "Laptop"
}

or:

{
    "ProductName": "Laptop"
}

ASP.NET Core exposes JsonSerializerOptions through its MVC JSON configuration options. (Microsoft Learn)

11. Custom Formatters

Sometimes an application needs to support a format that is not handled by the built-in formatters.

For example, an organization may use a specialized format such as:

text/vcard

or another proprietary media type.

In such cases, developers can create a custom formatter.

A custom formatter generally involves:

Custom Formatter
       |
       +-- Supported media types
       |
       +-- Supported encodings
       |
       +-- Read operation
       |
       +-- Write operation

For an output formatter, developers can derive from classes such as TextOutputFormatter.

For an input formatter, developers can derive from TextInputFormatter.

The formatter can specify which media types it supports and implement the logic required to read or write the data. (Microsoft Learn)

A custom formatter can then be registered with MVC:

builder.Services.AddControllers(options =>
{
    options.InputFormatters.Insert(0, new MyInputFormatter());
    options.OutputFormatters.Insert(0, new MyOutputFormatter());
});

Formatter order matters because ASP.NET Core evaluates formatters in their configured order. (Microsoft Learn)

12. Browser Requests and Content Negotiation

Browsers can send complicated Accept headers containing several possible formats.

ASP.NET Core normally does not honor all browser Accept header preferences in the same way it does for typical API clients. By default, browser Accept headers can be ignored to provide more predictable behavior.

If an application needs to respect browser Accept headers, it can configure:

builder.Services.AddControllers(options =>
{
    options.RespectBrowserAcceptHeader = true;
});

This is useful when browser-based clients genuinely need content negotiation. (Microsoft Learn)

13. Handling Unsupported Formats

Suppose an API supports JSON but a client requests:

Accept: application/pdf

If the API does not have a formatter capable of producing PDF, it cannot fulfill that particular representation request.

An application can configure:

builder.Services.AddControllers(options =>
{
    options.ReturnHttpNotAcceptable = true;
});

With this configuration, ASP.NET Core can return:

406 Not Acceptable

when no formatter can satisfy the requested format. (Microsoft Learn)

This is useful because it makes the API's behavior explicit instead of silently returning a different format than the client requested.

14. Format-Specific Action Results

Developers can also explicitly specify the format rather than allowing content negotiation to choose it.

For example:

return new JsonResult(product);

forces a JSON-oriented result.

Similarly, ContentResult can be used when a specific textual response is required.

Microsoft notes that format-specific action results such as JsonResult and ContentResult can use a specified format instead of simply following the client's requested format. (Microsoft Learn)

15. URL-Based Format Selection

ASP.NET Core can also support scenarios where the requested format is indicated through a URL or route/query value rather than only through the Accept header.

For example, an application could conceptually expose:

/api/products/1?format=json

or:

/api/products/1?format=xml

Format mappings can associate a URL format value with a corresponding media type. ASP.NET Core provides formatter-related types such as FormatFilter and FormatterMappings for this purpose. (Microsoft Learn)

This approach can be useful for APIs that need predictable, URL-based format selection.

16. Advantages of Content Negotiation

Content negotiation provides several important benefits.

First, it allows the same API endpoint to serve different client requirements.

Second, it keeps controllers focused on application logic rather than serialization details.

Third, it makes APIs more interoperable because different technologies can consume representations they understand.

Fourth, formatters provide a centralized mechanism for handling serialization and deserialization.

Finally, custom formatters allow applications to support specialized data formats without redesigning the entire API architecture.

17. Example of the Complete Process

Consider an API endpoint:

[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
    var product = new Product
    {
        Id = id,
        Name = "Laptop",
        Price = 55000
    };

    return Ok(product);
}

A JSON client sends:

GET /api/products/1
Accept: application/json

The process is:

Client
  |
  | Accept: application/json
  v
ASP.NET Core
  |
  v
Content Negotiation
  |
  v
JSON Output Formatter
  |
  v
JSON Response

Another client could send:

GET /api/products/1
Accept: application/xml

assuming XML support has been configured:

Client
  |
  | Accept: application/xml
  v
ASP.NET Core
  |
  v
Content Negotiation
  |
  v
XML Output Formatter
  |
  v
XML Response

The controller remains essentially the same in both cases.

18. Content Negotiation Versus Serialization

These concepts are related but should not be confused.

Serialization is the process of converting an object into a representation such as JSON or XML.

Deserialization is the process of converting JSON or XML into an object.

Content negotiation is the process of deciding which representation should be used based on the client's request and the server's capabilities.

For example:

Client requests XML
        |
        v
Content Negotiation
        |
        v
XML Formatter Selected
        |
        v
Object Serialized as XML

Thus, content negotiation determines the desired representation, while the formatter performs the actual formatting work.

19. Practical Applications

Content negotiation and formatters are particularly useful when an API has multiple types of clients.

For example, an organization may have a modern web application that prefers JSON and an older enterprise application that still requires XML. Instead of creating completely separate APIs, the same ASP.NET Core API can potentially support both representations through appropriate formatters.

They are also useful when integrating third-party systems, migrating legacy applications, supporting specialized media types, or gradually modernizing an existing API.

Conclusion

ASP.NET Core Content Negotiation and Formatters provide a structured mechanism for controlling how data moves between clients and Web APIs. Content negotiation examines client preferences, particularly the Accept header, while input and output formatters handle the conversion between HTTP representations and .NET objects. JSON is the default response format, while additional formats such as XML can be configured when required. (Microsoft Learn)

The key idea is that controllers work primarily with application objects, while formatters take responsibility for representing those objects as HTTP data. This separation makes ASP.NET Core APIs easier to maintain, more flexible, and capable of supporting clients with different data-format requirements. When built-in formatters are insufficient, developers can create custom input or output formatters for specialized media types. (Microsoft Learn)