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 This is not ASP.NET Core MVC. ASP.NET MVC 5 is the pre-Core, 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.