Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update live with current master #5776

Merged
merged 2 commits into from
Mar 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions aspnetcore/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@
### [Custom formatters](mvc/advanced/custom-formatters.md)
### [Format response data](mvc/models/formatting.md)

# [Web API](xref:web-api/index)
## [Controller action return types](xref:web-api/action-return-types)

# [Test and debug](xref:testing/index)
## [Unit testing](/dotnet/articles/core/testing/unit-testing-with-dotnet-test)
## [Integration tests](xref:testing/integration-testing)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ public void Configure(IApplicationBuilder app)
app.UseStaticFiles();

// Enable the Swagger UI middleware and the Swagger generator
app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly, new SwaggerUiSettings()
app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly, settings =>
{
DefaultPropertyNameHandling = PropertyNameHandling.CamelCase
settings.GeneratorSettings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase;
});

app.UseMvc();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="NSwag.AspNetCore" Version="11.9.0" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
<PackageReference Include="NSwag.AspNetCore" Version="11.16.0" />
</ItemGroup>

</Project>
99 changes: 99 additions & 0 deletions aspnetcore/web-api/action-return-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
title: Controller action return types in ASP.NET Core Web API
author: scottaddie
description: Learn about using the various controller action method return types in an ASP.NET Core Web API.
manager: wpickett
ms.author: scaddie
ms.custom: mvc
ms.date: 03/21/2018
ms.prod: aspnet-core
ms.technology: aspnet
ms.topic: article
uid: web-api/action-return-types
---
# Controller action return types in ASP.NET Core Web API

By [Scott Addie](https://github.com/scottaddie)

[View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/web-api/action-return-types/samples) ([how to download](xref:tutorials/index#how-to-download-a-sample))

ASP.NET Core offers the following three options for Web API controller action return types:

* [Specific type](#specific-type)
* [IActionResult](#iactionresult-type)
* [ActionResult\<T>](#actionresultt-type)

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:

[!code-csharp[](../web-api/action-return-types/samples/WebApiSample.Api.21/Controllers/ProductsController.cs?name=snippet_Get)]

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 known conditions need to be accounted for in an action, multiple return paths are introduced. In such a case, it's common to mix an [ActionResult](/dotnet/api/microsoft.aspnetcore.mvc.actionresult) return type with the primitive or complex return type. Either [IActionResult](#iactionresult-type) or [ActionResult\<T>](#actionresultt-type) are necessary to accommodate this type of action.

## IActionResult type

The [IActionResult](/dotnet/api/microsoft.aspnetcore.mvc.iactionresult) return type is appropriate when multiple [ActionResult](/dotnet/api/microsoft.aspnetcore.mvc.actionresult) return types are possible in an action. The `ActionResult` types represent various HTTP status codes. Some common return types falling into this category are [BadRequestResult](/dotnet/api/microsoft.aspnetcore.mvc.badrequestresult) (400), [NotFoundResult](/dotnet/api/microsoft.aspnetcore.mvc.notfoundresult) (404), and [OkObjectResult](/dotnet/api/microsoft.aspnetcore.mvc.okobjectresult) (200).

Because there are multiple return types and paths in the action, liberal use of the [[ProducesResponseType]](/dotnet/api/microsoft.aspnetcore.mvc.producesresponsetypeattribute.-ctor) attribute is necessary. This attribute produces more descriptive response details for API help pages generated by tools like [Swagger](/aspnet/core/tutorials/web-api-help-pages-using-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:

[!code-csharp[](../web-api/action-return-types/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs?name=snippet_GetById&highlight=8,11)]

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](/dotnet/api/system.web.http.apicontroller.notfound) helper method is invoked as a shortcut to `return new NotFoundResult();`. If the product does exist, a `Product` object representing the payload is returned with a 200 status code. The [Ok](/dotnet/api/system.web.http.apicontroller.ok) helper method is invoked as the shorthand form of `return new OkObjectResult(product);`.

### Asynchronous action

Consider the following asynchronous action in which there are two possible return types:

[!code-csharp[](../web-api/action-return-types/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs?name=snippet_CreateAsync&highlight=8,13)]

In the preceding action, a 400 status code is returned when model validation fails and the [BadRequest](/dotnet/api/system.web.http.apicontroller.badrequest) helper method is invoked. For example, the following model indicates that requests must provide the `Name` property and a value. Therefore, failure to provide a proper `Name` in the request causes model validation to fail.

[!code-csharp[](../web-api/action-return-types/samples/WebApiSample.DataAccess/Models/Product.cs?name=snippet_ProductClass&highlight=5-6)]

The preceding action's other known return code is a 201, which is generated by the [CreatedAtRoute](/dotnet/api/system.web.http.apicontroller.createdatroute) helper method. In this path, the `Product` object is returned.

## ActionResult\<T> type

ASP.NET Core 2.1 introduces the `ActionResult<T>` return type for Web API controller actions. It enables you to return a type deriving from [ActionResult](/dotnet/api/microsoft.aspnetcore.mvc.actionresult) or return a [specific type](#specific-type). `ActionResult<T>` offers the following benefits over the [IActionResult type](#iactionresult-type):

* The [[ProducesResponseType]](/dotnet/api/microsoft.aspnetcore.mvc.producesresponsetypeattribute) attribute's `Type` property can be excluded.
* [Implicit cast operators](/dotnet/csharp/language-reference/keywords/implicit) support the conversion of both `T` and `ActionResult` to `ActionResult<T>`. `T` converts to [ObjectResult](/dotnet/api/microsoft.aspnetcore.mvc.objectresult), which means `return new ObjectResult(T);` is simplified to `return T;`.

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:

[!code-csharp[](../web-api/action-return-types/samples/WebApiSample.Api.21/Controllers/ProductsController.cs?name=snippet_GetById&highlight=8,11)]

In the preceding code, a 404 status code is returned when the product doesn't exist in the database. If the product does exist, the corresponding `Product` object is returned. Before ASP.NET Core 2.1, the `return product;` line would have been `return Ok(product);`.

> [!TIP]
> As of ASP.NET Core 2.1, action parameter binding source inference is enabled when a controller class is decorated with the `[ApiController]` attribute. A parameter name matching a name in the route template is automatically bound using the request route data. Consequently, the preceding action's `id` parameter isn't explicitly annotated with the [[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute) attribute.

### Asynchronous action

Consider an asynchronous action in which there are two possible return types:

[!code-csharp[](../web-api/action-return-types/samples/WebApiSample.Api.21/Controllers/ProductsController.cs?name=snippet_CreateAsync&highlight=8,13)]

If model validation fails, the [BadRequest](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.badrequest#Microsoft_AspNetCore_Mvc_ControllerBase_BadRequest_Microsoft_AspNetCore_Mvc_ModelBinding_ModelStateDictionary_) method is invoked to return a 400 status code. The [ModelState](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.modelstate) property containing the specific validation errors is passed to it. If model validation succeeds, the product is created in the database. A 201 status code is returned.

> [!TIP]
> As of ASP.NET Core 2.1, action parameter binding source inference is enabled when a controller class is decorated with the `[ApiController]` attribute. Complex type parameters are automatically bound using the request body. Consequently, the preceding action's `product` parameter isn't explicitly annotated with the [[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute) attribute.

## Additional resources

* [Controller actions](xref:mvc/controllers/actions)
* [Model validation](xref:mvc/models/validation)
* [Web API help pages using Swagger](xref:tutorials/web-api-help-pages-using-swagger)
9 changes: 9 additions & 0 deletions aspnetcore/web-api/action-return-types/samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ASP.NET Core Web API sample

The following table provides a description of the various folders making up the sample solution:

| Folder | Purpose |
|--------------------------|---------------|
| *WebApiSample.Api.21* | An ASP.NET Core 2.1 Web API project. |
| *WebApiSample.Api.Pre21* | An ASP.NET Core 2.0 Web API project. |
| *WebApiSample.DataAccess*| A .NET Standard 2.0 data access layer project which is used by both Web API projects. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using WebApiSample.DataAccess.Models;
using WebApiSample.DataAccess.Repositories;

namespace WebApiSample.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly ProductsRepository _repository;

public ProductsController(ProductsRepository repository)
{
_repository = repository;
}

#region snippet_Get
[HttpGet]
public IEnumerable<Product> Get()
{
return _repository.GetProducts();
}
#endregion

#region snippet_GetById
[HttpGet("{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public ActionResult<Product> GetById(int id)
{
if (!_repository.TryGetProduct(id, out var product))
{
return NotFound();
}

return product;
}
#endregion

#region snippet_CreateAsync
[HttpPost]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

await _repository.AddProductAsync(product);

return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}
#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace WebApiSample.Api
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;
using System;
using System.IO;
using System.Reflection;
using WebApiSample.DataAccess;
using WebApiSample.DataAccess.Repositories;

namespace WebApiSample.Api
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ProductsRepository>();
services.AddDbContext<ProductContext>(opt =>
opt.UseInMemoryDatabase("ProductInventory"));

services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = "Products API",
Description = "A products Web API demonstrating action return types"
});

// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetEntryAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My ASP.NET Core 2.1 API v1");
c.RoutePrefix = string.Empty;
});
app.UseMvc();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<ProjectUISubcaption>&gt;= ASP.NET Core 2.1</ProjectUISubcaption>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Debug\$(TargetFramework)\$(MSBuildProjectName).xml</DocumentationFile>
<NoWarn>1701;1702;1705;1591</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-preview1-final" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="2.3.0" />
</ItemGroup>

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.1.0-preview1-final" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\WebApiSample.DataAccess\WebApiSample.DataAccess.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}
Loading