Isaac.

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

  1. Default format: Usually JSON
  2. Explicit: Prefer Accept header over query params
  3. Clear: Document supported formats
  4. Error messages: Format errors per request
  5. 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.