Content Negotiation in Web API
By FoxLearn 1/17/2025 8:26:02 AM 26
Although this feature has been a part of HTTP for a long time, it is often underused in API development.
In this example, we will build a simple product catalog API and demonstrate how content negotiation works in ASP.NET Core Web API. This will include returning responses in different formats (JSON, XML) based on the client’s preferences, and also how to restrict and extend the available formats using custom formatters.
How Content Negotiation Works
Content negotiation occurs when a client sends an Accept
header in the HTTP request, specifying the desired media type for the response.
By default, ASP.NET Core Web API returns responses in JSON format, but you can configure it to return other formats as well, such as XML or plain text.
ASP.NET Core supports the following media types by default:
application/json
text/json
text/plain
Let's begin by creating a simple product catalog with a Product
model and a ProductCatalog
controller.
Define the Product Model
We’ll define a simple Product
model to represent products in the catalog.
public class Product { public string Name { get; set; } public decimal Price { get; set; } public string Description { get; set; } public bool InStock { get; set; } }
Create the ProductCatalog Controller
Next, we create the ProductCatalogController
that returns a list of products.
[Route("api/products")] public class ProductCatalogController : Controller { public IActionResult Get() { var products = new List<Product> { new Product { Name = "Laptop", Price = 999.99, Description = "High-performance laptop", InStock = true }, new Product { Name = "Smartphone", Price = 599.99, Description = "Latest model smartphone", InStock = true }, new Product { Name = "Headphones", Price = 199.99, Description = "Noise-cancelling headphones", InStock = false } }; return Ok(products); } }
This will use the Ok()
method to return the list of products as the response body.
Default JSON Response
When you run the application and request the /api/products
endpoint, the server will return the products in JSON format by default:
[ { "name": "Laptop", "price": 999.99, "description": "High-performance laptop", "inStock": true }, { "name": "Smartphone", "price": 599.99, "description": "Latest model smartphone", "inStock": true }, { "name": "Headphones", "price": 199.99, "description": "Noise-cancelling headphones", "inStock": false } ]
Adding XML Support
If we want to return XML instead of JSON, we need to configure the Web API to handle the Accept
header properly. We can add XML formatters by modifying the configuration in the Program.cs
or Startup.cs
file.
Modify the configuration to respect the browser's Accept
header and include XML formatters:
builder.Services.AddControllers(options => { options.RespectBrowserAcceptHeader = true; }).AddXmlSerializerFormatters();
Now, if the client sends an Accept: application/xml
header, the server will respond with an XML representation of the products:
<ArrayOfProduct xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ProductCatalog"> <Product> <Description>High-performance laptop</Description> <InStock>true</InStock> <Name>Laptop</Name> <Price>999.99</Price> </Product> <Product> <Description>Latest model smartphone</Description> <InStock>true</InStock> <Name>Smartphone</Name> <Price>599.99</Price> </Product> <Product> <Description>Noise-cancelling headphones</Description> <InStock>false</InStock> <Name>Headphones</Name> <Price>199.99</Price> </Product> </ArrayOfProduct>
Restricting Media Types
You might want to restrict the media types your API can handle.
For example, if a client requests a media type that your API doesn't support, you can return a 406 Not Acceptable
status.
builder.Services.AddControllers(options => { options.RespectBrowserAcceptHeader = true; options.ReturnHttpNotAcceptable = true; }).AddXmlSerializerFormatters();
This is done by adding the ReturnHttpNotAcceptable
option in the configuration.
Now, if the client requests a media type that is unsupported (like text/html
), the API will return a 406 Not Acceptable
status.
Custom Formatters for Specialized Responses
Sometimes, you may need to create a custom response format, such as CSV. ASP.NET Core allows you to create custom output formatters for handling non-standard formats.
Let’s implement a custom CsvOutputFormatter
for our product catalog.
public class CsvOutputFormatter : TextOutputFormatter { public CsvOutputFormatter() { SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/csv")); SupportedEncodings.Add(Encoding.UTF8); } protected override bool CanWriteType(Type? type) => typeof(IEnumerable<Product>).IsAssignableFrom(type); public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { var response = context.HttpContext.Response; var buffer = new StringBuilder(); if (context.Object is IEnumerable<Product> products) { foreach (var product in products) { buffer.AppendLine($"{product.Name}, {product.Price}, {product.Description}, {product.InStock}"); } } await response.WriteAsync(buffer.ToString(), selectedEncoding); } }
To use this custom formatter, we need to add it to the list of output formatters in the configuration:
builder.Services.AddControllers(options => { options.RespectBrowserAcceptHeader = true; options.ReturnHttpNotAcceptable = true; }).AddXmlSerializerFormatters() .AddMvcOptions(options => options.OutputFormatters.Add(new CsvOutputFormatter()));
Now, if a client requests Accept: application/csv
, the server will respond with the product data in CSV format:
Laptop, 999.99, High-performance laptop, True Smartphone, 599.99, Latest model smartphone, True Headphones, 199.99, Noise-cancelling headphones, False
In this example, we demonstrated how content negotiation works in ASP.NET Core Web API using a product catalog. We covered:
- Returning the default JSON response
- Configuring the API to return XML based on the
Accept
header - Restricting unsupported media types and returning a
406 Not Acceptable
status - Implementing a custom CSV output formatter for specialized content
By utilizing content negotiation, you can make your API more flexible and adapt to different client needs, providing responses in multiple formats while maintaining control over the supported media types.
- How to fix 'InvalidOperationException: Scheme already exists: Bearer'
- How to fix System.InvalidOperationException: Scheme already exists: Identity.Application
- Add Thread ID to the Log File using Serilog
- Handling Exceptions in .NET Core API with Middleware
- InProcess Hosting in ASP.NET Core
- Limits on ThreadPool.SetMinThreads and SetMaxThreads
- Controlling DateTime Format in JSON Output with JsonSerializerOptions
- ‘s’ is an invalid start of a value. Path: $ in ASP.NET Core