Create web APIs with ASP.NET Core

By Scott Addie and Tom Dykstra

ASP.NET Core supports creating RESTful services, also known as web APIs, using C#. To handle requests, a web API uses controllers. Controllers in a web API are classes that derive from ControllerBase. This article shows how to use controllers for handling API requests.

View or download sample code. (How to download).

ControllerBase class

A web API has one or more controller classes that derive from ControllerBase. For example, the web API project template creates a Values controller:

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase

Don't create a web API controller by deriving from the Controller base class. Controller derives from ControllerBase and adds support for views, so it's for handling web pages, not web API requests. There's an exception to this rule: if you plan to use the same controller for both views and APIs, derive it from Controller.

The ControllerBase class provides many properties and methods that are useful for handling HTTP requests. For example, ControllerBase.CreatedAtAction returns a 201 status code:

[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);
}

Here are some more examples of methods that ControllerBase provides.

Method Notes
BadRequest Returns 400 status code.
NotFound Returns 404 status code.
PhysicalFile Returns a file.
TryUpdateModelAsync Invokes model binding.
TryValidateModel Invokes model validation.

For a list of all available methods and properties, see ControllerBase.

Attributes

The Microsoft.AspNetCore.Mvc namespace provides attributes that can be used to configure the behavior of web API controllers and action methods. The following example uses attributes to specify the HTTP method accepted and the status codes returned:

[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);
}

Here are some more examples of attributes that are available.

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 method.
[Consumes] Specifies data types that an action accepts.
[Produces] Specifies data types that an action returns.

For a list that includes the available attributes, see the Microsoft.AspNetCore.Mvc namespace.

ApiController attribute

The [ApiController] attribute can be applied to a controller class to enable API-specific behaviors:

These features require a compatibility version of 2.1 or later.

ApiController on specific controllers

The [ApiController] attribute can be applied to specific controllers, as in the following example from the project template:

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase

ApiController on multiple controllers

One approach to using the attribute on more than one controller is to create a custom base controller class annotated with the [ApiController] attribute. Here's an example showing a custom base class and a controller that derives from it:

[ApiController]
public class MyControllerBase : ControllerBase
{
}
[Produces("application/json")]
[Route("api/[controller]")]
public class PetsController : MyControllerBase

ApiController on an assembly

If compatibility version is set to 2.2 or later, the [ApiController] attribute can be applied to an assembly. Annotation in this manner applies web API behavior to all controllers in the assembly. There's no way to opt out for individual controllers. Apply the assembly-level attribute to the Startup class as shown in the following example:

[assembly: ApiController]
namespace WebApiSample
{
    public class Startup
    {
        ...
    }
}

Attribute routing requirement

The ApiController attribute makes attribute routing a requirement. For example:

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase

Actions are inaccessible via conventional routes defined by UseMvc or UseMvcWithDefaultRoute in Startup.Configure.

Automatic HTTP 400 responses

The ApiController attribute makes model validation errors automatically trigger an HTTP 400 response. Consequently, the following code is unnecessary in an action method:

if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

Default BadRequest response

With a compatibility version of 2.2 or later, the default response type for HTTP 400 responses is ValidationProblemDetails. The ValidationProblemDetails type complies with the RFC 7807 specification.

To change the default response to SerializableError, set the SuppressUseValidationProblemDetailsForInvalidModelStateResponses property to true in Startup.ConfigureServices, as shown in the following example:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
        options.ClientErrorMapping[404].Link =
            "https://httpstatuses.com/404";
    });

Customize BadRequest response

To customize the response that results from a validation error, use InvalidModelStateResponseFactory. Add the following highlighted code after services.AddMvc().SetCompatibilityVersion:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var problemDetails = new ValidationProblemDetails(context.ModelState)
            {
                Type = "https://contoso.com/probs/modelvalidation",
                Title = "One or more model validation errors occurred.",
                Status = StatusCodes.Status400BadRequest,
                Detail = "See the errors property for details.",
                Instance = context.HttpContext.Request.Path
            };

            return new BadRequestObjectResult(problemDetails)
            {
                ContentTypes = { "application/problem+json" }
            };
        };
    });

Log automatic 400 responses

See How to log automatic 400 responses on model validation errors (aspnet/AspNetCore.Docs #12157).

Disable automatic 400

To disable the automatic 400 behavior, set the SuppressModelStateInvalidFilter property to true. Add the following highlighted code in Startup.ConfigureServices after services.AddMvc().SetCompatibilityVersion:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
        options.ClientErrorMapping[404].Link =
            "https://httpstatuses.com/404";
    });

Binding source parameter inference

A binding source attribute defines the location at which an action parameter's value is found. The following binding source attributes exist:

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

Warning

Don't use [FromRoute] when values might contain %2f (that is /). %2f won't be unescaped to /. Use [FromQuery] if the value might contain %2f.

Without the [ApiController] attribute or binding source attributes like [FromQuery], the ASP.NET Core runtime attempts to use the complex object model binder. The complex object model binder pulls data from value providers in a defined order.

In the following example, the [FromQuery] attribute indicates that the discontinuedOnly parameter value is provided in the request URL's query string:

[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;
}

The [ApiController] attribute applies inference rules for the default data sources of action parameters. These rules save you from having to identify binding sources manually by applying attributes to the action parameters. The binding source inference rules behave as follows:

  • [FromBody] is inferred for complex type parameters. 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] isn't inferred for simple types such as string or int. Therefore, the [FromBody] attribute should be used for simple types when that functionality is needed.

When an action has more than one parameter bound from the request body, an exception is thrown. For example, all of the following action method signatures cause an exception:

  • [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)
    

Note

In ASP.NET Core 2.1, collection type parameters such as lists and arrays are incorrectly inferred as [FromQuery]. The [FromBody] attribute should be used for these parameters if they are to be bound from the request body. This behavior is corrected in ASP.NET Core 2.2 or later, where collection type parameters are inferred to be bound from the body by default.

Disable inference rules

To disable binding source inference, set SuppressInferBindingSourcesForParameters to true. Add the following code in Startup.ConfigureServices after services.AddMvc().SetCompatibilityVersion:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
        options.ClientErrorMapping[404].Link =
            "https://httpstatuses.com/404";
    });

Multipart/form-data request inference

The [ApiController] attribute applies an inference rule when an action parameter is annotated with the [FromForm] attribute: the multipart/form-data request content type is inferred.

To disable the default behavior, set SuppressConsumesConstraintForFormFileParameters to true in Startup.ConfigureServices, as shown in the following example:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
        options.ClientErrorMapping[404].Link =
            "https://httpstatuses.com/404";
    });

Problem details for error status codes

When the compatibility version is 2.2 or later, MVC transforms an error result (a result with status code 400 or higher) to a result with ProblemDetails. The ProblemDetails type is based on the RFC 7807 specification for providing machine-readable error details in an HTTP response.

Consider the following code in a controller action:

if (pet == null)
{
    return NotFound();
}

The HTTP response for NotFound has a 404 status code with a ProblemDetails body. For example:

{
    type: "https://tools.ietf.org/html/rfc7231#section-6.5.4",
    title: "Not Found",
    status: 404,
    traceId: "0HLHLV31KRN83:00000001"
}

Customize ProblemDetails response

Use the ClientErrorMapping property to configure the contents of the ProblemDetails response. For example, the following code updates the type property for 404 responses:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
        options.ClientErrorMapping[404].Link =
            "https://httpstatuses.com/404";
    });

Disable ProblemDetails response

The automatic creation of ProblemDetails is disabled when the SuppressMapClientErrors property is set to true. Add the following code in Startup.ConfigureServices:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
        options.ClientErrorMapping[404].Link =
            "https://httpstatuses.com/404";
    });

Additional resources