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 Endicators

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

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 enput 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