Content Negotiation in APIs
Implement flexible content negotiation for API clients.
By EMEPublished: February 20, 2025
content negotiationapi designformatsaccept headers
A Simple Analogy
Content negotiation is like a restaurant menu. Customer requests their preferred format, server delivers it.
Why Content Negotiation?
- Flexibility: Support multiple formats
- Compatibility: Old and new clients
- Performance: Compress when requested
- Standards: Follow REST principles
- Future-proof: Add formats without breaking
Accept Header
GET /api/products HTTP/1.1
Accept: application/json
GET /api/products HTTP/1.1
Accept: application/xml
GET /api/products HTTP/1.1
Accept: application/json, application/xml;q=0.9
The q parameter indicates preference (0-1).
ASP.NET Core Implementation
// Startup
services.AddControllers()
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters();
// Endpoint respects Accept header automatically
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
var product = new Product { Id = id, Name = "Widget" };
return Ok(product);
}
// Client requests:
// Accept: application/json → JSON response
// Accept: application/xml → XML response
Custom Formatters
public class CsvOutputFormatter : TextOutputFormatter
{
public CsvOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv"));
SupportedEncodings.Add(Encoding.UTF8);
}
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var response = context.HttpContext.Response;
var items = context.Object as List<Product>;
var csv = new StringBuilder();
csv.AppendLine("Id,Name,Price");
foreach (var item in items)
{
csv.AppendLine($"{item.Id},{item.Name},{item.Price}");
}
await response.WriteAsync(csv.ToString(), selectedEncoding);
}
}
// Register
services.AddControllers(options =>
options.OutputFormatters.Add(new CsvOutputFormatter()));
Quality Values
public static ContentNegotiator GetNegotiator()
{
var formatters = new MediaTypeFormatterCollection
{
new JsonMediaTypeFormatter(),
new XmlMediaTypeFormatter(),
new FormUrlEncodedMediaTypeFormatter()
};
return new DefaultContentNegotiator(includeMatchOnTypeLevel: true);
}
Best Practices
- Default format: Usually JSON
- Explicit: Prefer Accept header over query params
- Clear: Document supported formats
- Error messages: Format errors per request
- Caching: Include format in cache key
Related Concepts
- Content-Type header
- Media types
- REST principles
- API versioning
Summary
Implement content negotiation to support multiple response formats based on Accept headers, using custom formatters as needed.