Content Negotiation & Output Formatters

15 questions found

What is content negotiation in ASP.NET Core, and how does the Accept header influence it?

Beginner
Content negotiation is the process by which the server determines the format (JSON, XML, plain text) to return a response in, based on the client's Accept header expressing its preferences -- ASP.NET Core's [ApiController] defaults to JSON output but can serve alternate formats if the corresponding output formatter is registered and the client requests it via Accept.
// Client request: Accept: application/xml
[HttpGet("{id}")]
public ActionResult<Product> GetProduct(int id) => Ok(_service.GetById(id));
// Returns XML instead of the default JSON if an XML formatter is registered and requested
Real-world example A legacy enterprise client expecting XML responses and a modern SPA expecting JSON both call the exact same endpoint, each receiving their preferred format automatically based on their respective Accept headers.

Common follow-ups: What happens if the requested format isn't supported by any registered formatter?;How do you force a specific format regardless of the Accept header?

RESTful Web APIs & Controllers;API Documentation with Swagger/OpenAPI

How do you add XML output formatting support to an ASP.NET Core API alongside the default JSON formatter?

Intermediate
AddXmlSerializerFormatters() (or AddXmlDataContractSerializerFormatters() for an alternative serialization approach) registers XML input/output formatters alongside the default JSON ones, letting the same controllers serve both formats automatically based on content negotiation without any per-action code changes.
builder.Services.AddControllers()
    .AddXmlSerializerFormatters();

// Now both of these work automatically:
// GET /api/products/1  Accept: application/json  -> JSON response
// GET /api/products/1  Accept: application/xml   -> XML response
Real-world example A B2B integration platform serving both modern partners (JSON) and legacy enterprise systems (XML) adds XML formatters once at startup, letting every existing controller serve both formats without touching individual action methods.

Common follow-ups: What are the performance and complexity trade-offs of supporting multiple output formats?;How does XML serialization handle types that don't map cleanly to XML, like dictionaries?

RESTful Web APIs & Controllers;Testing ASP.NET Core Applications

How would you write a custom output formatter to support a non-standard format, like CSV, for specific endpoints?

Advanced
Subclass TextOutputFormatter (or OutputFormatter for binary formats), override CanWriteType to declare which types it supports, set SupportedMediaTypes to the custom MIME type (like text/csv), and override WriteResponseBodyAsync to implement the actual serialization logic -- registered via options.OutputFormatters.Add(new CsvOutputFormatter()) in AddControllers, letting clients request text/csv via Accept to receive CSV instead of JSON from the same endpoint.
public class CsvOutputFormatter : TextOutputFormatter {
    public CsvOutputFormatter() {
        SupportedMediaTypes.Add("text/csv");
        SupportedEncodings.Add(Encoding.UTF8);
    }
    protected override bool CanWriteType(Type type) => typeof(IEnumerable<Product>).IsAssignableFrom(type);
    public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding encoding) {
        var products = (IEnumerable<Product>)context.Object;
        var csv = string.Join("\n", products.Select(p => $"{p.Id},{p.Name},{p.Price}"));
        await context.HttpContext.Response.WriteAsync(csv, encoding);
    }
}
Real-world example A reporting API lets analysts request the exact same /api/sales-report endpoint with Accept: text/csv to download data directly importable into Excel, without needing a separate dedicated CSV export endpoint.

Common follow-ups: How does a custom formatter interact with Swagger documentation generation for the extra format?;What's the difference between TextOutputFormatter and the lower-level OutputFormatter base class?

API Documentation with Swagger/OpenAPI;File Uploads & Streaming Large Files

How does ASP.NET Core decide which output format to use when a client's Accept header lists multiple acceptable types with quality values (q-values)?

Intermediate
The framework parses the Accept header's comma-separated list of media types, each optionally annotated with a q-value (like application/json;q=0.9,application/xml;q=0.5) indicating relative preference, and selects the highest-priority type for which a matching output formatter is actually registered -- if no requested type has a matching formatter, behavior depends on configuration (either falling back to a default formatter or returning 406 Not Acceptable).
// Accept: application/xml;q=0.9, application/json;q=0.8
// If both XML and JSON formatters are registered, XML wins due to higher q-value

// Accept: application/pdf
// If no PDF formatter is registered: either falls back to default, or returns 406
Real-world example A client library correctly specifies application/json;q=1.0, application/xml;q=0.5 to express a strong preference for JSON while still accepting XML as an acceptable fallback if JSON somehow weren't available.

Common follow-ups: What does RespectBrowserAcceptHeader configuration control, and why is it off by default?;How do you configure the API to return 406 instead of silently falling back to a default format?

RESTful Web APIs & Controllers;Error Handling

Why is RespectBrowserAcceptHeader disabled by default in ASP.NET Core, and what problem does this default avoid?

Advanced
Browsers typically send an Accept header requesting HTML first (text/html) since that's their primary use case for regular web browsing, even when a developer manually navigates to an API URL expecting JSON -- with RespectBrowserAcceptHeader left at its default false, ASP.NET Core deliberately ignores this browser-supplied preference for API responses and returns the default format (JSON) instead, avoiding the confusing behavior of an API returning an unexpected format just because it was accessed via a browser address bar.
// Browser navigating directly to /api/products sends:
// Accept: text/html, application/xhtml+xml, ...

// With RespectBrowserAcceptHeader = false (default):
// API still returns JSON, ignoring the browser's HTML preference

// With RespectBrowserAcceptHeader = true:
// API might return 406 or attempt HTML formatting, which is rarely desired for a pure API
Real-world example A developer debugging an API by pasting its URL directly into a browser address bar still sees a properly formatted JSON response instead of a confusing 406 error or garbled HTML attempt, thanks to this deliberate default.

Common follow-ups: When would you actually want to enable RespectBrowserAcceptHeader?;How does this interact with an API that also serves some HTML content, like Swagger UI?

RESTful Web APIs & Controllers;API Documentation with Swagger/OpenAPI

How does System.Text.Json's configuration (via AddJsonOptions) let you customize the default JSON serialization behavior globally?

Intermediate
AddJsonOptions lets you configure JsonSerializerOptions applied to every JSON response and request across the API -- common customizations include property naming policy (camelCase vs PascalCase), handling of null values, enum serialization as strings instead of numbers, and reference handling for circular object graphs.
builder.Services.AddControllers()
    .AddJsonOptions(options => {
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    });
Real-world example An API configures global JSON options to serialize enums as readable strings ("Active" instead of 1) and omit null fields entirely, producing cleaner, more JavaScript-idiomatic responses for its frontend consumers.

Common follow-ups: How do these global options interact with per-property [JsonPropertyName] attributes?;What's the performance cost of adding custom converters to every serialization?

RESTful Web APIs & Controllers;Configuration & Options Pattern

How would you support versioned response shapes for the same resource using content negotiation via custom media types instead of URL-based versioning?

Advanced
Media type versioning defines custom vendor-specific media types (like application/vnd.myapp.v2+json) that clients specify in their Accept header, with a custom formatter or action constraint selecting the correct response shape based on the requested version embedded in the media type itself -- an alternative to URL-segment versioning that keeps URLs stable while still allowing multiple concurrent response contract versions.
[HttpGet("{id}")]
[Produces("application/vnd.myapp.v1+json", "application/vnd.myapp.v2+json")]
public IActionResult GetProduct(int id) {
    var acceptHeader = Request.Headers.Accept.ToString();
    return acceptHeader.Contains("v2")
        ? Ok(MapToV2(product))
        : Ok(MapToV1(product));
}
Real-world example A REST purist API uses media-type versioning (application/vnd.api.v2+json) specifically to keep its resource URLs completely stable and semantically 'permanent' across contract versions, following stricter REST/HATEOAS principles than typical URL-segment versioning allows.

Common follow-ups: Why is media-type versioning less commonly adopted in practice than URL or header versioning?;How would you document this versioning approach clearly in OpenAPI/Swagger?

API Versioning;API Documentation with Swagger/OpenAPI

What is the [Produces] attribute, and how does it restrict which content types a specific action can return regardless of what the client's Accept header requests?

Intermediate
[Produces("application/json")] applied to a controller or action explicitly restricts the response to only the listed content type(s), overriding normal content negotiation entirely for that scope -- useful when an endpoint should always return a specific format (like a webhook receiver that always expects JSON) regardless of what a misconfigured or unusual client's Accept header might otherwise request.
[Produces("application/json")]
[HttpPost("webhook")]
public IActionResult ReceiveWebhook(WebhookPayload payload) {
    // Always returns JSON, even if the caller's Accept header requested XML
    return Ok(new { received = true });
}
Real-world example A webhook receiver endpoint forces JSON output via [Produces] specifically because the calling service (a third-party payment provider) doesn't correctly negotiate content types, and JSON must always be returned regardless of its actual Accept header.

Common follow-ups: How does [Produces] interact with content negotiation for the request body versus the response?;What's the difference between [Produces] at the controller level versus the action level?

RESTful Web APIs & Controllers;Error Handling

How does content negotiation handle the request body (input formatters) differently from the response body (output formatters)?

Advanced
Input formatters (like the built-in JSON input formatter) deserialize the incoming request body based on its Content-Type header (not Accept, which governs the response), converting raw bytes into the strongly-typed parameter your action method expects -- a mismatch between the actual Content-Type header and the request body's real format (or an unsupported Content-Type with no matching input formatter) results in a 415 Unsupported Media Type response before your action code even runs.
// Client sends Content-Type: application/json with a JSON body -- input formatter deserializes it
[HttpPost]
public IActionResult CreateProduct([FromBody] ProductDto dto) { ... }

// Client sends Content-Type: application/xml but no XML input formatter registered:
// -> 415 Unsupported Media Type, action never executes
Real-world example A client accidentally sending Content-Type: text/plain with a JSON-formatted body receives a 415 Unsupported Media Type response rather than a confusing model binding failure, since no input formatter registered for text/plain can even attempt to deserialize the request.

Common follow-ups: What's the difference between 415 (wrong Content-Type) and 400 (malformed body of the correct type)?;How would you add a custom input formatter for a non-standard request format?

Model Binding & Validation;Error Handling

How does minimal API content negotiation differ from the [ApiController]-based MVC approach, given minimal APIs return results via IResult?

Intermediate
Minimal APIs primarily default to JSON via Results.Json() / implicit JSON serialization for returned objects, with less built-in support for automatic multi-format content negotiation compared to MVC's formatter pipeline -- supporting additional formats in minimal APIs typically requires more explicit, manual logic (checking the Accept header yourself and choosing a response type accordingly) rather than the declarative, pluggable output formatter system MVC controllers benefit from automatically.
app.MapGet("/products/{id}", (int id, HttpContext context) => {
    var product = GetProduct(id);
    if (context.Request.Headers.Accept.ToString().Contains("xml")) {
        return Results.Text(SerializeToXml(product), "application/xml");
    }
    return Results.Json(product);
});
Real-world example A minimal-API-based service supporting a rare XML-consuming legacy client implements manual Accept header inspection for that one specific endpoint, since minimal APIs lack MVC's automatic formatter-based content negotiation pipeline out of the box.

Common follow-ups: Why did minimal APIs deprioritize built-in multi-format negotiation compared to MVC?;How would you build a reusable helper for this manual negotiation pattern across many minimal API endpoints?

Controllers vs Minimal APIs;RESTful Web APIs & Controllers

Showing 1–10 of 15