HTTP Status Handling and Content Negotiation in ASP.NET Core Web APIs

Foundations of API Resource Design

Effective API architecture relies on clear resource identification, standardized HTTP methods, and predictable payload handling. Consumers expect endpoints to follow RESTful conventions rather than RPC-style patterns.

Naming and Structural Conventions

  • Noun-Based URIs: Endpoints should represent resources, not actions. Use GET /api/users instead of /getUsers.
  • Hierarchical Relationships: URIs should reflect data ownership and structure, enabling clients to navigate related entities logically.
    • Retrieve all employees under a specific organization: GET /api/companies/{companyId}/employees
    • Fetch a single employee record: GET /api/companies/{companyId}/employees/{employeeId}
  • Query Parameters for Filtering/Sorting: Use the query string for non-essential operations like pagination or ordering. Example: GET /api/users?sort=age&order=asc

The [ApiController] Attribute

Decorating a controller class with [ApiController] is optional but highly recommended for modern ASP.NET Core APIs. It automatically enables several production-ready behaviors:

  • Enforces attribute routing exclusively.
  • Automatically returns HTTP 400 when model validation fails.
  • Infers parameter binding sources (e.g., [FromBody], [FromRoute]) without explicit attributes.
  • Handles multipart/form-data binding seamlessly.
  • Generates detailed RFC 7807 problem details for error responses.

HTTP Status Code Categories

Status codes communicate execution outcomes to API consumers, indicating whether an operation succeeded and pinpointing responsibility when it fails.

2xx: Success Indicators

Code Meaning
200 OK The request completed successfully and returns data.
201 Created A new resource was successfully intsantiated.
204 No Content The operation succeeded, but the server intentionally omits a response body (commonly used for deletions).

3xx: Redirection

Primarily used for browser navigation or legacy routing. Modern API architectures generally avoid redirects, preferring client-side resolution or direct endpoint updates.

4xx: Client-Side Failures

Code Meaning
400 Bad Request Malformed syntax, invalid parameters, or missing required fields.
401 Unauthorized Authentication credentials are missing or invalid.
403 Forbidden Authentication succeeded, but the identity lacks permission for the target resource.
404 Not Found The requested URI or resource does not exist on the server.
405 Method Not Allowed The HTTP verb used is not supported for the specified endpoint.
406 Not Acceptable The client requested a response format (via Accept header) that the server cannot generate.
409 Conflict The request conflicts with the current server state, often due to concurrent modifications or duplicate resource creation.
415 Unsupported Media Type The payload format (via Content-Type header) is not recognized or accepted by the API.
422 Unprocessable Entity The request syntax and media type are valid, but semantic validation or business rule checks failed.

Distinguishing 409 and 422

While both indicate client-side rejections, their triggers differ. 409 typically signals state conflicts, such as optimistic concurrency failures during updates or attempting to create a duplicate unique entity. Conversely, 422 is reserved for semantic validation failures where the JSON/XML structure is perfectly parseable, but the contained data violates domain constraints or validation rules.

5xx: Server-Side Faults

500 Internal Server Error indicates an unhandled exception or infrastructure failure. The client cannot resolve these issues, and they usually require logging, monitoring, and backend intervention.

Client Errors vs. System Faults

Understanding the root cause classification is vital for API health monitoring:

  • Client Errors (4xx): Originate from malformed requests, invalid payloads, or incorrect routing. They are expected behaviors that do not degrade service availability. APIs should handle them gracefully with descriptive responses.
  • Server Faults (5xx): Stem from unhandled exceptions, database outages, or configuration issues. These represent genuine system failures and can cascade into broader availability problems if not isolated and monitored.

HTTP Content Negotiation

Content negotiation allows a single endpoint to serve multiple data representations based on client preferences. The framework matches client headers against registered formatters to determine serialization/deserialization strategies.

Response Formatting (Accept Header)

Clients specify their preferred output format using the Accept request header. Common media types include application/json and application/xml. If omitted, the framework defaults to its primary configured formatter. If the requested type is unsupported and strict negotiation is enforced, the server responds with 406.

Request Parsing (Content-Type Header)

The Content-Type header informs the server how to parse the incoming payload. The framework routes the raw request body through the corresponding input formatter to deserialize it into strongly-typed models.

Configuring Formatters in ASP.NET Core

By default, ASP.NET Core registers JSON formatters. To support additional media types or enforce strict negotiation rules, you must modify the MvcOptions pipeline during service registration.

Example 1: Enforcing Strict Accept Headers and Adding XML Output

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        // Throw 406 if the client requests an unsupported format
        options.ReturnHttpNotAcceptable = true;

        var xmlSerializer = new XmlDataContractSerializerOutputFormatter();
        
        // Prepend XML to the formatter list to make it the primary default
        options.OutputFormatters.Insert(0, xmlSerializer);
    });
}

Example 2: Registering XML Input Parsers

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(mvcOptions =>
    {
        var xmlDeserializer = new XmlDataContractSerializerInputFormatter();
        
        mvcOptions.InputFormatters.Add(xmlDeserializer);
        mvcOptions.InputFormatters.Insert(0, xmlDeserializer);
    });
}

Modern Unified Configuration

Recent ASP.NET Core versions simplify this process by providing extension methods that register both input and output XML formatters simultaneously:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
        {
            options.ReturnHttpNotAcceptable = true;
        })
        .AddXmlDataContractSerializerFormatters();
}

Tags: aspnetcore rest-api-design http-status-codes content-negotiation mvc-formatters

Posted on Thu, 14 May 2026 15:54:33 +0000 by mtlhd