APIs - Controller-based APIs - Overview
In this article
ControllerBase class
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Pet> Create(Pet pet)
{
pet.Id = _petsInMemoryStore.Any() ?
_petsInMemoryStore.Max(p => p.Id) + 1 : 1;
_petsInMemoryStore.Add(pet);
return CreatedAtAction(nameof(GetById), new { id = pet.Id }, pet);
}
| Method | Notes |
|---|---|
| BadRequest | Returns 400 status code. |
| NotFound | Returns 404 status code. |
| PhysicalFile | Returns a file. |
| TryUpdateModelAsync | Invokes model binding. |
| TryValidateModel | Invokes model validation. |
Attributes
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Pet> Create(Pet pet)
{
pet.Id = _petsInMemoryStore.Any() ?
_petsInMemoryStore.Max(p => p.Id) + 1 : 1;
_petsInMemoryStore.Add(pet);
return CreatedAtAction(nameof(GetById), new { id = pet.Id }, pet);
}
| Attribute | Notes |
|---|---|
[Route] |
Specifies URL pattern for a controller or action. |
[Bind] |
Specifies prefix and properties to include for model binding. |
[HttpGet] |
Identifies an action that supports the HTTP GET action verb. |
[Consumes] |
Specifies data types that an action accepts. |
[Produces] |
Specifies data types that an action returns. |
ApiController attribute
-
Attribute routing requirement
-
Automatic HTTP 400 responses
-
Binding source parameter inference
-
Multipart/form-data request inference
-
Problem details for error status codes
Attribute on specific controllers
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
Attribute on multiple controllers
[ApiController]
public class MyControllerBase : ControllerBase
{
}
[Produces(MediaTypeNames.Application.Json)]
[Route("[controller]")]
public class PetsController : MyControllerBase
Attribute on an assembly
using Microsoft.AspNetCore.Mvc;
[assembly: ApiController]
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Attribute routing requirement
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
Automatic HTTP 400 responses
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Default BadRequest response
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|7fb5e16a-4c8f23bbfc974667.",
"errors": {
"": [
"A non-empty request body is required."
]
}
}
-
Provides a machine-readable format for specifying errors in web API responses.
-
Complies with the RFC 7807 specification.
Log automatic 400 responses
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behavior, capture the original delegate to call later.
var builtInFactory = options.InvalidModelStateResponseFactory;
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
// Perform logging here.
// ...
// Invoke the default behavior, which produces a ValidationProblemDetails
// response.
// To produce a custom response, return a different implementation of
// IActionResult instead.
return builtInFactory(context);
};
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Disable automatic 400 response
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Binding source parameter inference
| Attribute | Binding source |
|---|---|
[FromBody] |
Request body |
[FromForm] |
Form data in the request body |
[FromHeader] |
Request header |
[FromQuery] |
Request query ```string``` parameter |
[FromRoute] |
Route data from the current request |
[FromServices] |
The request service injected as an action parameter |
[AsParameters] |
Method parameters |
Warning Don't use
[FromRoute]when values might contain%2f(that is /).%2fwon't be unescaped to /. Use[FromQuery]if the value might contain%2f.
[HttpGet]
public ActionResult<List<Product>> Get(
[FromQuery] bool discontinuedOnly = false)
{
List<Product> products = null;
if (discontinuedOnly)
{
products = _productsInMemoryStore.Where(p => p.IsDiscontinued).ToList();
}
else
{
products = _productsInMemoryStore;
}
return products;
}
-
[FromServices]is inferred for complex type parameters registered in the DI Container. -
[FromBody]is inferred for complex type parameters not registered in the DI Container. An exception to the[FromBody]inference rule is any complex, built-in type with a special meaning, such as IFormCollection and CancellationToken. The binding source inference code ignores those special types. -
[FromForm]is inferred for action parameters of type IFormFile and IFormFileCollection. It's not inferred for any simple or user-defined types. -
[FromRoute]is inferred for any action parameter name matching a parameter in the route template. When more than one route matches an action parameter, any route value is considered[FromRoute]. -
[FromQuery]is inferred for any other action parameters.
FromBody inference notes
[FromBody]inferred on both because they're complex types.
[HttpPost]
public IActionResult Action1(Product product, Order order)
[FromBody]attribute on one, inferred on the other because it's a complex type.
[HttpPost]
public IActionResult Action2(Product product, [FromBody] Order order)
[FromBody]attribute on both.
[HttpPost]
public IActionResult Action3([FromBody] Product product, [FromBody] Order order)
FromServices inference notes
[Route("[controller]")]
[ApiController]
public class MyController : ControllerBase
{
public ActionResult GetWithAttribute([FromServices] IDateTime dateTime)
=> Ok(dateTime.Now);
[Route("noAttribute")]
public ActionResult Get(IDateTime dateTime) => Ok(dateTime.Now);
}
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<IDateTime, SystemDateTime>();
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.DisableImplicitFromServicesParameters = true;
});
var app = builder.Build();
app.MapControllers();
app.Run();
-
A previously specified
BindingInfo.BindingSourceis never overwritten. -
A complex type parameter, registered in the DI container, is assigned
BindingSource.Services. -
A complex type parameter, not registered in the DI container, is assigned
BindingSource.Body. -
A parameter with a name that appears as a route value in any route template is assigned
BindingSource.Path. -
All other parameters are
BindingSource.Query.
Disable inference rules
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
options.DisableImplicitFromServicesParameters = true;
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Multipart/form-data request inference
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Problem details for error status codes
if (pet == null)
{
return NotFound();
}
{
type: "https://tools.ietf.org/html/rfc7231#section-6.5.4",
title: "Not Found",
status: 404,
traceId: "0HLHLV31KRN83:00000001"
}
Disable ProblemDetails response
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Define supported request content types with the [Consumes] attribute
[HttpPost]
[Consumes("application/xml")]
public IActionResult CreateProduct(Product product)
[ApiController]
[Route("api/[controller]")]
public class ConsumesController : ControllerBase
{
[HttpPost]
[Consumes("application/json")]
public IActionResult PostJson(IEnumerable<int> values) =>
Ok(new { Consumes = "application/json", Values = values });
[HttpPost]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult PostForm([FromForm] IEnumerable<int> values) =>
Ok(new { Consumes = "application/x-www-form-urlencoded", Values = values });
}
Additional resources
-
View or download sample code. (How to download).
-
Controlleraction return types in ASP.NET Core web API -
Handle errors in ASP.NET Core controller-based web APIs
-
Custom formatters in ASP.NET Core Web API
-
Format response data in ASP.NET Core Web API
-
ASP.NET Core web API documentation with Swagger / OpenAPI
-
Routing to controller actions in ASP.NET Core
-
Use port tunneling Visual Studio to debug web APIs
-
Create a web API with ASP.NET Core