ASP.NET Web API 2

This section documents ASP.NET MVC 5.3.x, ASP.NET Web API 2.2, ASP.NET Web Pages 3, OWIN/Katana, SignalR 2, and ASP.NET Identity 2 — all running on .NET Framework 4.8.1 — the System.Web-hosted MVC framework, its routing, Razor views, HTML helpers, model binding, filters, and the OWIN-based authentication/Identity stack — as described by the official documentation at Microsoft Learn (plus Web API, Web Pages, SignalR, and Identity), which are the reference these pages are written and verified against.

This is not ASP.NET Core MVC. ASP.NET MVC 5 is the pre-Core, System.Web-hosted MVC framework; it is functionally frozen and receives only security fixes. For the current, cross-platform MVC framework see MVC Controllers and Views under ASP.NET Core (Blazor).

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page documents ASP.NET Web API 2.2 on .NET Framework 4.8.1 — not ASP.NET Core’s unified controller pipeline, where Web API’s concepts (ApiController, content negotiation, [ApiController]) merged into the same MVC pipeline used for views (see Web API Controllers under ASP.NET Core).

ApiController vs. Controller: two separate pipelines

Despite living in the same Visual Studio project (and often the same Controllers folder) as MVC controllers, Web API 2 is architecturally independent: System.Web.Http.ApiController is unrelated to System.Web.Mvc.Controller, is invoked through its own HttpConfiguration/HttpControllerDispatcher rather than MvcHandler, and was designed from the start to also run self-hosted outside System.Web entirely (see OWIN self-hosting below) — something MVC’s Controller cannot do.

public class ProductsController : ApiController      // System.Web.Http.ApiController, not System.Web.Mvc.Controller
{
    private readonly IProductRepository _repository;
    public ProductsController(IProductRepository repository) => _repository = repository;

    public IEnumerable<Product> Get() => _repository.GetAll();
}

HttpConfiguration and WebApiConfig

// App_Start/WebApiConfig.cs
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional });

        config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
            new CamelCasePropertyNamesContractResolver();
        config.Formatters.Remove(config.Formatters.XmlFormatter);   // JSON-only API
    }
}
// Global.asax.cs: GlobalConfiguration.Configure(WebApiConfig.Register);

HttpConfiguration is Web API’s equivalent of MVC’s RouteTable/GlobalFilters combined — routes, formatters, message handlers, and filters all hang off this one object, separately from MVC’s own configuration (see Getting Started).

Routing: attribute and convention

[RoutePrefix("api/products")]
public class ProductsController : ApiController
{
    [Route("")]
    public IHttpActionResult Get() => Ok(_repository.GetAll());

    [Route("{id:int}", Name = "GetProduct")]
    public IHttpActionResult Get(int id)
    {
        var product = _repository.Find(id);
        return product == null ? (IHttpActionResult)NotFound() : Ok(product);
    }
}

Convention routing (api/{controller}/{id} above) maps HTTP verbs to method-name prefixes (Get, Post, Put, Delete) when no explicit [Http*]/[Route] attribute is present — the same conceptual mechanism as MVC’s MapRoute, but on a separate HttpConfiguration.Routes collection (see Routing and Areas for MVC’s own routing).

IHttpActionResult and ApiController helpers

IHttpActionResult (Web API 2’s counterpart to MVC’s ActionResult) has a single ExecuteAsync method; ApiController provides factory helpers that cover almost every case:

public IHttpActionResult Post(Product product)
{
    if (!ModelState.IsValid) return BadRequest(ModelState);
    _repository.Add(product);
    return CreatedAtRoute("GetProduct", new { id = product.Id }, product);   // 201 + Location header
}

public IHttpActionResult Delete(int id)
{
    var product = _repository.Find(id);
    if (product == null) return NotFound();
    _repository.Remove(product);
    return Ok();
}

Ok, NotFound, BadRequest, CreatedAtRoute, Conflict, Unauthorized, and StatusCode cover the majority of REST responses without hand-constructing an HttpResponseMessage.

Content negotiation, formatters, JSON.NET

Web API inspects the request’s Accept header and picks a registered MediaTypeFormatter (JsonMediaTypeFormatter by default, backed by Json.NET/Newtonsoft.Json) to serialize the response — distinct from, and configured separately from, any JSON settings MVC itself might use for JsonResult:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.NullValueHandling
    = NullValueHandling.Ignore;

HttpRequestMessage/HttpResponseMessage, message handlers vs. filters

Web API is built directly on System.Net.Http (HttpRequestMessage/HttpResponseMessage), the same types HttpClient uses — unlike MVC, which is built on System.Web’s `HttpContextBase. Message handlers (DelegatingHandler) sit before routing and see every request/response regardless of which controller handles it (good for cross-cutting HTTP-level concerns: logging, compression); filters (ActionFilterAttribute etc., a separate System.Web.Http.Filters hierarchy from MVC’s System.Web.Mvc filters) run per-controller, after routing, mirroring MVC’s own filter pipeline conceptually but with distinct base types:

public class LoggingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);
        Trace.TraceInformation($"{request.Method} {request.RequestUri} -> {(int)response.StatusCode}");
        return response;
    }
}
// config.MessageHandlers.Add(new LoggingHandler());

CORS

// NuGet: Microsoft.AspNet.WebApi.Cors
public static void Register(HttpConfiguration config)
{
    var cors = new EnableCorsAttribute("https://app.example.com", "*", "GET,POST,PUT,DELETE");
    config.EnableCors(cors);
}

[EnableCors] can also be applied per controller/action for finer-grained origins. See Enable Cross-Origin Requests in ASP.NET Web API 2.

OData v4

Microsoft.AspNet.OData layers query-string-driven filtering/sorting/paging ($filter, $orderby, $top, $expand) onto a Web API controller with almost no extra code:

public class ProductsController : ODataController
{
    [EnableQuery]
    public IQueryable<Product> Get() => _repository.GetAll().AsQueryable();
}
// config.MapODataServiceRoute("odata", "odata", GetEdmModel());

Help Pages and Swashbuckle

The ASP.NET Web API Help Page NuGet package (Microsoft.AspNet.WebApi.HelpPage) generates a browsable /Help page from XML doc comments and reflection; Swashbuckle (the Web API 2-era Swagger/OpenAPI generator, distinct from ASP.NET Core’s built-in OpenAPI support) produces a swagger.json and a Swagger UI page instead, which is the more commonly chosen option in current MVC 5 codebases needing machine-readable API docs.

Hosting: IIS vs. OWIN self-host

Web API can run either IIS-hosted (the default — System.Web, same process as MVC, configured via GlobalConfiguration) or OWIN self-hosted (Microsoft.AspNet.WebApi.OwinSelfHost, no IIS/System.Web at all — a console app or Windows Service calling WebApp.Start<Startup>), a flexibility MVC’s own Controller pipeline never had. See Authentication, Identity, and OWIN for the shared OWIN pipeline both Web API self-hosting and Identity build on.

HttpClient as the client

var client = new HttpClient { BaseAddress = new Uri("https://api.example.com/") };
var response = await client.GetAsync("api/products/42");
response.EnsureSuccessStatusCode();
var product = await response.Content.ReadAsAsync<Product>();

Next: SignalR 2 covers the era’s real-time complement to Web API.