Overview [ Documentation]
There are four options for return types for web API controller action methods:
- Specific type
IActionResult
ActionResult<T>
HttpResults
Specific Type
Use when there are no known conditions against which to safeguard.
Considerations for Specific Types IEnumerable<T>
or IAsyncEnumerable<T>
ASP.NET Core buffers the result of actions that return IEnumerable<T>
before writing them to the response:
[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;
}
Consider using IAsyncEnumerable<T>
when asynchronous iteration is needed:
[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
Use when multiple ActionResult
return types are possible. For example, consider that when model validation fails, an HTTP 400 response is returned.
ActionResult
types represent various HTTP status codes. Any class deriving from ActionResult
(like BadRequestResult
, NotFoundResult
, OkObjectResult
, etc) is a valid return type.
In this action, [ProducesResponseType]
is used to indicate that it can return two ActionResult derivatives: NotFoundResult
or OkObjectResult
:
[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() // same as NotFoundResult(); HTTP 404
: Ok(product); // same as OkObjectResult(product); HTTP 200
}
ActionResult<T>
Like IActionResult
, ActionResult<T>
enables returning a type deriving from ActionResult
or returning a specific type. It offers these benefits vs IActionResult
:
- Omitting the
Type
property of[ProducesResponseType]
attribute (ie:[ProducesResponseType(200)]
instead of[ProducesResponseType(200, Type = typeof(Product))]
) - Convert both
T
andActionResult
toActionResult<T>
via implicit cast operators.
HttpResults
(IResult
)
HttpResults
can be used in both minimal APIs and controller-based APIs and are useful when sharing code between the two.
The Microsoft.AspNetCore.Http.HttpResults
namespace contains types that implement the IResult
interface. HttpResults
can be returned by calling IResult.ExecuteAsync
or helper methods on the static Results
class. It
Example:
[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);
}
Result<T>
Like HttpResults
, Result<T>
can be used when sharing code between minimal and controller-based APIs. Compared to HttpResults
, all [ProducesResponseType]
attributes can be excluded.
Additionally, when multiple IResult
return types are needed, prefer returning Result<TResult1, TResult2>
over IResult
because generic union types automatically retain endpoint metadata.
The static TypedResults
class returns concrete IResult
implementations.
Example:
[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
var product = _productContext.Products.Find(id);
return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}
Web API Conventions
Documentation: https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/conventions?view=aspnetcore-7.0
Web API conventions are a substitute for decorating actions with [ProducesResponseType]
. They define the most common return types and status codes returned from a specific type of action and identify actions that deviate from that standard.
Conventions work by applying attributes behind the scenes.
Default conventions are in Microsoft.AspNetCore.Mvc.DefaultApiConventions
namespace.
Applying Web API Conventions
Each action must be associated with exactly one convention. Conventions can be applied to actions, controllers (to all actions in the controller), or assemblies (to all controllers in the assembly).
Here, the Put
convention is applied to the Update
action:
// PUT api/contactsconvention/{guid}
[HttpPut("{id}")]
[ApiConventionMethod(typeof(DefaultApiConventions),
nameof(DefaultApiConventions.Put))]
public IActionResult Update(string id, Contact contact)
{
var contactToUpdate = _contacts.Get(id);
if (contactToUpdate == null)
return NotFound();
_contacts.Update(contact);
return NoContent();
}
This convention applies these attributes to the action:
[ProducesDefaultResponseType]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
Creating Custom Conventions
Custom conventions can be created.
Documentation: https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/conventions?view=aspnetcore-7.0#create-web-api-conventions