Controller action return types in ASP.NET Core web API
Note
This isn't the latest version of this article. For the current release, see the .NET 8 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see .NET and .NET Core Support Policy. For the current release, see the .NET 8 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 8 version of this article.
View or download sample code (how to download)
ASP.NET Core provides the following options for web API controller action return types:
This article explains when it's most appropriate to use each return type.
Specific type
The most basic action returns a primitive or complex data type, for example, string
or a custom object. Consider the following action, which returns a collection of custom Product
objects:
[HttpGet]
public Task<List<Product>> Get() =>
_productContext.Products.OrderBy(p => p.Name).ToListAsync();
Without known conditions to safeguard against, returning a specific type could suffice. The preceding action accepts no parameters, so parameter constraints validation isn't needed.
When multiple return types are possible, it's common to mix an ActionResult return type with the primitive or complex return type. Either IActionResult or ActionResult<T> are necessary to accommodate this type of action. Several samples of multiple return types are provided in this article.
Return IEnumerable<T> or IAsyncEnumerable<T>
See Return IEnumerable<T>
or IAsyncEnumerable<T>
for performance considerations.
ASP.NET Core buffers the result of actions that return IEnumerable<T> before writing them to the response. Consider declaring the action signature's return type as IAsyncEnumerable<T> to guarantee asynchronous iteration. Ultimately, the iteration mode is based on the underlying concrete type being returned and the selected formatter affects how the result is processed:
- When using
System.Text.Json
formatter, MVC relies on the support thatSystem.Text.Json
added to stream the result. - When using
Newtonsoft.Json
or withXML-based
formatters the result is buffered.
Consider the following action, which returns sale-priced product records as IEnumerable<Product>
:
[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
var products = _productContext.Products.OrderBy(p => p.Name).ToList();
foreach (var product in products)
{
if (product.IsOnSale)
{
yield return product;
}
}
}
The IAsyncEnumerable<Product>
equivalent of the preceding action is:
[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();
await foreach (var product in products)
{
if (product.IsOnSale)
{
yield return product;
}
}
}
IActionResult type
The IActionResult return type is appropriate when multiple ActionResult
return types are possible in an action. The ActionResult
types represent various HTTP status codes. Any non-abstract class deriving from ActionResult
qualifies as a valid return type. Some common return types in this category are BadRequestResult (400), NotFoundResult (404), and OkObjectResult (200). Alternatively, convenience methods in the ControllerBase class can be used to return ActionResult
types from an action. For example, return BadRequest();
is a shorthand form of return new BadRequestResult();
.
Because there are multiple return types and paths in this type of action, liberal use of the [ProducesResponseType]
attribute is necessary. This attribute produces more descriptive response details for web API help pages generated by tools like Swagger. [ProducesResponseType]
indicates the known types and HTTP status codes to be returned by the action.
Synchronous action
Consider the following synchronous action in which there are two possible return types:
[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? NotFound() : Ok(product);
}
In the preceding action:
- A 404 status code is returned when the product represented by
id
doesn't exist in the underlying data store. The NotFound convenience method is invoked as shorthand forreturn new NotFoundResult();
. - A 200 status code is returned with the
Product
object when the product does exist. The Ok convenience method is invoked as shorthand forreturn new OkObjectResult(product);
.
Asynchronous action
Consider the following asynchronous action in which there are two possible return types:
[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync_IActionResult(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}
_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();
return CreatedAtAction(nameof(CreateAsync_IActionResult), new { id = product.Id }, product);
}
In the preceding action:
A 400 status code is returned when the product description contains "XYZ Widget". The BadRequest convenience method is invoked as shorthand for
return new BadRequestResult();
.A 201 status code is generated by the CreatedAtAction convenience method when a product is created. The following code is an alternative to calling
CreatedAtAction
:return new CreatedAtActionResult(nameof(CreateAsync), "Products", new { id = product.Id }, product);
In the preceding code path, the
Product
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.
For example, the following model indicates that requests must include the Name
and Description
properties. Failure to provide Name
and Description
in the request causes model validation to fail.
public class Product
{
public int Id { get; set; }
[Required]
public string Name { get; set; } = string.Empty;
[Required]
public string Description { get; set; } = string.Empty;
public bool IsOnSale { get; set; }
}
If the [ApiController]
attribute is applied, model validation errors result in a 400 status code. For more information, see Automatic HTTP 400 responses.
ActionResult vs IActionResult
The following section compares ActionResult
to IActionResult
ActionResult<T> type
ASP.NET Core includes the ActionResult<T> return type for web API controller actions. It enables returning a type deriving from ActionResult or return a specific type. ActionResult<T>
offers the following benefits over the IActionResult type:
- The
[ProducesResponseType]
attribute'sType
property can be excluded. For example,[ProducesResponseType(200, Type = typeof(Product))]
is simplified to[ProducesResponseType(200)]
. The action's expected return type is inferred from theT
inActionResult<T>
. - Implicit cast operators support the conversion of both
T
andActionResult
toActionResult<T>
.T
converts to ObjectResult, which meansreturn new ObjectResult(T);
is simplified toreturn T;
.
C# doesn't support implicit cast operators on interfaces. Consequently, conversion of the interface to a concrete type is necessary to use ActionResult<T>
. For example, use of IEnumerable
in the following example doesn't work:
[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
_repository.GetProducts();
One option to fix the preceding code is to return _repository.GetProducts().ToList();
.
Most actions have a specific return type. Unexpected conditions can occur during action execution, in which case the specific type isn't returned. For example, an action's input parameter may fail model validation. In such a case, it's common to return the appropriate ActionResult
type instead of the specific type.
Synchronous action
Consider a synchronous action in which there are two possible return types:
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? NotFound() : product;
}
In the preceding action:
- A 404 status code is returned when the product doesn't exist in the database.
- A 200 status code is returned with the corresponding
Product
object when the product does exist.
Asynchronous action
Consider an asynchronous action in which there are two possible return types:
[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync_ActionResultOfT(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}
_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();
return CreatedAtAction(nameof(CreateAsync_ActionResultOfT), new { id = product.Id }, product);
}
In the preceding action:
- A 400 status code (BadRequest) is returned by the ASP.NET Core runtime when:
- The
[ApiController]
attribute has been applied and model validation fails. - The product description contains "XYZ Widget".
- The
- A 201 status code is generated by the CreatedAtAction method when a product is created. In this code path, the
Product
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.
HttpResults type
In addition to the MVC-specific built-in result types (IActionResult and ActionResult<T>), ASP.NET Core includes the HttpResults types that can be used in both Minimal APIs and Web API.
Different than the MVC-specific result types, the HttpResults
:
Are a results implementation that is processed by a call to IResult.ExecuteAsync.
Does not leverage the configured Formatters. Not leveraging the configured formatters means:
- Some features like
Content negotiation
aren't available. - The produced
Content-Type
is decided by theHttpResults
implementation.
- Some features like
The HttpResults
can be useful when sharing code between Minimal APIs and Web API.
IResult type
The Microsoft.AspNetCore.Http.HttpResults namespace contains classes that implement the IResult interface. The IResult
interface defines a contract that represents the result of an HTTP endpoint. The static Results class is used to create varying IResult
objects that represent different types of responses.
The Built-in results table shows the common result helpers.
Consider the following code:
[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? Results.NotFound() : Results.Ok(product);
}
In the preceding action:
- A 404 status code is returned when the product doesn't exist in the database.
- A 200 status code is returned with the corresponding
Product
object when the product does exist, generated by the Results.Ok<T>().
Consider the following code:
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType<Product>(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IResult> CreateAsync(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return Results.BadRequest();
}
_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();
var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
return Results.Created(location, product);
}
In the preceding action:
- A 400 status code is returned when:
- The
[ApiController]
attribute has been applied and model validation fails. - The product description contains "XYZ Widget".
- The
- A 201 status code is generated by the
Results.Create
method when a product is created. In this code path, theProduct
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.
Results<TResult1, TResultN> type
The static TypedResults class returns the concrete IResult
implementation that allows using IResult
as return type. The usage of the concrete IResult
implementation offers the following benefit over the IResult type:
- All the
[ProducesResponseType]
attributes can be excluded, since theHttpResult
implementation contributes automatically to the endpoint metadata.
When multiple IResult
return types are needed, returning Results<TResult1, TResultN>
is preferred over returning IResult
. Returning Results<TResult1, TResultN>
is preferred because generic union types automatically retain the endpoint metadata.
The Results<TResult1, TResultN>
union types implement implicit cast operators so that the compiler can automatically convert the types specified in the generic arguments to an instance of the union type. This has the added benefit of providing compile-time checking that a route handler actually only returns the results that it declares it does. Attempting to return a type that isn’t declared as one of the generic arguments to Results<>
results in a compilation error.
Consider the following code:
[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}
In the preceding action:
- A 404 status code is returned when the product doesn't exist in the database.
- A 200 status code is returned with the corresponding
Product
object when the product does exist, generated by the TypedResults.Ok<T>.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return TypedResults.BadRequest();
}
_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();
var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
return TypedResults.Created(location, product);
}
In the preceding action:
- A 400 status code is returned when:
- The
[ApiController]
attribute was applied and model validation fails. - The product description contains "XYZ Widget".
- The
- A 201 status code is generated by the
TypedResults.Created
method when a product is created. In this code path, theProduct
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.
Additional resources
View or download sample code (how to download)
ASP.NET Core provides the following options for web API controller action return types:
This article explains when it's most appropriate to use each return type.
Specific type
The most basic action returns a primitive or complex data type, for example, string
or a custom object. Consider the following action, which returns a collection of custom Product
objects:
[HttpGet]
public Task<List<Product>> Get() =>
_productContext.Products.OrderBy(p => p.Name).ToListAsync();
Without known conditions to safeguard against, returning a specific type could suffice. The preceding action accepts no parameters, so parameter constraints validation isn't needed.
When multiple return types are possible, it's common to mix an ActionResult return type with the primitive or complex return type. Either IActionResult or ActionResult<T> are necessary to accommodate this type of action. Several samples of multiple return types are provided in this article.
Return IEnumerable<T> or IAsyncEnumerable<T>
See Return IEnumerable<T>
or IAsyncEnumerable<T>
for performance considerations.
ASP.NET Core buffers the result of actions that return IEnumerable<T> before writing them to the response. Consider declaring the action signature's return type as IAsyncEnumerable<T> to guarantee asynchronous iteration. Ultimately, the iteration mode is based on the underlying concrete type being returned and the selected formatter affects how the result is processed:
- When using
System.Text.Json
formatter, MVC relies on the support thatSystem.Text.Json
added to stream the result. - When using
Newtonsoft.Json
or withXML-based
formatters the result is buffered.
Consider the following action, which returns sale-priced product records as IEnumerable<Product>
:
[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
var products = _productContext.Products.OrderBy(p => p.Name).ToList();
foreach (var product in products)
{
if (product.IsOnSale)
{
yield return product;
}
}
}
The IAsyncEnumerable<Product>
equivalent of the preceding action is:
[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();
await foreach (var product in products)
{
if (product.IsOnSale)
{
yield return product;
}
}
}
IActionResult type
The IActionResult return type is appropriate when multiple ActionResult
return types are possible in an action. The ActionResult
types represent various HTTP status codes. Any non-abstract class deriving from ActionResult
qualifies as a valid return type. Some common return types in this category are BadRequestResult (400), NotFoundResult (404), and OkObjectResult (200). Alternatively, convenience methods in the ControllerBase class can be used to return ActionResult
types from an action. For example, return BadRequest();
is a shorthand form of return new BadRequestResult();
.
Because there are multiple return types and paths in this type of action, liberal use of the [ProducesResponseType]
attribute is necessary. This attribute produces more descriptive response details for web API help pages generated by tools like Swagger. [ProducesResponseType]
indicates the known types and HTTP status codes to be returned by the action.
Synchronous action
Consider the following synchronous action in which there are two possible return types:
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? NotFound() : Ok(product);
}
In the preceding action:
- A 404 status code is returned when the product represented by
id
doesn't exist in the underlying data store. The NotFound convenience method is invoked as shorthand forreturn new NotFoundResult();
. - A 200 status code is returned with the
Product
object when the product does exist. The Ok convenience method is invoked as shorthand forreturn new OkObjectResult(product);
.
Asynchronous action
Consider the following asynchronous action in which there are two possible return types:
[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync_IActionResult(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}
_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();
return CreatedAtAction(nameof(GetById_IActionResult), new { id = product.Id }, product);
}
In the preceding action:
A 400 status code is returned when the product description contains "XYZ Widget". The BadRequest convenience method is invoked as shorthand for
return new BadRequestResult();
.A 201 status code is generated by the CreatedAtAction convenience method when a product is created. The following code is an alternative to calling
CreatedAtAction
:return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);
In the preceding code path, the
Product
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.
For example, the following model indicates that requests must include the Name
and Description
properties. Failure to provide Name
and Description
in the request causes model validation to fail.
public class Product
{
public int Id { get; set; }
[Required]
public string Name { get; set; } = string.Empty;
[Required]
public string Description { get; set; } = string.Empty;
public bool IsOnSale { get; set; }
}
If the [ApiController]
attribute is applied, model validation errors result in a 400 status code. For more information, see Automatic HTTP 400 responses.
ActionResult vs IActionResult
The following section compares ActionResult
to IActionResult
ActionResult<T> type
ASP.NET Core includes the ActionResult<T> return type for web API controller actions. It enables returning a type deriving from ActionResult or return a specific type. ActionResult<T>
offers the following benefits over the IActionResult type:
- The
[ProducesResponseType]
attribute'sType
property can be excluded. For example,[ProducesResponseType(200, Type = typeof(Product))]
is simplified to[ProducesResponseType(200)]
. The action's expected return type is inferred from theT
inActionResult<T>
. - Implicit cast operators support the conversion of both
T
andActionResult
toActionResult<T>
.T
converts to ObjectResult, which meansreturn new ObjectResult(T);
is simplified toreturn T;
.
C# doesn't support implicit cast operators on interfaces. Consequently, conversion of the interface to a concrete type is necessary to use ActionResult<T>
. For example, use of IEnumerable
in the following example doesn't work:
[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
_repository.GetProducts();
One option to fix the preceding code is to return _repository.GetProducts().ToList();
.
Most actions have a specific return type. Unexpected conditions can occur during action execution, in which case the specific type isn't returned. For example, an action's input parameter may fail model validation. In such a case, it's common to return the appropriate ActionResult
type instead of the specific type.
Synchronous action
Consider a synchronous action in which there are two possible return types:
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? NotFound() : product;
}
In the preceding action:
- A 404 status code is returned when the product doesn't exist in the database.
- A 200 status code is returned with the corresponding
Product
object when the product does exist.
Asynchronous action
Consider an asynchronous action in which there are two possible return types:
[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync_ActionResultOfT(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}
_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();
return CreatedAtAction(nameof(GetById_ActionResultOfT), new { id = product.Id }, product);
}
In the preceding action:
- A 400 status code (BadRequest) is returned by the ASP.NET Core runtime when:
- The
[ApiController]
attribute has been applied and model validation fails. - The product description contains "XYZ Widget".
- The
- A 201 status code is generated by the CreatedAtAction method when a product is created. In this code path, the
Product
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.
HttpResults type
In addition to the MVC-specific built-in result types (IActionResult and ActionResult<T>), ASP.NET Core includes the HttpResults types that can be used in both Minimal APIs and Web API.
Different than the MVC-specific result types, the HttpResults
:
Are a results implementation that is processed by a call to IResult.ExecuteAsync.
Does not leverage the configured Formatters. Not leveraging the configured formatters means:
- Some features like
Content negotiation
aren't available. - The produced
Content-Type
is decided by theHttpResults
implementation.
- Some features like
The HttpResults
can be useful when sharing code between Minimal APIs and Web API.
IResult type
The Microsoft.AspNetCore.Http.HttpResults namespace contains classes that implement the IResult interface. The IResult
interface defines a contract that represents the result of an HTTP endpoint. The static Results class is used to create varying IResult
objects that represent different types of responses.
The Built-in results table shows the common result helpers.
Consider the following code:
[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? Results.NotFound() : Results.Ok(product);
}
In the preceding action:
- A 404 status code is returned when the product doesn't exist in the database.
- A 200 status code is returned with the corresponding
Product
object when the product does exist, generated by the Results.Ok<T>().
Consider the following code:
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IResult> CreateAsync(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return Results.BadRequest();
}
_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();
var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
return Results.Created(location, product);
}
In the preceding action:
- A 400 status code is returned when:
- The
[ApiController]
attribute has been applied and model validation fails. - The product description contains "XYZ Widget".
- The
- A 201 status code is generated by the
Results.Create
method when a product is created. In this code path, theProduct
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.
Results<TResult1, TResultN> type
The static TypedResults class returns the concrete IResult
implementation that allows using IResult
as return type. The usage of the concrete IResult
implementation offers the following benefit over the IResult type:
- All the
[ProducesResponseType]
attributes can be excluded, since theHttpResult
implementation contributes automatically to the endpoint metadata.
When multiple IResult
return types are needed, returning Results<TResult1, TResultN>
is preferred over returning IResult
. Returning Results<TResult1, TResultN>
is preferred because generic union types automatically retain the endpoint metadata.
The Results<TResult1, TResultN>
union types implement implicit cast operators so that the compiler can automatically convert the types specified in the generic arguments to an instance of the union type. This has the added benefit of providing compile-time checking that a route handler actually only returns the results that it declares it does. Attempting to return a type that isn’t declared as one of the generic arguments to Results<>
results in a compilation error.
Consider the following code:
[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}
In the preceding action:
- A 404 status code is returned when the product doesn't exist in the database.
- A 200 status code is returned with the corresponding
Product
object when the product does exist, generated by the TypedResults.Ok<T>.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return TypedResults.BadRequest();
}
_productContext.Products.Add(product);
await _productContext.SaveChangesAsync();
var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
return TypedResults.Created(location, product);
}
In the preceding action:
- A 400 status code is returned when:
- The
[ApiController]
attribute was applied and model validation fails. - The product description contains "XYZ Widget".
- The
- A 201 status code is generated by the
TypedResults.Create
method when a product is created. In this code path, theProduct
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.
Additional resources
View or download sample code (how to download)
ASP.NET Core offers the following options for web API controller action return types:
This document explains when it's most appropriate to use each return type.
Specific type
The simplest action returns a primitive or complex data type (for example, string
or a custom object type). Consider the following action, which returns a collection of custom Product
objects:
[HttpGet]
public List<Product> Get() =>
_repository.GetProducts();
Without known conditions to safeguard against during action execution, returning a specific type could suffice. The preceding action accepts no parameters, so parameter constraints validation isn't needed.
When multiple return types are possible, it's common to mix an ActionResult return type with the primitive or complex return type. Either IActionResult or ActionResult<T> are necessary to accommodate this type of action. Several samples of multiple return types are provided in this document.
Return IEnumerable<T> or IAsyncEnumerable<T>
ASP.NET Core buffers the result of actions that return IEnumerable<T> before writing them to the response. Consider declaring the action signature's return type as IAsyncEnumerable<T> to guarantee asynchronous iteration. Ultimately, the iteration mode is based on the underlying concrete type being returned. MVC automatically buffers any concrete type that implements IAsyncEnumerable<T>
.
Consider the following action, which returns sale-priced product records as IEnumerable<Product>
:
[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
var products = _repository.GetProducts();
foreach (var product in products)
{
if (product.IsOnSale)
{
yield return product;
}
}
}
The IAsyncEnumerable<Product>
equivalent of the preceding action is:
[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
var products = _repository.GetProductsAsync();
await foreach (var product in products)
{
if (product.IsOnSale)
{
yield return product;
}
}
}
IActionResult type
The IActionResult return type is appropriate when multiple ActionResult
return types are possible in an action. The ActionResult
types represent various HTTP status codes. Any non-abstract class deriving from ActionResult
qualifies as a valid return type. Some common return types in this category are BadRequestResult (400), NotFoundResult (404), and OkObjectResult (200). Alternatively, convenience methods in the ControllerBase class can be used to return ActionResult
types from an action. For example, return BadRequest();
is a shorthand form of return new BadRequestResult();
.
Because there are multiple return types and paths in this type of action, liberal use of the [ProducesResponseType]
attribute is necessary. This attribute produces more descriptive response details for web API help pages generated by tools like Swagger. [ProducesResponseType]
indicates the known types and HTTP status codes to be returned by the action.
Synchronous action
Consider the following synchronous action in which there are two possible return types:
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
if (!_repository.TryGetProduct(id, out var product))
{
return NotFound();
}
return Ok(product);
}
In the preceding action:
- A 404 status code is returned when the product represented by
id
doesn't exist in the underlying data store. The NotFound convenience method is invoked as shorthand forreturn new NotFoundResult();
. - A 200 status code is returned with the
Product
object when the product does exist. The Ok convenience method is invoked as shorthand forreturn new OkObjectResult(product);
.
Asynchronous action
Consider the following asynchronous action in which there are two possible return types:
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}
await _repository.AddProductAsync(product);
return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}
In the preceding action:
- A 400 status code is returned when the product description contains "XYZ Widget". The BadRequest convenience method is invoked as shorthand for
return new BadRequestResult();
. - A 201 status code is generated by the CreatedAtAction convenience method when a product is created. An alternative to calling
CreatedAtAction
isreturn new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);
. In this code path, theProduct
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.
For example, the following model indicates that requests must include the Name
and Description
properties. Failure to provide Name
and Description
in the request causes model validation to fail.
public class Product
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
public bool IsOnSale { get; set; }
}
If the [ApiController]
attribute is applied, model validation errors result in a 400 status code. For more information, see Automatic HTTP 400 responses.
ActionResult vs IActionResult
The following section compares ActionResult
to IActionResult
ActionResult<T> type
ASP.NET Core includes the ActionResult<T> return type for web API controller actions. It enables you to return a type deriving from ActionResult or return a specific type. ActionResult<T>
offers the following benefits over the IActionResult type:
- The
[ProducesResponseType]
attribute'sType
property can be excluded. For example,[ProducesResponseType(200, Type = typeof(Product))]
is simplified to[ProducesResponseType(200)]
. The action's expected return type is instead inferred from theT
inActionResult<T>
. - Implicit cast operators support the conversion of both
T
andActionResult
toActionResult<T>
.T
converts to ObjectResult, which meansreturn new ObjectResult(T);
is simplified toreturn T;
.
C# doesn't support implicit cast operators on interfaces. Consequently, conversion of the interface to a concrete type is necessary to use ActionResult<T>
. For example, use of IEnumerable
in the following example doesn't work:
[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
_repository.GetProducts();
One option to fix the preceding code is to return _repository.GetProducts().ToList();
.
Most actions have a specific return type. Unexpected conditions can occur during action execution, in which case the specific type isn't returned. For example, an action's input parameter may fail model validation. In such a case, it's common to return the appropriate ActionResult
type instead of the specific type.
Synchronous action
Consider a synchronous action in which there are two possible return types:
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById(int id)
{
if (!_repository.TryGetProduct(id, out var product))
{
return NotFound();
}
return product;
}
In the preceding action:
- A 404 status code is returned when the product doesn't exist in the database.
- A 200 status code is returned with the corresponding
Product
object when the product does exist.
Asynchronous action
Consider an asynchronous action in which there are two possible return types:
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}
await _repository.AddProductAsync(product);
return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}
In the preceding action:
- A 400 status code (BadRequest) is returned by the ASP.NET Core runtime when:
- The
[ApiController]
attribute has been applied and model validation fails. - The product description contains "XYZ Widget".
- The
- A 201 status code is generated by the CreatedAtAction method when a product is created. In this code path, the
Product
object is provided in the response body. ALocation
response header containing the newly created product's URL is provided.