From 39c56df3968064e28b09a9d335cf6fd4aa9082d5 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 17 Apr 2018 13:27:10 -0500 Subject: [PATCH 01/33] Add Web API controller definition doc --- aspnetcore/web-api/define-controller.md | 117 ++++++++++++++++++ .../Controllers/ProductsController.cs | 58 +++++++++ .../Controllers/ValuesController.cs | 42 +++++++ .../samples/WebApiSample.Api/Program.cs | 24 ++++ .../samples/WebApiSample.Api/Startup.cs | 56 +++++++++ .../WebApiSample.Api/WebApiSample.Api.csproj | 20 +++ .../appsettings.Development.json | 9 ++ .../samples/WebApiSample.Api/appsettings.json | 8 ++ .../WebApiSample.DataAccess/Models/Product.cs | 14 +++ .../WebApiSample.DataAccess/ProductContext.cs | 15 +++ .../Repositories/ProductsRepository.cs | 55 ++++++++ .../WebApiSample.DataAccess.csproj | 16 +++ 12 files changed, 434 insertions(+) create mode 100644 aspnetcore/web-api/define-controller.md create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ValuesController.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Program.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/appsettings.Development.json create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/appsettings.json create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Product.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/ProductContext.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/WebApiSample.DataAccess.csproj diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md new file mode 100644 index 000000000000..95177cc1c7c7 --- /dev/null +++ b/aspnetcore/web-api/define-controller.md @@ -0,0 +1,117 @@ +--- +title: Define a controller in ASP.NET Core Web API +author: scottaddie +description: Learn about the options for defining a controller class in an ASP.NET Core Web API and when it's most appropriate to use each. +manager: wpickett +ms.author: scaddie +ms.custom: mvc +ms.date: 04/17/2018 +ms.prod: aspnet-core +ms.technology: aspnet +ms.topic: article +uid: web-api/define-controller +--- +# Define a controller in ASP.NET Core Web API + +By [Scott Addie](https://github.com/scottaddie) + +::: moniker range=">= aspnetcore-2.1" +[View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/web-api/define-controller/samples) ([how to download](xref:tutorials/index#how-to-download-a-sample)) +::: moniker-end + +ASP.NET Core offers the following options for creating a Web API controller: + +::: moniker range="<= aspnetcore-2.0" +* [Derive class from Controller](#derive-class-from-controller) +* [Derive class from ControllerBase](#derive-class-from-controllerbase) +::: moniker-end +::: moniker range=">= aspnetcore-2.1" +* [Derive class from Controller](#derive-class-from-controller) +* [Derive class from ControllerBase](#derive-class-from-controllerbase) +* [Decorate class with ApiControllerAttribute](#decorate-class-with-apicontrollerattribute) +::: moniker-end + +This document explains when it's most appropriate to use each option. + +## Derive class from Controller + +Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) class when your controller needs to support MVC views in addition to Web API actions. For example: + +```csharp +[Route("api/[controller]")] +public class ProductsController : Controller +{ +} +``` + +## Derive class from ControllerBase + +Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class when your controller doesn't need to support MVC views. For example: + +```csharp +[Route("api/[controller]")] +public class ProductsController : ControllerBase +{ +} +``` + +::: moniker range=">= aspnetcore-2.1" +## Decorate class with ApiControllerAttribute + +ASP.NET Core 2.1 introduces the `[ApiController]` attribute to denote a Web API controller class. For example: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=2)] + +The following sections describe convenience features added by the attribute. + +### Automatic HTTP 400 responses + +Validation errors automatically trigger an HTTP 400 response. Code such as the following becomes unnecessary: + +```csharp +if (!ModelState.IsValid) +{ + return BadRequest(ModelState); +} +``` + +This default behavior is disabled with the following code in *Startup.ConfigureServices*: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=5)] + +### Binding source parameter inference + +Inference rules are applied for the default data sources of action parameters. The binding source attributes behave as follows: + +* [[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute) is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. If you decide to explicitly apply the `[FromBody]` attribute, multiple occurrences of it in the same action results in an exception. +* [[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute) is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. +* [[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute) is inferred for anything else. + +For example, the `[FromBody]` attribute is implied for the `Product` parameter: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_CreateAsync)] + +Inheriting from `ControllerBase` is necessary in the preceding example, since access to the [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction#Microsoft_AspNetCore_Mvc_ControllerBase_CreatedAtAction_System_String_System_Object_System_Object_) method is required. + +The default inference rules are disabled with the following code in *Startup.ConfigureServices*: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=4)] + +### Multipart/form-data request inference + +When an action parameter is annotated with the [[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute) attribute, the `multipart/form-data` request format is inferred. + +The default behavior is disabled with the following code in *Startup.ConfigureServices*: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=3)] + +### Attribute routing requirement + +Attribute routing becomes a requirement. For example: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=1)] + +Actions are inaccessible via convention-based routes defined via [UseMvc](/dotnet/api/microsoft.aspnetcore.builder.mvcapplicationbuilderextensions.usemvc#Microsoft_AspNetCore_Builder_MvcApplicationBuilderExtensions_UseMvc_Microsoft_AspNetCore_Builder_IApplicationBuilder_System_Action_Microsoft_AspNetCore_Routing_IRouteBuilder__) in *Startup.Configure*. +::: moniker-end + +## Additional resources diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs new file mode 100644 index 000000000000..a8ca2ce47cde --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using WebApiSample.DataAccess.Models; +using WebApiSample.DataAccess.Repositories; + +namespace WebApiSample.Api.Controllers +{ + #region snippet_ControllerSignature + [Route("api/[controller]")] + [ApiController] + public class ProductsController : ControllerBase + #endregion + { + private readonly ProductsRepository _repository; + + public ProductsController(ProductsRepository repository) + { + _repository = repository; + } + + #region snippet_Get + [HttpGet] + public IEnumerable Get() + { + return _repository.GetProducts(); + } + #endregion + + #region snippet_GetById + [HttpGet("{id}")] + [ProducesResponseType(200)] + [ProducesResponseType(404)] + public ActionResult 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> CreateAsync(Product product) + { + await _repository.AddProductAsync(product); + + return CreatedAtAction(nameof(GetById), + new { id = product.Id }, product); + } + #endregion + } +} \ No newline at end of file diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ValuesController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ValuesController.cs new file mode 100644 index 000000000000..bbe772b0342a --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ValuesController.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; + +namespace WebApiSample.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + // GET api/values + [HttpGet] + public ActionResult> Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Program.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Program.cs new file mode 100644 index 000000000000..0ee369f03883 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +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(); + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs new file mode 100644 index 000000000000..3366f9d1dcdd --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +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(); + services.AddDbContext(opt => + opt.UseInMemoryDatabase("ProductInventory")); + + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + #region snippet_ConfigureApiBehaviorOptions + services.Configure(options => + { + options.SuppressConsumesConstraintForFormFileParameters = true; + options.SuppressInferBindingSourcesForParameters = true; + options.SuppressModelStateInvalidFilter = true; + }); + #endregion + } + + // 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.UseMvc(); + } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj new file mode 100644 index 000000000000..05284ce2b465 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/appsettings.Development.json b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/appsettings.Development.json new file mode 100644 index 000000000000..e203e9407e74 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/appsettings.json b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/appsettings.json new file mode 100644 index 000000000000..def9159a7d94 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Product.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Product.cs new file mode 100644 index 000000000000..4ec278b11cb2 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Product.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace WebApiSample.DataAccess.Models +{ + public class Product + { + public int Id { get; set; } + + [Required] + public string Name { get; set; } + + public string Description { get; set; } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/ProductContext.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/ProductContext.cs new file mode 100644 index 000000000000..68bcd969c366 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/ProductContext.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; +using WebApiSample.DataAccess.Models; + +namespace WebApiSample.DataAccess +{ + public class ProductContext : DbContext + { + public ProductContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Products { get; set; } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs new file mode 100644 index 000000000000..d4912d7fcaea --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WebApiSample.DataAccess.Models; + +namespace WebApiSample.DataAccess.Repositories +{ + public class ProductsRepository + { + private readonly ProductContext _context; + + public ProductsRepository(ProductContext context) + { + _context = context; + + if (_context.Products.Count() == 0) + { + _context.Products.AddRange( + new Product + { + Name = "Learning ASP.NET Core", + Description = "A best-selling book covering the fundamentals of ASP.NET Core" + }, + new Product + { + Name = "Learning EF Core", + Description = "A best-selling book covering the fundamentals of Entity Framework Core" + }); + _context.SaveChanges(); + } + } + + public IEnumerable GetProducts() + { + return _context.Products.ToList(); + } + + public bool TryGetProduct(int id, out Product product) + { + product = _context.Products.Find(id); + + return (product != null); + } + + public async Task AddProductAsync(Product product) + { + int rowsAffected = 0; + + _context.Products.Add(product); + rowsAffected = await _context.SaveChangesAsync(); + + return rowsAffected; + } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/WebApiSample.DataAccess.csproj b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/WebApiSample.DataAccess.csproj new file mode 100644 index 000000000000..3ac312753432 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/WebApiSample.DataAccess.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + + + + + ..\..\..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.entityframeworkcore\2.1.0-preview2-final\lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll + + + ..\..\..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0-preview2-26406-04\ref\netcoreapp2.1\System.ComponentModel.Annotations.dll + + + + From f6cb5146ceb48ec2c7e78f5af4fdd4d8587a0d19 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 17 Apr 2018 13:40:24 -0500 Subject: [PATCH 02/33] Remove wwwroot folder reference from csproj file --- .../samples/WebApiSample.Api/WebApiSample.Api.csproj | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj index 05284ce2b465..a07f3a4b0596 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj @@ -1,13 +1,9 @@ - + netcoreapp2.1 - - - - From 28ba86e750560cc9c62d8bcbaaf1e449f77e79d5 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 17 Apr 2018 13:56:20 -0500 Subject: [PATCH 03/33] Verbiage tweaks --- aspnetcore/web-api/define-controller.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index 95177cc1c7c7..594f322b4706 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -66,7 +66,7 @@ The following sections describe convenience features added by the attribute. ### Automatic HTTP 400 responses -Validation errors automatically trigger an HTTP 400 response. Code such as the following becomes unnecessary: +Validation errors automatically trigger an HTTP 400 response. The following code becomes unnecessary: ```csharp if (!ModelState.IsValid) @@ -91,7 +91,7 @@ For example, the `[FromBody]` attribute is implied for the `Product` parameter: [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_CreateAsync)] -Inheriting from `ControllerBase` is necessary in the preceding example, since access to the [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction#Microsoft_AspNetCore_Mvc_ControllerBase_CreatedAtAction_System_String_System_Object_System_Object_) method is required. +The preceding action requires access to the [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction#Microsoft_AspNetCore_Mvc_ControllerBase_CreatedAtAction_System_String_System_Object_System_Object_) method. For this reason only, the controller to which the action belongs inherits from `ControllerBase`. The default inference rules are disabled with the following code in *Startup.ConfigureServices*: From e5ab1c9f023b8b01d5c88f33c7558f0724cd33a8 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 17 Apr 2018 14:09:54 -0500 Subject: [PATCH 04/33] Add info about FromForm inference --- aspnetcore/web-api/define-controller.md | 1 + 1 file changed, 1 insertion(+) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index 594f322b4706..bc0cde830d42 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -84,6 +84,7 @@ This default behavior is disabled with the following code in *Startup.ConfigureS Inference rules are applied for the default data sources of action parameters. The binding source attributes behave as follows: * [[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute) is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. If you decide to explicitly apply the `[FromBody]` attribute, multiple occurrences of it in the same action results in an exception. +* [[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute) is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.iformfilecollection). * [[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute) is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. * [[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute) is inferred for anything else. From 2183485d7700947cb28b7809da6f29bd03fed1cf Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 17 Apr 2018 14:36:37 -0500 Subject: [PATCH 05/33] Bold the attribute names --- aspnetcore/web-api/define-controller.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index bc0cde830d42..12a56c52b82a 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -83,10 +83,10 @@ This default behavior is disabled with the following code in *Startup.ConfigureS Inference rules are applied for the default data sources of action parameters. The binding source attributes behave as follows: -* [[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute) is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. If you decide to explicitly apply the `[FromBody]` attribute, multiple occurrences of it in the same action results in an exception. -* [[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute) is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.iformfilecollection). -* [[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute) is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. -* [[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute) is inferred for anything else. +* **[[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute)** is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. If you decide to explicitly apply the `[FromBody]` attribute, multiple occurrences of it in the same action results in an exception. +* **[[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute)** is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.iformfilecollection). +* **[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. +* **[[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute)** is inferred for anything else. For example, the `[FromBody]` attribute is implied for the `Product` parameter: @@ -100,7 +100,7 @@ The default inference rules are disabled with the following code in *Startup.Con ### Multipart/form-data request inference -When an action parameter is annotated with the [[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute) attribute, the `multipart/form-data` request format is inferred. +When an action parameter is annotated with the [[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute) attribute, the `multipart/form-data` request constraint is inferred. The default behavior is disabled with the following code in *Startup.ConfigureServices*: From c7f4d30663d917d8df681d171c5a8e56cbb155c0 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 17 Apr 2018 15:04:33 -0500 Subject: [PATCH 06/33] Add TOC entries --- aspnetcore/toc.md | 1 + aspnetcore/web-api/index.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/aspnetcore/toc.md b/aspnetcore/toc.md index e14bc2767fad..fe1168638473 100644 --- a/aspnetcore/toc.md +++ b/aspnetcore/toc.md @@ -181,6 +181,7 @@ # [Web API](xref:web-api/index) ## [Controller action return types](xref:web-api/action-return-types) +## [Define a controller](xref:web-api/define-controller) ## [Advanced](xref:web-api/advanced/index) ### [Custom formatters](xref:web-api/advanced/custom-formatters) ### [Format response data](xref:web-api/advanced/formatting) diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index edd96198cedb..3e4a39dddfba 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -4,7 +4,7 @@ author: scottaddie description: A collection of resources related to using ASP.NET Core Web API manager: wpickett ms.author: scaddie -ms.date: 04/02/2018 +ms.date: 04/17/2018 ms.prod: asp.net-core ms.technology: aspnet ms.topic: article @@ -16,6 +16,7 @@ uid: web-api/index * [Create a Web API with ASP.NET Core and Visual Studio for Mac](xref:tutorials/first-web-api-mac) * [Create a Web API with ASP.NET Core and Visual Studio for Windows](xref:tutorials/first-web-api) * [Controller action return types in ASP.NET Core Web API](xref:web-api/action-return-types) +* [Define a controller in ASP.NET Core Web API](xref:web-api/define-controller) * [Help pages using Swagger](xref:tutorials/web-api-help-pages-using-swagger) * [Create backend services for native mobile apps](xref:mobile/native-mobile-backend) * [Format response data](xref:web-api/advanced/formatting) From 403f2f16ff571fe75e433dcb20083155a7751af5 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 17 Apr 2018 15:23:49 -0500 Subject: [PATCH 07/33] Change link ordering in TOC --- aspnetcore/toc.md | 2 +- aspnetcore/web-api/define-controller.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/aspnetcore/toc.md b/aspnetcore/toc.md index fe1168638473..ed990dbb408a 100644 --- a/aspnetcore/toc.md +++ b/aspnetcore/toc.md @@ -180,8 +180,8 @@ ### [Custom model binding](mvc/advanced/custom-model-binding.md) # [Web API](xref:web-api/index) -## [Controller action return types](xref:web-api/action-return-types) ## [Define a controller](xref:web-api/define-controller) +## [Controller action return types](xref:web-api/action-return-types) ## [Advanced](xref:web-api/advanced/index) ### [Custom formatters](xref:web-api/advanced/custom-formatters) ### [Format response data](xref:web-api/advanced/formatting) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index 12a56c52b82a..0cca21700220 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -116,3 +116,5 @@ Actions are inaccessible via convention-based routes defined via [UseMvc](/dotne ::: moniker-end ## Additional resources + +[Controller action return types in ASP.NET Core Web API](xref:web-api/action-return-types) \ No newline at end of file From 18faa472d0a18ef00e508149adb634b4588e6cc2 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 10:05:26 -0500 Subject: [PATCH 08/33] Add note about IntelliSense --- aspnetcore/web-api/define-controller.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index 0cca21700220..102249f9cfae 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -5,7 +5,7 @@ description: Learn about the options for defining a controller class in an ASP.N manager: wpickett ms.author: scaddie ms.custom: mvc -ms.date: 04/17/2018 +ms.date: 04/18/2018 ms.prod: aspnet-core ms.technology: aspnet ms.topic: article @@ -55,6 +55,8 @@ public class ProductsController : ControllerBase } ``` +An added benefit of using `ControllerBase` over `Controller` is that only Web API-specific members are displayed in IntelliSense. + ::: moniker range=">= aspnetcore-2.1" ## Decorate class with ApiControllerAttribute From 484b1ab57ce76b1b0795b08e3a6dfa518b56ab0d Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 16:41:00 -0500 Subject: [PATCH 09/33] Major sample app changes --- aspnetcore/web-api/define-controller.md | 48 ++++++------- .../Controllers/OrdersController.cs | 50 ++++++++++++++ .../Controllers/PetsController.cs | 58 ++++++++++++++++ .../samples/WebApiSample.Api.Pre21/Program.cs | 25 +++++++ .../samples/WebApiSample.Api.Pre21/Startup.cs | 63 +++++++++++++++++ .../Views/Orders/Index.cshtml | 7 ++ .../Views/Shared/_Layout.cshtml | 13 ++++ .../Views/_ViewStart.cshtml | 3 + .../WebApiSample.Api.Pre21.csproj | 33 +++++++++ .../appsettings.Development.json | 10 +++ .../WebApiSample.Api.Pre21/appsettings.json | 15 ++++ .../Controllers/ProductsController.cs | 2 +- .../Controllers/ValuesController.cs | 42 ----------- .../samples/WebApiSample.Api/Program.cs | 9 +-- .../samples/WebApiSample.Api/Startup.cs | 18 ++++- .../WebApiSample.Api/WebApiSample.Api.csproj | 6 ++ .../WebApiSample.DataAccess/Models/Order.cs | 13 ++++ .../WebApiSample.DataAccess/Models/Pet.cs | 23 +++++++ .../WebApiSample.DataAccess/OrderContext.cs | 15 ++++ .../WebApiSample.DataAccess/PetContext.cs | 15 ++++ .../Repositories/OrdersRepository.cs | 56 +++++++++++++++ .../Repositories/PetsRepository.cs | 69 +++++++++++++++++++ .../Repositories/ProductsRepository.cs | 2 +- .../WebApiSample.DataAccess.csproj | 9 +-- .../define-controller/samples/global.json | 5 ++ 25 files changed, 521 insertions(+), 88 deletions(-) create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Program.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Orders/Index.cshtml create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Shared/_Layout.cshtml create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/_ViewStart.cshtml create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/appsettings.Development.json create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/appsettings.json delete mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ValuesController.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Order.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Pet.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/OrderContext.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/PetContext.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/OrdersRepository.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/PetsRepository.cs create mode 100644 aspnetcore/web-api/define-controller/samples/global.json diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index 102249f9cfae..a557fd13f61c 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -15,9 +15,7 @@ uid: web-api/define-controller By [Scott Addie](https://github.com/scottaddie) -::: moniker range=">= aspnetcore-2.1" [View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/web-api/define-controller/samples) ([how to download](xref:tutorials/index#how-to-download-a-sample)) -::: moniker-end ASP.NET Core offers the following options for creating a Web API controller: @@ -35,27 +33,19 @@ This document explains when it's most appropriate to use each option. ## Derive class from Controller -Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) class when your controller needs to support MVC views in addition to Web API actions. For example: +Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) class when your controller needs to support MVC views in addition to Web API actions. -```csharp -[Route("api/[controller]")] -public class ProductsController : Controller -{ -} -``` +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] + +In the preceding controller, the `Index` action returns the associated MVC view at *Views/Orders/Index.cshtml*. The `GetById` and `CreateAsync` actions respond to HTTP GET and POST requests, respectively. ## Derive class from ControllerBase -Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class when your controller doesn't need to support MVC views. For example: +Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class when your controller doesn't need to support MVC views. For example, the following controller only supports Web API actions: -```csharp -[Route("api/[controller]")] -public class ProductsController : ControllerBase -{ -} -``` +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] -An added benefit of using `ControllerBase` over `Controller` is that only Web API-specific members are displayed in IntelliSense. +A benefit of deriving from `ControllerBase` instead of `Controller` is that IntelliSense displays only Web API-specific members. ::: moniker range=">= aspnetcore-2.1" ## Decorate class with ApiControllerAttribute @@ -64,18 +54,13 @@ ASP.NET Core 2.1 introduces the `[ApiController]` attribute to denote a Web API [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=2)] -The following sections describe convenience features added by the attribute. +This attribute is commonly coupled with either `ControllerBase` or `Controller`. The following sections describe convenience features added by the attribute. ### Automatic HTTP 400 responses -Validation errors automatically trigger an HTTP 400 response. The following code becomes unnecessary: +Validation errors automatically trigger an HTTP 400 response. The following code becomes unnecessary in your actions: -```csharp -if (!ModelState.IsValid) -{ - return BadRequest(ModelState); -} -``` +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?range=38-41)] This default behavior is disabled with the following code in *Startup.ConfigureServices*: @@ -92,7 +77,7 @@ Inference rules are applied for the default data sources of action parameters. T For example, the `[FromBody]` attribute is implied for the `Product` parameter: -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_CreateAsync)] +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_CreateAsync&highlight=4)] The preceding action requires access to the [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction#Microsoft_AspNetCore_Mvc_ControllerBase_CreatedAtAction_System_String_System_Object_System_Object_) method. For this reason only, the controller to which the action belongs inherits from `ControllerBase`. @@ -114,9 +99,16 @@ Attribute routing becomes a requirement. For example: [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=1)] -Actions are inaccessible via convention-based routes defined via [UseMvc](/dotnet/api/microsoft.aspnetcore.builder.mvcapplicationbuilderextensions.usemvc#Microsoft_AspNetCore_Builder_MvcApplicationBuilderExtensions_UseMvc_Microsoft_AspNetCore_Builder_IApplicationBuilder_System_Action_Microsoft_AspNetCore_Routing_IRouteBuilder__) in *Startup.Configure*. +Actions are inaccessible via [conventional routes](xref:mvc/controllers/routing#conventional-routing) defined in [UseMvc](/dotnet/api/microsoft.aspnetcore.builder.mvcapplicationbuilderextensions.usemvc#Microsoft_AspNetCore_Builder_MvcApplicationBuilderExtensions_UseMvc_Microsoft_AspNetCore_Builder_IApplicationBuilder_System_Action_Microsoft_AspNetCore_Routing_IRouteBuilder__) or by [UseMvcWithDefaultRoute](/dotnet/api/microsoft.aspnetcore.builder.mvcapplicationbuilderextensions.usemvcwithdefaultroute#Microsoft_AspNetCore_Builder_MvcApplicationBuilderExtensions_UseMvcWithDefaultRoute_Microsoft_AspNetCore_Builder_IApplicationBuilder_) in *Startup.Configure*. ::: moniker-end ## Additional resources -[Controller action return types in ASP.NET Core Web API](xref:web-api/action-return-types) \ No newline at end of file +::: moniker range=">= aspnetcore-2.1" +* [Controller action return types in ASP.NET Core Web API](xref:web-api/action-return-types) +* [Routing to controller actions in ASP.NET Core](xref:mvc/controllers/routing) +::: moniker-end + +::: moniker range="<= aspnetcore-2.0" +* [Routing to controller actions in ASP.NET Core](xref:mvc/controllers/routing) +::: moniker-end \ No newline at end of file diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs new file mode 100644 index 000000000000..4ad9b31bcee2 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using WebApiSample.DataAccess.Models; +using WebApiSample.DataAccess.Repositories; + +namespace WebApiSample.Api.Controllers +{ + #region snippet_OrdersController + public class OrdersController : Controller + { + private readonly OrdersRepository _repository; + + public OrdersController(OrdersRepository repository) + { + _repository = repository; + } + + public IActionResult Index() => View(); + + [HttpGet("{id}")] + [ProducesResponseType(typeof(Order), 200)] + [ProducesResponseType(404)] + public IActionResult GetById(int id) + { + if (!_repository.TryGetOrder(id, out var order)) + { + return NotFound(); + } + + return Ok(order); + } + + [HttpPost] + [ProducesResponseType(typeof(Order), 201)] + [ProducesResponseType(400)] + public async Task CreateAsync([FromBody] Order order) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + await _repository.AddOrderAsync(order); + + return CreatedAtAction(nameof(GetById), + new { id = order.Id }, order); + } + } + #endregion +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs new file mode 100644 index 000000000000..fdaa1147c99d --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using WebApiSample.DataAccess.Models; +using WebApiSample.DataAccess.Repositories; + +namespace WebApiSample.Api.Controllers +{ + #region snippet_PetsController + [Produces("application/json")] + [Route("api/[controller]")] + public class PetsController : ControllerBase + { + private readonly PetsRepository _repository; + + public PetsController(PetsRepository repository) + { + _repository = repository; + } + + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), 200)] + public IActionResult Get() + { + return Ok(_repository.GetPets()); + } + + [HttpGet("{id}")] + [ProducesResponseType(typeof(Pet), 200)] + [ProducesResponseType(404)] + public IActionResult GetById(int id) + { + if (!_repository.TryGetPet(id, out var pet)) + { + return NotFound(); + } + + return Ok(pet); + } + + [HttpPost] + [ProducesResponseType(typeof(Pet), 201)] + [ProducesResponseType(400)] + public async Task CreateAsync([FromBody] Pet pet) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + await _repository.AddPetAsync(pet); + + return CreatedAtAction(nameof(GetById), + new { id = pet.Id }, pet); + } + } + #endregion +} \ No newline at end of file diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Program.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Program.cs new file mode 100644 index 000000000000..d648f541213e --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace WebApiSample.Api.Pre21 +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs new file mode 100644 index 000000000000..92eefe207176 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Swashbuckle.AspNetCore.Swagger; +using WebApiSample.DataAccess; +using WebApiSample.DataAccess.Repositories; + +namespace WebApiSample.Api.Pre21 +{ + 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(); + services.AddScoped(); + + services.AddDbContext(opt => + opt.UseInMemoryDatabase("PetInventory")); + services.AddDbContext(opt => + opt.UseInMemoryDatabase("Orders")); + + services.AddMvc(); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new Info + { + Title = "ASP.NET Core Pre-2.1 Web API", + Version = "v1" + }); + + c.DescribeAllEnumsAsStrings(); + }); + } + + // 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(); + } + + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1"); + c.RoutePrefix = string.Empty; + }); + app.UseMvcWithDefaultRoute(); + } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Orders/Index.cshtml b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Orders/Index.cshtml new file mode 100644 index 000000000000..15ac2f2d06d0 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Orders/Index.cshtml @@ -0,0 +1,7 @@ + +@{ + ViewData["Title"] = "Index"; +} + +

Index

+ diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Shared/_Layout.cshtml b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Shared/_Layout.cshtml new file mode 100644 index 000000000000..3c25d93a57e0 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Shared/_Layout.cshtml @@ -0,0 +1,13 @@ + + + + + + @ViewBag.Title + + +
+ @RenderBody() +
+ + diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/_ViewStart.cshtml b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/_ViewStart.cshtml new file mode 100644 index 000000000000..a5f10045db97 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj new file mode 100644 index 000000000000..9dba099bd554 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj @@ -0,0 +1,33 @@ + + + + netcoreapp2.0 + <= ASP.NET Core 2.0 + + + + + + + + + + + + + + + + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + + + diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/appsettings.Development.json b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/appsettings.Development.json new file mode 100644 index 000000000000..fa8ce71a97a3 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/appsettings.json b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/appsettings.json new file mode 100644 index 000000000000..26bb0ac7ac67 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs index a8ca2ce47cde..ef9464deb1d2 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs @@ -21,7 +21,7 @@ public ProductsController(ProductsRepository repository) #region snippet_Get [HttpGet] - public IEnumerable Get() + public ActionResult> Get() { return _repository.GetProducts(); } diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ValuesController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ValuesController.cs deleted file mode 100644 index bbe772b0342a..000000000000 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ValuesController.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; - -namespace WebApiSample.Api.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class ValuesController : ControllerBase - { - // GET api/values - [HttpGet] - public ActionResult> Get() - { - return new string[] { "value1", "value2" }; - } - - // GET api/values/5 - [HttpGet("{id}")] - public ActionResult Get(int id) - { - return "value"; - } - - // POST api/values - [HttpPost] - public void Post([FromBody] string value) - { - } - - // PUT api/values/5 - [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) - { - } - - // DELETE api/values/5 - [HttpDelete("{id}")] - public void Delete(int id) - { - } - } -} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Program.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Program.cs index 0ee369f03883..bfc4b2da7a12 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Program.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Program.cs @@ -1,12 +1,5 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; namespace WebApiSample.Api { diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs index 3366f9d1dcdd..8c246799aec7 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Swashbuckle.AspNetCore.Swagger; using WebApiSample.DataAccess; using WebApiSample.DataAccess.Repositories; @@ -24,9 +25,18 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddDbContext(opt => opt.UseInMemoryDatabase("ProductInventory")); - + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new Info + { + Title = "ASP.NET Core 2.1+ Web API", + Version = "v1" + }); + }); + #region snippet_ConfigureApiBehaviorOptions services.Configure(options => { @@ -50,6 +60,12 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) } app.UseHttpsRedirection(); + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1"); + c.RoutePrefix = string.Empty; + }); app.UseMvc(); } } diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj index a07f3a4b0596..c7d994116a6b 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/WebApiSample.Api.csproj @@ -2,11 +2,17 @@ netcoreapp2.1 + >= ASP.NET Core 2.1 + + + + + diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Order.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Order.cs new file mode 100644 index 000000000000..b1c5c25d8217 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Order.cs @@ -0,0 +1,13 @@ +using System; + +namespace WebApiSample.DataAccess.Models +{ + public class Order + { + public int Id { get; set; } + + public string Description { get; set; } + + public DateTime OrderDate { get; set; } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Pet.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Pet.cs new file mode 100644 index 000000000000..56f8acc60ff7 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Pet.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; + +namespace WebApiSample.DataAccess.Models +{ + public class Pet + { + public int Id { get; set; } + + [Required] + public string Breed { get; set; } + + public string Name { get; set; } + + [Required] + public PetType PetType { get; set; } + } + + public enum PetType + { + Dog = 0, + Cat = 1 + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/OrderContext.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/OrderContext.cs new file mode 100644 index 000000000000..3ccb44716098 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/OrderContext.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; +using WebApiSample.DataAccess.Models; + +namespace WebApiSample.DataAccess +{ + public class OrderContext : DbContext + { + public OrderContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Orders { get; set; } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/PetContext.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/PetContext.cs new file mode 100644 index 000000000000..1451f963bdc0 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/PetContext.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; +using WebApiSample.DataAccess.Models; + +namespace WebApiSample.DataAccess +{ + public class PetContext : DbContext + { + public PetContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Pets { get; set; } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/OrdersRepository.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/OrdersRepository.cs new file mode 100644 index 000000000000..751bea92084b --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/OrdersRepository.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WebApiSample.DataAccess.Models; + +namespace WebApiSample.DataAccess.Repositories +{ + public class OrdersRepository + { + private readonly OrderContext _context; + + public OrdersRepository(OrderContext context) + { + _context = context; + + if (_context.Orders.Count() == 0) + { + _context.Orders.AddRange( + new Order + { + OrderDate = DateTime.Now, + Description = "Textbook order for John Doe" + }, + new Order + { + OrderDate = DateTime.Now, + Description = "Docs laptop sticker order for Jane Doe" + }); + _context.SaveChanges(); + } + } + + public IEnumerable GetOrders() + { + return _context.Orders.ToList(); + } + + public bool TryGetOrder(int id, out Order order) + { + order = _context.Orders.Find(id); + + return (order != null); + } + + public async Task AddOrderAsync(Order order) + { + int rowsAffected = 0; + + _context.Orders.Add(order); + rowsAffected = await _context.SaveChangesAsync(); + + return rowsAffected; + } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/PetsRepository.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/PetsRepository.cs new file mode 100644 index 000000000000..6216d0dc4449 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/PetsRepository.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WebApiSample.DataAccess.Models; + +namespace WebApiSample.DataAccess.Repositories +{ + public class PetsRepository + { + private readonly PetContext _context; + + public PetsRepository(PetContext context) + { + _context = context; + + if (_context.Pets.Count() == 0) + { + _context.Pets.AddRange( + new Pet + { + Name = "Opie", + Breed = "Shih Tzu", + PetType = PetType.Dog + }, + new Pet + { + Name = "Reggie", + Breed = "Beagle", + PetType = PetType.Dog + }, + new Pet + { + Name = "Diesel", + Breed = "Bombay", + PetType = PetType.Cat + }, + new Pet + { + Name = "Lucy", + Breed = "Maine Coon", + PetType = PetType.Cat + }); + _context.SaveChanges(); + } + } + + public IEnumerable GetPets() + { + return _context.Pets.ToList(); + } + + public bool TryGetPet(int id, out Pet pet) + { + pet = _context.Pets.Find(id); + + return (pet != null); + } + + public async Task AddPetAsync(Pet pet) + { + int rowsAffected = 0; + + _context.Pets.Add(pet); + rowsAffected = await _context.SaveChangesAsync(); + + return rowsAffected; + } + } +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs index d4912d7fcaea..b2dc4ef7a45d 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs @@ -30,7 +30,7 @@ public ProductsRepository(ProductContext context) } } - public IEnumerable GetProducts() + public List GetProducts() { return _context.Products.ToList(); } diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/WebApiSample.DataAccess.csproj b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/WebApiSample.DataAccess.csproj index 3ac312753432..9f32df0d0643 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/WebApiSample.DataAccess.csproj +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/WebApiSample.DataAccess.csproj @@ -1,16 +1,11 @@ - + netstandard2.0 - - ..\..\..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.entityframeworkcore\2.1.0-preview2-final\lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll - - - ..\..\..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0-preview2-26406-04\ref\netcoreapp2.1\System.ComponentModel.Annotations.dll - + diff --git a/aspnetcore/web-api/define-controller/samples/global.json b/aspnetcore/web-api/define-controller/samples/global.json new file mode 100644 index 000000000000..80c47990fb8d --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "2.1.300-preview2-008530" + } +} \ No newline at end of file From 2a5bd8a78f3be36b18521cb03f3afbceeae617da Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 16:51:25 -0500 Subject: [PATCH 10/33] Fix code snippet line numbers --- aspnetcore/web-api/define-controller.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index a557fd13f61c..1176b48a120f 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -48,7 +48,7 @@ Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controlle A benefit of deriving from `ControllerBase` instead of `Controller` is that IntelliSense displays only Web API-specific members. ::: moniker range=">= aspnetcore-2.1" -## Decorate class with ApiControllerAttribute +## Annotate class with ApiControllerAttribute ASP.NET Core 2.1 introduces the `[ApiController]` attribute to denote a Web API controller class. For example: @@ -60,7 +60,7 @@ This attribute is commonly coupled with either `ControllerBase` or `Controller`. Validation errors automatically trigger an HTTP 400 response. The following code becomes unnecessary in your actions: -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?range=38-41)] +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?range=46-49)] This default behavior is disabled with the following code in *Startup.ConfigureServices*: From 10c9c3fc7112efcdc7c274155f2920c2e1df139c Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 17:08:47 -0500 Subject: [PATCH 11/33] Minor verbiage tweaks --- aspnetcore/web-api/define-controller.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index 1176b48a120f..d9cd879d8293 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -26,7 +26,7 @@ ASP.NET Core offers the following options for creating a Web API controller: ::: moniker range=">= aspnetcore-2.1" * [Derive class from Controller](#derive-class-from-controller) * [Derive class from ControllerBase](#derive-class-from-controllerbase) -* [Decorate class with ApiControllerAttribute](#decorate-class-with-apicontrollerattribute) +* [Annotate class with ApiControllerAttribute](#annotate-class-with-apicontrollerattribute) ::: moniker-end This document explains when it's most appropriate to use each option. @@ -37,7 +37,7 @@ Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) c [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] -In the preceding controller, the `Index` action returns the associated MVC view at *Views/Orders/Index.cshtml*. The `GetById` and `CreateAsync` actions respond to HTTP GET and POST requests, respectively. +In the preceding controller, the `Index` action returns a [ViewResult](/dotnet/api/microsoft.aspnetcore.mvc.viewresult) representing the associated MVC view at *Views/Orders/Index.cshtml*. The `GetById` and `CreateAsync` actions respond to HTTP GET and POST requests, respectively. ## Derive class from ControllerBase From e9a1da9f85366db85ffe02a4a5a8991b464a854a Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 17:22:40 -0500 Subject: [PATCH 12/33] Add a README file --- .../web-api/define-controller/samples/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 aspnetcore/web-api/define-controller/samples/README.md diff --git a/aspnetcore/web-api/define-controller/samples/README.md b/aspnetcore/web-api/define-controller/samples/README.md new file mode 100644 index 000000000000..49e313059033 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/README.md @@ -0,0 +1,13 @@ +# ASP.NET Core Web API Controller Sample + +This sample app consists of the following projects: + +- **WebApiSample.Api**: An ASP.NET Core 2.1 project targeting .NET Core 2.1. +- **WebApiSample.Api.Pre21**: An ASP.NET Core 2.0 project targeting .NET Core 2.0. +- **WebApiSample.DataAccess**: A .NET Standard 2.0 class library serving as a data access tier for the 2 Web API projects. + +This sample illustrates variations of Web API controller creation: + +- [Derive class from Controller](https://docs.microsoft.com/en-us/aspnet/core/web-api/define-controller#derive-class-from-controller) +- [Derive class from ControllerBase](https://docs.microsoft.com/en-us/aspnet/core/web-api/define-controller#derive-class-from-controllerbase) +- [Annotate class with ApiControllerAttribute](https://docs.microsoft.com/en-us/aspnet/core/web-api/define-controller#annotate-class-with-apicontrollerattribute) \ No newline at end of file From d279a55be2cb476280bdc8f8697a7a430f932238 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 17:54:20 -0500 Subject: [PATCH 13/33] Add TestController.cs --- aspnetcore/web-api/define-controller.md | 5 +++- .../Controllers/TestController.cs | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index d9cd879d8293..18116d8f9fcf 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -70,7 +70,10 @@ This default behavior is disabled with the following code in *Startup.ConfigureS Inference rules are applied for the default data sources of action parameters. The binding source attributes behave as follows: -* **[[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute)** is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. If you decide to explicitly apply the `[FromBody]` attribute, multiple occurrences of it in the same action results in an exception. +* **[[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute)** is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. When an action has more than one parameter explicitly specified (via `[FromBody]`) or inferred as bound from the request body, an exception is thrown. For example, the following action signatures result in an exception: + + [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs?name=snippet_ActionsCausingExceptions)] + * **[[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute)** is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.iformfilecollection). * **[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. * **[[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute)** is inferred for anything else. diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs new file mode 100644 index 000000000000..a628e879662d --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using WebApiSample.DataAccess.Models; + +namespace WebApiSample.Api.Controllers +{ + [Produces("application/json")] + [Route("api/[controller]")] + [ApiController] + public class TestController : Controller + { + #region snippet_ActionsCausingExceptions + // Don't do this. All of the following actions result in an exception. + [HttpPost] + public IActionResult Action1(Product product, + Order order) => null; + + [HttpPost] + public IActionResult Action2(Product product, + [FromBody] Order order) => null; + + [HttpPost] + public IActionResult Action3([FromBody] Product product, + [FromBody] Order order) => null; + #endregion + } +} \ No newline at end of file From 4bffbfdf6bd2c2320037b8376b3d95a81d52f0a1 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 18:17:59 -0500 Subject: [PATCH 14/33] Verbiage changes --- aspnetcore/web-api/define-controller.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index 18116d8f9fcf..3b4eb1061a90 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -33,7 +33,7 @@ This document explains when it's most appropriate to use each option. ## Derive class from Controller -Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) class when your controller needs to support MVC views in addition to Web API actions. +Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) class when your controller needs to support presentation layer concerns in addition to Web API actions. Examples of presentation layer concerns include returning MVC views or [invoking view components](xref:mvc/views/view-components#invoking-a-view-component-directly-from-a-controller). [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] @@ -41,7 +41,7 @@ In the preceding controller, the `Index` action returns a [ViewResult](/dotnet/a ## Derive class from ControllerBase -Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class when your controller doesn't need to support MVC views. For example, the following controller only supports Web API actions: +Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class when your controller doesn't need to return MVC views or [invoke view components]((xref:mvc/views/view-components#invoking-a-view-component-directly-from-a-controller)). For example, the following controller only supports Web API actions: [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] @@ -54,7 +54,9 @@ ASP.NET Core 2.1 introduces the `[ApiController]` attribute to denote a Web API [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=2)] -This attribute is commonly coupled with either `ControllerBase` or `Controller`. The following sections describe convenience features added by the attribute. +This attribute is commonly coupled with either `ControllerBase` or `Controller` to gain access to useful methods and properties. `ControllerBase` provides access to methods such as [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction) and [File](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.file). `Controller` provides access to methods such as [Json](/dotnet/api/microsoft.aspnetcore.mvc.controller.json) and [View](/dotnet/api/microsoft.aspnetcore.mvc.controller.view). + +The following sections describe convenience features added by the attribute. ### Automatic HTTP 400 responses @@ -78,12 +80,6 @@ Inference rules are applied for the default data sources of action parameters. T * **[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. * **[[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute)** is inferred for anything else. -For example, the `[FromBody]` attribute is implied for the `Product` parameter: - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_CreateAsync&highlight=4)] - -The preceding action requires access to the [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction#Microsoft_AspNetCore_Mvc_ControllerBase_CreatedAtAction_System_String_System_Object_System_Object_) method. For this reason only, the controller to which the action belongs inherits from `ControllerBase`. - The default inference rules are disabled with the following code in *Startup.ConfigureServices*: [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=4)] From 57639669531927f5570c2c57a9c906e99a376818 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 18:33:45 -0500 Subject: [PATCH 15/33] Shorten wording --- aspnetcore/web-api/define-controller.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index 3b4eb1061a90..ed212b4d187a 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -72,9 +72,9 @@ This default behavior is disabled with the following code in *Startup.ConfigureS Inference rules are applied for the default data sources of action parameters. The binding source attributes behave as follows: -* **[[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute)** is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. When an action has more than one parameter explicitly specified (via `[FromBody]`) or inferred as bound from the request body, an exception is thrown. For example, the following action signatures result in an exception: +* **[[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute)** is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. When an action has more than one parameter explicitly specified (via `[FromBody]`) or inferred as bound from the request body, an exception is thrown. For example, the following action signatures cause an exception: - [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs?name=snippet_ActionsCausingExceptions)] +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs?name=snippet_ActionsCausingExceptions)] * **[[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute)** is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.iformfilecollection). * **[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. From 6dd85f1f9587ad7a5aa85205022f04ab65ffd4ba Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 18:52:11 -0500 Subject: [PATCH 16/33] Change wording --- aspnetcore/web-api/define-controller.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index ed212b4d187a..4b6b3ef9279c 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -86,7 +86,7 @@ The default inference rules are disabled with the following code in *Startup.Con ### Multipart/form-data request inference -When an action parameter is annotated with the [[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute) attribute, the `multipart/form-data` request constraint is inferred. +When an action parameter is annotated with the [[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute) attribute, the `multipart/form-data` request content type is inferred. The default behavior is disabled with the following code in *Startup.ConfigureServices*: From 0e2bba95f2a1c632b65701681d58a414b6bcd295 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Wed, 18 Apr 2018 21:38:25 -0500 Subject: [PATCH 17/33] Minor edit --- aspnetcore/web-api/define-controller.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md index 4b6b3ef9279c..dc9fc0e739e5 100644 --- a/aspnetcore/web-api/define-controller.md +++ b/aspnetcore/web-api/define-controller.md @@ -78,7 +78,7 @@ Inference rules are applied for the default data sources of action parameters. T * **[[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute)** is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.iformfilecollection). * **[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. -* **[[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute)** is inferred for anything else. +* **[[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute)** is inferred for any other action parameters. The default inference rules are disabled with the following code in *Startup.ConfigureServices*: From ecdf8caffee890da35dfb16b4e5041f6f02c270f Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 12:09:43 -0500 Subject: [PATCH 18/33] React to feedback --- aspnetcore/toc.md | 1 - aspnetcore/web-api/define-controller.md | 113 ----------------- .../Controllers/ProductsController.cs | 72 +++++++++++ .../samples/WebApiSample.Api.Pre21/Startup.cs | 3 + .../WebApiSample.Api.Pre21.csproj | 1 + .../WebApiSample.DataAccess/Models/Product.cs | 2 + .../Repositories/ProductsRepository.cs | 10 ++ aspnetcore/web-api/index.md | 117 ++++++++++++++++-- 8 files changed, 192 insertions(+), 127 deletions(-) delete mode 100644 aspnetcore/web-api/define-controller.md create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs diff --git a/aspnetcore/toc.md b/aspnetcore/toc.md index ed990dbb408a..e14bc2767fad 100644 --- a/aspnetcore/toc.md +++ b/aspnetcore/toc.md @@ -180,7 +180,6 @@ ### [Custom model binding](mvc/advanced/custom-model-binding.md) # [Web API](xref:web-api/index) -## [Define a controller](xref:web-api/define-controller) ## [Controller action return types](xref:web-api/action-return-types) ## [Advanced](xref:web-api/advanced/index) ### [Custom formatters](xref:web-api/advanced/custom-formatters) diff --git a/aspnetcore/web-api/define-controller.md b/aspnetcore/web-api/define-controller.md deleted file mode 100644 index dc9fc0e739e5..000000000000 --- a/aspnetcore/web-api/define-controller.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -title: Define a controller in ASP.NET Core Web API -author: scottaddie -description: Learn about the options for defining a controller class in an ASP.NET Core Web API and when it's most appropriate to use each. -manager: wpickett -ms.author: scaddie -ms.custom: mvc -ms.date: 04/18/2018 -ms.prod: aspnet-core -ms.technology: aspnet -ms.topic: article -uid: web-api/define-controller ---- -# Define a controller 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/define-controller/samples) ([how to download](xref:tutorials/index#how-to-download-a-sample)) - -ASP.NET Core offers the following options for creating a Web API controller: - -::: moniker range="<= aspnetcore-2.0" -* [Derive class from Controller](#derive-class-from-controller) -* [Derive class from ControllerBase](#derive-class-from-controllerbase) -::: moniker-end -::: moniker range=">= aspnetcore-2.1" -* [Derive class from Controller](#derive-class-from-controller) -* [Derive class from ControllerBase](#derive-class-from-controllerbase) -* [Annotate class with ApiControllerAttribute](#annotate-class-with-apicontrollerattribute) -::: moniker-end - -This document explains when it's most appropriate to use each option. - -## Derive class from Controller - -Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) class when your controller needs to support presentation layer concerns in addition to Web API actions. Examples of presentation layer concerns include returning MVC views or [invoking view components](xref:mvc/views/view-components#invoking-a-view-component-directly-from-a-controller). - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] - -In the preceding controller, the `Index` action returns a [ViewResult](/dotnet/api/microsoft.aspnetcore.mvc.viewresult) representing the associated MVC view at *Views/Orders/Index.cshtml*. The `GetById` and `CreateAsync` actions respond to HTTP GET and POST requests, respectively. - -## Derive class from ControllerBase - -Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class when your controller doesn't need to return MVC views or [invoke view components]((xref:mvc/views/view-components#invoking-a-view-component-directly-from-a-controller)). For example, the following controller only supports Web API actions: - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] - -A benefit of deriving from `ControllerBase` instead of `Controller` is that IntelliSense displays only Web API-specific members. - -::: moniker range=">= aspnetcore-2.1" -## Annotate class with ApiControllerAttribute - -ASP.NET Core 2.1 introduces the `[ApiController]` attribute to denote a Web API controller class. For example: - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=2)] - -This attribute is commonly coupled with either `ControllerBase` or `Controller` to gain access to useful methods and properties. `ControllerBase` provides access to methods such as [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction) and [File](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.file). `Controller` provides access to methods such as [Json](/dotnet/api/microsoft.aspnetcore.mvc.controller.json) and [View](/dotnet/api/microsoft.aspnetcore.mvc.controller.view). - -The following sections describe convenience features added by the attribute. - -### Automatic HTTP 400 responses - -Validation errors automatically trigger an HTTP 400 response. The following code becomes unnecessary in your actions: - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?range=46-49)] - -This default behavior is disabled with the following code in *Startup.ConfigureServices*: - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=5)] - -### Binding source parameter inference - -Inference rules are applied for the default data sources of action parameters. The binding source attributes behave as follows: - -* **[[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute)** is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. When an action has more than one parameter explicitly specified (via `[FromBody]`) or inferred as bound from the request body, an exception is thrown. For example, the following action signatures cause an exception: - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs?name=snippet_ActionsCausingExceptions)] - -* **[[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute)** is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.iformfilecollection). -* **[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. -* **[[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute)** is inferred for any other action parameters. - -The default inference rules are disabled with the following code in *Startup.ConfigureServices*: - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=4)] - -### Multipart/form-data request inference - -When an action parameter is annotated with the [[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute) attribute, the `multipart/form-data` request content type is inferred. - -The default behavior is disabled with the following code in *Startup.ConfigureServices*: - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=3)] - -### Attribute routing requirement - -Attribute routing becomes a requirement. For example: - -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=1)] - -Actions are inaccessible via [conventional routes](xref:mvc/controllers/routing#conventional-routing) defined in [UseMvc](/dotnet/api/microsoft.aspnetcore.builder.mvcapplicationbuilderextensions.usemvc#Microsoft_AspNetCore_Builder_MvcApplicationBuilderExtensions_UseMvc_Microsoft_AspNetCore_Builder_IApplicationBuilder_System_Action_Microsoft_AspNetCore_Routing_IRouteBuilder__) or by [UseMvcWithDefaultRoute](/dotnet/api/microsoft.aspnetcore.builder.mvcapplicationbuilderextensions.usemvcwithdefaultroute#Microsoft_AspNetCore_Builder_MvcApplicationBuilderExtensions_UseMvcWithDefaultRoute_Microsoft_AspNetCore_Builder_IApplicationBuilder_) in *Startup.Configure*. -::: moniker-end - -## Additional resources - -::: moniker range=">= aspnetcore-2.1" -* [Controller action return types in ASP.NET Core Web API](xref:web-api/action-return-types) -* [Routing to controller actions in ASP.NET Core](xref:mvc/controllers/routing) -::: moniker-end - -::: moniker range="<= aspnetcore-2.0" -* [Routing to controller actions in ASP.NET Core](xref:mvc/controllers/routing) -::: moniker-end \ No newline at end of file diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs new file mode 100644 index 000000000000..786bd86cbf40 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using WebApiSample.DataAccess.Models; +using WebApiSample.DataAccess.Repositories; + +namespace WebApiSample.Api.Pre21.Controllers +{ + #region snippet_ControllerSignature + [Route("api/[controller]")] + public class ProductsController : ControllerBase + #endregion + { + private readonly ProductsRepository _repository; + + public ProductsController(ProductsRepository repository) + { + _repository = repository; + } + + #region snippet_GetById + [HttpGet("{id}")] + [ProducesResponseType(typeof(Product), 200)] + [ProducesResponseType(404)] + public IActionResult GetById(int id) + { + if (!_repository.TryGetProduct(id, out var product)) + { + return NotFound(); + } + + return Ok(product); + } + #endregion + + #region snippet_BindingSourceAttributes + [HttpGet] + [ProducesResponseType(typeof(List), 200)] + public IActionResult Get([FromQuery] bool discontinuedOnly = false) + { + List products = null; + + if (discontinuedOnly) + { + products = _repository.GetDiscontinuedProducts(); + } + else + { + products = _repository.GetProducts(); + } + + return Ok(products); + } + + [HttpPost] + [ProducesResponseType(typeof(Product), 201)] + [ProducesResponseType(400)] + public async Task CreateAsync([FromBody] Product product) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + await _repository.AddProductAsync(product); + + return CreatedAtAction(nameof(GetById), + new { id = product.Id }, product); + } + #endregion + } +} \ No newline at end of file diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs index 92eefe207176..e165ba20577b 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs @@ -21,9 +21,12 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddDbContext(opt => + opt.UseInMemoryDatabase("ProductInventory")); services.AddDbContext(opt => opt.UseInMemoryDatabase("PetInventory")); services.AddDbContext(opt => diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj index 9dba099bd554..d527420c9e53 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj @@ -7,6 +7,7 @@ + diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Product.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Product.cs index 4ec278b11cb2..cd2e6fa4a39f 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Product.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Models/Product.cs @@ -6,6 +6,8 @@ public class Product { public int Id { get; set; } + public bool IsDiscontinued { get; set; } + [Required] public string Name { get; set; } diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs index b2dc4ef7a45d..910582f1b9ca 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/ProductsRepository.cs @@ -18,6 +18,7 @@ public ProductsRepository(ProductContext context) _context.Products.AddRange( new Product { + IsDiscontinued = true, Name = "Learning ASP.NET Core", Description = "A best-selling book covering the fundamentals of ASP.NET Core" }, @@ -30,6 +31,15 @@ public ProductsRepository(ProductContext context) } } + public List GetDiscontinuedProducts() + { + var products = (from p in _context.Products + where p.IsDiscontinued + select p).ToList(); + + return products; + } + public List GetProducts() { return _context.Products.ToList(); diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index 3e4a39dddfba..6ec265a9d823 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -1,23 +1,114 @@ --- -title: Build Web APIs in ASP.NET Core +title: Build web APIs with ASP.NET Core author: scottaddie -description: A collection of resources related to using ASP.NET Core Web API +description: Learn about the features available for building a web API in ASP.NET Core and when it's appropriate to use each feature. manager: wpickett ms.author: scaddie -ms.date: 04/17/2018 -ms.prod: asp.net-core +ms.custom: mvc +ms.date: 04/23/2018 +ms.prod: aspnet-core ms.technology: aspnet ms.topic: article uid: web-api/index --- -# Build Web APIs in ASP.NET Core +# Build web APIs with ASP.NET Core -* [Create a Web API with ASP.NET Core and Visual Studio Code](xref:tutorials/web-api-vsc) -* [Create a Web API with ASP.NET Core and Visual Studio for Mac](xref:tutorials/first-web-api-mac) -* [Create a Web API with ASP.NET Core and Visual Studio for Windows](xref:tutorials/first-web-api) -* [Controller action return types in ASP.NET Core Web API](xref:web-api/action-return-types) -* [Define a controller in ASP.NET Core Web API](xref:web-api/define-controller) -* [Help pages using Swagger](xref:tutorials/web-api-help-pages-using-swagger) -* [Create backend services for native mobile apps](xref:mobile/native-mobile-backend) +By [Scott Addie](https://github.com/scottaddie) + +[View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/web-api/define-controller/samples) ([how to download](xref:tutorials/index#how-to-download-a-sample)) + +ASP.NET Core offers the following options for creating a web API controller: + +::: moniker range="<= aspnetcore-2.0" +* [Derive class from Controller](#derive-class-from-controller) +* [Derive class from ControllerBase](#derive-class-from-controllerbase) +::: moniker-end +::: moniker range=">= aspnetcore-2.1" +* [Derive class from Controller](#derive-class-from-controller) +* [Derive class from ControllerBase](#derive-class-from-controllerbase) +* [Annotate class with ApiControllerAttribute](#annotate-class-with-apicontrollerattribute) +::: moniker-end + +This document explains when it's most appropriate to use each option. + +## Derive class from Controller + +Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) class in a controller that needs to support HTML and Razor in addition to web API actions. Examples that require `Controller` inheritance include returning MVC views or [invoking view components](xref:mvc/views/view-components#invoking-a-view-component-directly-from-a-controller). + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] + +In the preceding controller, the `Index` action returns a [ViewResult](/dotnet/api/microsoft.aspnetcore.mvc.viewresult) representing the associated MVC view at *Views/Orders/Index.cshtml*. The `GetById` and `CreateAsync` actions respond to HTTP GET and POST requests, respectively. + +## Derive class from ControllerBase + +Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class in a controller that doesn't need to support HTML and Razor. For example, the following controller only supports Web API actions: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] + +A benefit of deriving from `ControllerBase` instead of `Controller` is that IntelliSense displays only web API-specific members. + +::: moniker range=">= aspnetcore-2.1" +## Annotate class with ApiControllerAttribute + +ASP.NET Core 2.1 introduces the `[ApiController]` attribute to denote a web API controller class. For example: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=2)] + +This attribute is commonly coupled with either `ControllerBase` or `Controller` to gain access to useful methods and properties. `ControllerBase` provides access to methods such as [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction) and [File](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.file). `Controller` provides access to methods such as [Json](/dotnet/api/microsoft.aspnetcore.mvc.controller.json) and [View](/dotnet/api/microsoft.aspnetcore.mvc.controller.view). Another approach is to create a custom base controller class annotated with the `[ApiController]` attribute. + +The following sections describe convenience features added by the attribute. + +### Automatic HTTP 400 responses + +Validation errors automatically trigger an HTTP 400 response. The following code becomes unnecessary in your actions: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?range=46-49)] + +This default behavior is disabled with the following code in *Startup.ConfigureServices*: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=5)] + +### Binding source parameter inference + +Without the `[ApiController]` attribute, binding source attributes are explicitly defined to configure each parameter's binding source. In the following example, the `[FromQuery]` attribute indicates that the `discontinuedOnly` parameter value is provided in the request URL's querystring. The `[FromBody]` attribute indicates that the `product` parameter value is provided in the request body. + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs?name=snippet_BindingSourceAttributes&highlight=3,22)] + +Inference rules are applied for the default data sources of action parameters. These rules configure the binding sources you're otherwise likely to manually apply to the action parameters. The binding source attributes behave as follows: + +* **[[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute)** is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. When an action has more than one parameter explicitly specified (via `[FromBody]`) or inferred as bound from the request body, an exception is thrown. For example, the following action signatures cause an exception: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs?name=snippet_ActionsCausingExceptions)] + +* **[[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute)** is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.iformfilecollection). It's not inferred for any simple or user-defined types. +* **[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. +* **[[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute)** is inferred for any other action parameters. + +The default inference rules are disabled with the following code in *Startup.ConfigureServices*: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=4)] + +### Multipart/form-data request inference + +When an action parameter is annotated with the [[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute) attribute, the `multipart/form-data` request content type is inferred. + +The default behavior is disabled with the following code in *Startup.ConfigureServices*: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Startup.cs?name=snippet_ConfigureApiBehaviorOptions&highlight=3)] + +### Attribute routing requirement + +Attribute routing becomes a requirement. For example: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=1)] + +Actions are inaccessible via [conventional routes](xref:mvc/controllers/routing#conventional-routing) defined in [UseMvc](/dotnet/api/microsoft.aspnetcore.builder.mvcapplicationbuilderextensions.usemvc#Microsoft_AspNetCore_Builder_MvcApplicationBuilderExtensions_UseMvc_Microsoft_AspNetCore_Builder_IApplicationBuilder_System_Action_Microsoft_AspNetCore_Routing_IRouteBuilder__) or by [UseMvcWithDefaultRoute](/dotnet/api/microsoft.aspnetcore.builder.mvcapplicationbuilderextensions.usemvcwithdefaultroute#Microsoft_AspNetCore_Builder_MvcApplicationBuilderExtensions_UseMvcWithDefaultRoute_Microsoft_AspNetCore_Builder_IApplicationBuilder_) in *Startup.Configure*. +::: moniker-end + +## Additional resources + +* [Controller action return types](xref:web-api/action-return-types) +* [Custom formatters](xref:web-api/advanced/custom-formatters) * [Format response data](xref:web-api/advanced/formatting) -* [Custom formatters](xref:web-api/advanced/custom-formatters) \ No newline at end of file +* [Help pages using Swagger](xref:tutorials/web-api-help-pages-using-swagger) +* [Routing to controller actions](xref:mvc/controllers/routing) \ No newline at end of file From 3932ec74e2d9fdddf9ab661b35763bc21674ad91 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 12:41:07 -0500 Subject: [PATCH 19/33] Add attributes table --- aspnetcore/web-api/index.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index 6ec265a9d823..5f11c397291b 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -70,19 +70,30 @@ This default behavior is disabled with the following code in *Startup.ConfigureS ### Binding source parameter inference +The following binding source attributes exist: + +|Attribute|Binding source | +|---------|---------| +|**[[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute)** | Request body | +|**[[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute)** | Form data in the request body | +|**[[FromHeader]](/dotnet/api/microsoft.aspnetcore.mvc.fromheaderattribute)** | Request header | +|**[[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute)** | Request query string parameter | +|**[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** | Route data from the current request | +|**[[FromServices]](xref:mvc/controllers/dependency-injection#action-injection-with-fromservices)** | The request service injected as an action parameter | + Without the `[ApiController]` attribute, binding source attributes are explicitly defined to configure each parameter's binding source. In the following example, the `[FromQuery]` attribute indicates that the `discontinuedOnly` parameter value is provided in the request URL's querystring. The `[FromBody]` attribute indicates that the `product` parameter value is provided in the request body. [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs?name=snippet_BindingSourceAttributes&highlight=3,22)] Inference rules are applied for the default data sources of action parameters. These rules configure the binding sources you're otherwise likely to manually apply to the action parameters. The binding source attributes behave as follows: -* **[[FromBody]](/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute)** is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. When an action has more than one parameter explicitly specified (via `[FromBody]`) or inferred as bound from the request body, an exception is thrown. For example, the following action signatures cause an exception: +* **[FromBody]** is inferred for complex type parameters. An exception to this rule is any complex, built-in type with a special meaning, such as [IFormCollection](/dotnet/api/microsoft.aspnetcore.http.iformcollection) and [CancellationToken](/dotnet/api/system.threading.cancellationtoken). The binding source inference code ignores those special types. When an action has more than one parameter explicitly specified (via `[FromBody]`) or inferred as bound from the request body, an exception is thrown. For example, the following action signatures cause an exception: [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs?name=snippet_ActionsCausingExceptions)] -* **[[FromForm]](/dotnet/api/microsoft.aspnetcore.mvc.fromformattribute)** is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.iformfilecollection). It's not inferred for any simple or user-defined types. -* **[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** is inferred for any action parameter name matching a parameter in the route template. When multiple routes match an action parameter, any route value is considered `[FromRoute]`. -* **[[FromQuery]](/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute)** is inferred for any other action parameters. +* **[FromForm]** is inferred for action parameters of type [IFormFile](/dotnet/api/microsoft.aspnetcore.http.iformfile) and [IFormFileCollection](/dotnet/api/microsoft.aspnetcore.http.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 multiple routes match an action parameter, any route value is considered `[FromRoute]`. +* **[FromQuery]** is inferred for any other action parameters. The default inference rules are disabled with the following code in *Startup.ConfigureServices*: From 11ae6cbac3c1e6730c18daf62580deb08489d80d Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 12:48:36 -0500 Subject: [PATCH 20/33] Verbiage tweaks --- aspnetcore/web-api/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index 5f11c397291b..e2808cfb7922 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -70,7 +70,7 @@ This default behavior is disabled with the following code in *Startup.ConfigureS ### Binding source parameter inference -The following binding source attributes exist: +A binding source attribute defines an action parameter's expected binding source. The following binding source attributes exist: |Attribute|Binding source | |---------|---------| @@ -81,7 +81,7 @@ The following binding source attributes exist: |**[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** | Route data from the current request | |**[[FromServices]](xref:mvc/controllers/dependency-injection#action-injection-with-fromservices)** | The request service injected as an action parameter | -Without the `[ApiController]` attribute, binding source attributes are explicitly defined to configure each parameter's binding source. In the following example, the `[FromQuery]` attribute indicates that the `discontinuedOnly` parameter value is provided in the request URL's querystring. The `[FromBody]` attribute indicates that the `product` parameter value is provided in the request body. +Without the `[ApiController]` attribute, binding source attributes are explicitly defined. In the following example, the `[FromQuery]` attribute indicates that the `discontinuedOnly` parameter value is provided in the request URL's querystring. The `[FromBody]` attribute indicates that the `product` parameter value is provided in the request body. [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs?name=snippet_BindingSourceAttributes&highlight=3,22)] From 7e883bc1a9fca94e041476b584c1873d8b4fcfd2 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 12:51:01 -0500 Subject: [PATCH 21/33] querystring -> query string --- aspnetcore/web-api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index e2808cfb7922..758dcc17f8af 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -81,7 +81,7 @@ A binding source attribute defines an action parameter's expected binding source |**[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** | Route data from the current request | |**[[FromServices]](xref:mvc/controllers/dependency-injection#action-injection-with-fromservices)** | The request service injected as an action parameter | -Without the `[ApiController]` attribute, binding source attributes are explicitly defined. In the following example, the `[FromQuery]` attribute indicates that the `discontinuedOnly` parameter value is provided in the request URL's querystring. The `[FromBody]` attribute indicates that the `product` parameter value is provided in the request body. +Without the `[ApiController]` attribute, binding source attributes are explicitly defined. In the following example, the `[FromQuery]` attribute indicates that the `discontinuedOnly` parameter value is provided in the request URL's query string. The `[FromBody]` attribute indicates that the `product` parameter value is provided in the request body. [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs?name=snippet_BindingSourceAttributes&highlight=3,22)] From 916c9998759c828ff27428fce73272faf8c0b3d4 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 12:53:18 -0500 Subject: [PATCH 22/33] Minor edit --- aspnetcore/web-api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index 758dcc17f8af..4e359a512c60 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -70,7 +70,7 @@ This default behavior is disabled with the following code in *Startup.ConfigureS ### Binding source parameter inference -A binding source attribute defines an action parameter's expected binding source. The following binding source attributes exist: +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 | |---------|---------| From f010d895d4fd5e541dd8023f06f4f39927e46dae Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 13:35:47 -0500 Subject: [PATCH 23/33] Add 2.1 controller samples --- .../Controllers/OrdersController.cs | 48 ++++++++++++++++ .../Controllers/PetsController.cs | 55 +++++++++++++++++++ .../Controllers/TestController.cs | 1 + .../samples/WebApiSample.Api/Startup.cs | 9 ++- .../Repositories/PetsRepository.cs | 2 +- aspnetcore/web-api/index.md | 10 ++++ 6 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/PetsController.cs diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs new file mode 100644 index 000000000000..0c7d71c79a18 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using WebApiSample.DataAccess.Models; +using WebApiSample.DataAccess.Repositories; + +namespace WebApiSample.Api.Controllers +{ + #region snippet_OrdersController + public class OrdersController : Controller + { + private readonly OrdersRepository _repository; + + public OrdersController(OrdersRepository repository) + { + _repository = repository; + } + + public IActionResult Index() => View(); + + [HttpGet("{id}")] + [ProducesResponseType(404)] + public ActionResult GetById(int id) + { + if (!_repository.TryGetOrder(id, out var order)) + { + return NotFound(); + } + + return order; + } + + [HttpPost] + [ProducesResponseType(400)] + public async Task> CreateAsync(Order order) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + await _repository.AddOrderAsync(order); + + return CreatedAtAction(nameof(GetById), + new { id = order.Id }, order); + } + } + #endregion +} \ No newline at end of file diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/PetsController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/PetsController.cs new file mode 100644 index 000000000000..a73fd9a5877f --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/PetsController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using WebApiSample.DataAccess.Models; +using WebApiSample.DataAccess.Repositories; + +namespace WebApiSample.Api.Controllers +{ + #region snippet_PetsController + [Produces("application/json")] + [Route("api/[controller]")] + public class PetsController : ControllerBase + { + private readonly PetsRepository _repository; + + public PetsController(PetsRepository repository) + { + _repository = repository; + } + + [HttpGet] + public ActionResult> Get() + { + return _repository.GetPets(); + } + + [HttpGet("{id}")] + [ProducesResponseType(404)] + public ActionResult GetById(int id) + { + if (!_repository.TryGetPet(id, out var pet)) + { + return NotFound(); + } + + return pet; + } + + [HttpPost] + [ProducesResponseType(400)] + public async Task> CreateAsync(Pet pet) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + await _repository.AddPetAsync(pet); + + return CreatedAtAction(nameof(GetById), + new { id = pet.Id }, pet); + } + } + #endregion +} \ No newline at end of file diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs index a628e879662d..3b6e26751f97 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs @@ -5,6 +5,7 @@ namespace WebApiSample.Api.Controllers { [Produces("application/json")] [Route("api/[controller]")] + [ApiExplorerSettings(IgnoreApi = true)] [ApiController] public class TestController : Controller { diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs index 8c246799aec7..3c1f32bc2fd4 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs @@ -23,9 +23,16 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddDbContext(opt => opt.UseInMemoryDatabase("ProductInventory")); - + services.AddDbContext(opt => + opt.UseInMemoryDatabase("PetInventory")); + services.AddDbContext(opt => + opt.UseInMemoryDatabase("Orders")); + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddSwaggerGen(c => diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/PetsRepository.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/PetsRepository.cs index 6216d0dc4449..417eb042b0cf 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/PetsRepository.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.DataAccess/Repositories/PetsRepository.cs @@ -44,7 +44,7 @@ public PetsRepository(PetContext context) } } - public IEnumerable GetPets() + public List GetPets() { return _context.Pets.ToList(); } diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index 4e359a512c60..6c0bd4127098 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -35,7 +35,12 @@ This document explains when it's most appropriate to use each option. Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) class in a controller that needs to support HTML and Razor in addition to web API actions. Examples that require `Controller` inheritance include returning MVC views or [invoking view components](xref:mvc/views/view-components#invoking-a-view-component-directly-from-a-controller). +::: moniker range=">= aspnetcore-2.1" +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] +::: moniker-end +::: moniker range="<= aspnetcore2.0" [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] +:::moniker-end In the preceding controller, the `Index` action returns a [ViewResult](/dotnet/api/microsoft.aspnetcore.mvc.viewresult) representing the associated MVC view at *Views/Orders/Index.cshtml*. The `GetById` and `CreateAsync` actions respond to HTTP GET and POST requests, respectively. @@ -43,7 +48,12 @@ In the preceding controller, the `Index` action returns a [ViewResult](/dotnet/a Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class in a controller that doesn't need to support HTML and Razor. For example, the following controller only supports Web API actions: +::: moniker range=">= aspnetcore-2.1" +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] +::: moniker-end +::: moniker range="<= aspnetcore2.0" [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] +::: moniker-end A benefit of deriving from `ControllerBase` instead of `Controller` is that IntelliSense displays only web API-specific members. From da136e53c1a37e988e9d5a1f46a4c077f87cfb49 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 13:41:25 -0500 Subject: [PATCH 24/33] Fix invalid moniker names --- aspnetcore/web-api/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index 6c0bd4127098..a976cf83cde8 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -38,7 +38,7 @@ Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) c ::: moniker range=">= aspnetcore-2.1" [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] ::: moniker-end -::: moniker range="<= aspnetcore2.0" +::: moniker range="<= aspnetcore-2.0" [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] :::moniker-end @@ -51,7 +51,7 @@ Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controlle ::: moniker range=">= aspnetcore-2.1" [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] ::: moniker-end -::: moniker range="<= aspnetcore2.0" +::: moniker range="<= aspnetcore-2.0" [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] ::: moniker-end From f58d2627b28e23b0b95a3f452b45933bb06a0cb1 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 14:20:37 -0500 Subject: [PATCH 25/33] Update code sample to 2.1 --- .../Controllers/ProductsController.cs | 2 +- .../Controllers/ProductsController.cs | 29 ++++++++++++------- aspnetcore/web-api/index.md | 4 +-- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs index 786bd86cbf40..fdbb154a70cc 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs @@ -51,6 +51,7 @@ public IActionResult Get([FromQuery] bool discontinuedOnly = false) return Ok(products); } + #endregion [HttpPost] [ProducesResponseType(typeof(Product), 201)] @@ -67,6 +68,5 @@ public async Task CreateAsync([FromBody] Product product) return CreatedAtAction(nameof(GetById), new { id = product.Id }, product); } - #endregion } } \ No newline at end of file diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs index ef9464deb1d2..6b80584d753a 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs @@ -19,14 +19,6 @@ public ProductsController(ProductsRepository repository) _repository = repository; } - #region snippet_Get - [HttpGet] - public ActionResult> Get() - { - return _repository.GetProducts(); - } - #endregion - #region snippet_GetById [HttpGet("{id}")] [ProducesResponseType(200)] @@ -42,7 +34,25 @@ public ActionResult GetById(int id) } #endregion - #region snippet_CreateAsync + #region snippet_BindingSourceAttributes + [HttpGet] + public ActionResult> Get([FromQuery] bool discontinuedOnly = false) + { + List products = null; + + if (discontinuedOnly) + { + products = _repository.GetDiscontinuedProducts(); + } + else + { + products = _repository.GetProducts(); + } + + return products; + } + #endregion + [HttpPost] [ProducesResponseType(201)] [ProducesResponseType(400)] @@ -53,6 +63,5 @@ public async Task> CreateAsync(Product product) return CreatedAtAction(nameof(GetById), new { id = product.Id }, product); } - #endregion } } \ No newline at end of file diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index a976cf83cde8..8341f84ac256 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -91,9 +91,9 @@ A binding source attribute defines the location at which an action parameter's v |**[[FromRoute]](/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute)** | Route data from the current request | |**[[FromServices]](xref:mvc/controllers/dependency-injection#action-injection-with-fromservices)** | The request service injected as an action parameter | -Without the `[ApiController]` attribute, binding source attributes are explicitly defined. In the following example, the `[FromQuery]` attribute indicates that the `discontinuedOnly` parameter value is provided in the request URL's query string. The `[FromBody]` attribute indicates that the `product` parameter value is provided in the request body. +Without the `[ApiController]` attribute, binding source attributes are explicitly defined. In the following example, the `[FromQuery]` attribute indicates that the `discontinuedOnly` parameter value is provided in the request URL's query string: -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/ProductsController.cs?name=snippet_BindingSourceAttributes&highlight=3,22)] +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_BindingSourceAttributes&highlight=3)] Inference rules are applied for the default data sources of action parameters. These rules configure the binding sources you're otherwise likely to manually apply to the action parameters. The binding source attributes behave as follows: From b2371afed6d9f45122a906cb01a752817ec64201 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 14:27:59 -0500 Subject: [PATCH 26/33] Update line number --- aspnetcore/web-api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index 8341f84ac256..3f065831f305 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -93,7 +93,7 @@ A binding source attribute defines the location at which an action parameter's v Without the `[ApiController]` attribute, binding source attributes are explicitly defined. In the following example, the `[FromQuery]` attribute indicates that the `discontinuedOnly` parameter value is provided in the request URL's query string: -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_BindingSourceAttributes&highlight=3)] +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_BindingSourceAttributes&highlight=2)] Inference rules are applied for the default data sources of action parameters. These rules configure the binding sources you're otherwise likely to manually apply to the action parameters. The binding source attributes behave as follows: From 5401a7243ec42023f4d4037b4e9a8bef2726f2d3 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Mon, 23 Apr 2018 14:38:15 -0500 Subject: [PATCH 27/33] Convert uppercase W to lowercase --- aspnetcore/web-api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index 3f065831f305..e5029ba50a90 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -46,7 +46,7 @@ In the preceding controller, the `Index` action returns a [ViewResult](/dotnet/a ## Derive class from ControllerBase -Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class in a controller that doesn't need to support HTML and Razor. For example, the following controller only supports Web API actions: +Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class in a controller that doesn't need to support HTML and Razor. For example, the following controller only supports web API actions: ::: moniker range=">= aspnetcore-2.1" [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] From c2658cca343dd4fd7b480cac40d67535b5df727a Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 24 Apr 2018 13:36:21 -0500 Subject: [PATCH 28/33] React to feedback --- .../Controllers/OrdersController.cs | 50 ------------------- .../samples/WebApiSample.Api.Pre21/Startup.cs | 3 -- .../Controllers/MyBaseController.cs | 11 ++++ .../Controllers/OrdersController.cs | 48 ------------------ .../samples/WebApiSample.Api/Startup.cs | 3 -- aspnetcore/web-api/index.md | 39 ++++----------- 6 files changed, 20 insertions(+), 134 deletions(-) delete mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs create mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/MyBaseController.cs delete mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs deleted file mode 100644 index 4ad9b31bcee2..000000000000 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using WebApiSample.DataAccess.Models; -using WebApiSample.DataAccess.Repositories; - -namespace WebApiSample.Api.Controllers -{ - #region snippet_OrdersController - public class OrdersController : Controller - { - private readonly OrdersRepository _repository; - - public OrdersController(OrdersRepository repository) - { - _repository = repository; - } - - public IActionResult Index() => View(); - - [HttpGet("{id}")] - [ProducesResponseType(typeof(Order), 200)] - [ProducesResponseType(404)] - public IActionResult GetById(int id) - { - if (!_repository.TryGetOrder(id, out var order)) - { - return NotFound(); - } - - return Ok(order); - } - - [HttpPost] - [ProducesResponseType(typeof(Order), 201)] - [ProducesResponseType(400)] - public async Task CreateAsync([FromBody] Order order) - { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - await _repository.AddOrderAsync(order); - - return CreatedAtAction(nameof(GetById), - new { id = order.Id }, order); - } - } - #endregion -} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs index e165ba20577b..5a461e066507 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Startup.cs @@ -23,14 +23,11 @@ public void ConfigureServices(IServiceCollection services) { services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddDbContext(opt => opt.UseInMemoryDatabase("ProductInventory")); services.AddDbContext(opt => opt.UseInMemoryDatabase("PetInventory")); - services.AddDbContext(opt => - opt.UseInMemoryDatabase("Orders")); services.AddMvc(); diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/MyBaseController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/MyBaseController.cs new file mode 100644 index 000000000000..d03aeaf749c6 --- /dev/null +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/MyBaseController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WebApiSample.Api.Controllers +{ + #region snippet_ControllerSignature + [ApiController] + public class MyBaseController + { + } + #endregion +} diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs deleted file mode 100644 index 0c7d71c79a18..000000000000 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using WebApiSample.DataAccess.Models; -using WebApiSample.DataAccess.Repositories; - -namespace WebApiSample.Api.Controllers -{ - #region snippet_OrdersController - public class OrdersController : Controller - { - private readonly OrdersRepository _repository; - - public OrdersController(OrdersRepository repository) - { - _repository = repository; - } - - public IActionResult Index() => View(); - - [HttpGet("{id}")] - [ProducesResponseType(404)] - public ActionResult GetById(int id) - { - if (!_repository.TryGetOrder(id, out var order)) - { - return NotFound(); - } - - return order; - } - - [HttpPost] - [ProducesResponseType(400)] - public async Task> CreateAsync(Order order) - { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - await _repository.AddOrderAsync(order); - - return CreatedAtAction(nameof(GetById), - new { id = order.Id }, order); - } - } - #endregion -} \ No newline at end of file diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs index 3c1f32bc2fd4..1875cc88a292 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Startup.cs @@ -24,14 +24,11 @@ public void ConfigureServices(IServiceCollection services) { services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddDbContext(opt => opt.UseInMemoryDatabase("ProductInventory")); services.AddDbContext(opt => opt.UseInMemoryDatabase("PetInventory")); - services.AddDbContext(opt => - opt.UseInMemoryDatabase("Orders")); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index e5029ba50a90..a810c6762f3f 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -5,7 +5,7 @@ description: Learn about the features available for building a web API in ASP.NE manager: wpickett ms.author: scaddie ms.custom: mvc -ms.date: 04/23/2018 +ms.date: 04/24/2018 ms.prod: aspnet-core ms.technology: aspnet ms.topic: article @@ -17,36 +17,11 @@ By [Scott Addie](https://github.com/scottaddie) [View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/web-api/define-controller/samples) ([how to download](xref:tutorials/index#how-to-download-a-sample)) -ASP.NET Core offers the following options for creating a web API controller: - -::: moniker range="<= aspnetcore-2.0" -* [Derive class from Controller](#derive-class-from-controller) -* [Derive class from ControllerBase](#derive-class-from-controllerbase) -::: moniker-end -::: moniker range=">= aspnetcore-2.1" -* [Derive class from Controller](#derive-class-from-controller) -* [Derive class from ControllerBase](#derive-class-from-controllerbase) -* [Annotate class with ApiControllerAttribute](#annotate-class-with-apicontrollerattribute) -::: moniker-end - -This document explains when it's most appropriate to use each option. - -## Derive class from Controller - -Inherit from the [Controller](/dotnet/api/microsoft.aspnetcore.mvc.controller) class in a controller that needs to support HTML and Razor in addition to web API actions. Examples that require `Controller` inheritance include returning MVC views or [invoking view components](xref:mvc/views/view-components#invoking-a-view-component-directly-from-a-controller). - -::: moniker range=">= aspnetcore-2.1" -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] -::: moniker-end -::: moniker range="<= aspnetcore-2.0" -[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/OrdersController.cs?name=snippet_OrdersController&highlight=1)] -:::moniker-end - -In the preceding controller, the `Index` action returns a [ViewResult](/dotnet/api/microsoft.aspnetcore.mvc.viewresult) representing the associated MVC view at *Views/Orders/Index.cshtml*. The `GetById` and `CreateAsync` actions respond to HTTP GET and POST requests, respectively. +This document explains how to build a web API in ASP.NET Core and when it's most appropriate to use each option. ## Derive class from ControllerBase -Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class in a controller that doesn't need to support HTML and Razor. For example, the following controller only supports web API actions: +Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase) class in a controller that's intended to serve as a web API. For example: ::: moniker range=">= aspnetcore-2.1" [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] @@ -55,7 +30,7 @@ Inherit from the [ControllerBase](/dotnet/api/microsoft.aspnetcore.mvc.controlle [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api.Pre21/Controllers/PetsController.cs?name=snippet_PetsController&highlight=3)] ::: moniker-end -A benefit of deriving from `ControllerBase` instead of `Controller` is that IntelliSense displays only web API-specific members. +The `ControllerBase` class provides access to numerous properties and methods. In the preceding example, some such methods include [BadRequest](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.badrequest) and [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction). These methods are invoked within action methods to return HTTP 400 and 201 status codes, respectively. The [ModelState](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.modelstate) property, also provided by `ControllerBase`, is accessed to perform request model validation. ::: moniker range=">= aspnetcore-2.1" ## Annotate class with ApiControllerAttribute @@ -64,7 +39,11 @@ ASP.NET Core 2.1 introduces the `[ApiController]` attribute to denote a web API [!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/ProductsController.cs?name=snippet_ControllerSignature&highlight=2)] -This attribute is commonly coupled with either `ControllerBase` or `Controller` to gain access to useful methods and properties. `ControllerBase` provides access to methods such as [CreatedAtAction](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction) and [File](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.file). `Controller` provides access to methods such as [Json](/dotnet/api/microsoft.aspnetcore.mvc.controller.json) and [View](/dotnet/api/microsoft.aspnetcore.mvc.controller.view). Another approach is to create a custom base controller class annotated with the `[ApiController]` attribute. +This attribute is commonly coupled with `ControllerBase` to gain access to useful methods and properties. `ControllerBase` provides access to methods such as [NotFound](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.notfound) and [File](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.file). + +Another approach is to create a custom base controller class annotated with the `[ApiController]` attribute: + +[!code-csharp[](../web-api/define-controller/samples/WebApiSample.Api/Controllers/MyBaseController.cs?name=snippet_ControllerSignature)] The following sections describe convenience features added by the attribute. From e790d33f2747e9f6275a9e9659e298a6ead2a477 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 24 Apr 2018 13:41:06 -0500 Subject: [PATCH 29/33] Update sample app's README file --- aspnetcore/web-api/define-controller/samples/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/aspnetcore/web-api/define-controller/samples/README.md b/aspnetcore/web-api/define-controller/samples/README.md index 49e313059033..812e307525ce 100644 --- a/aspnetcore/web-api/define-controller/samples/README.md +++ b/aspnetcore/web-api/define-controller/samples/README.md @@ -8,6 +8,5 @@ This sample app consists of the following projects: This sample illustrates variations of Web API controller creation: -- [Derive class from Controller](https://docs.microsoft.com/en-us/aspnet/core/web-api/define-controller#derive-class-from-controller) - [Derive class from ControllerBase](https://docs.microsoft.com/en-us/aspnet/core/web-api/define-controller#derive-class-from-controllerbase) - [Annotate class with ApiControllerAttribute](https://docs.microsoft.com/en-us/aspnet/core/web-api/define-controller#annotate-class-with-apicontrollerattribute) \ No newline at end of file From 5d45cb5b078ac89b6d0a3ce9dd8083e2a46ba62c Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 24 Apr 2018 13:48:19 -0500 Subject: [PATCH 30/33] Project file cleanup --- .../WebApiSample.Api.Pre21.csproj | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj index d527420c9e53..eab56ca32b89 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/WebApiSample.Api.Pre21.csproj @@ -19,16 +19,4 @@ - - - $(IncludeRazorContentInPack) - - - $(IncludeRazorContentInPack) - - - $(IncludeRazorContentInPack) - - -
From 6b2192e15981d222ecfae071ef2fdb6b1df7d8cd Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 24 Apr 2018 13:49:33 -0500 Subject: [PATCH 31/33] Replace word --- aspnetcore/web-api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/index.md b/aspnetcore/web-api/index.md index a810c6762f3f..18736befde00 100644 --- a/aspnetcore/web-api/index.md +++ b/aspnetcore/web-api/index.md @@ -17,7 +17,7 @@ By [Scott Addie](https://github.com/scottaddie) [View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/web-api/define-controller/samples) ([how to download](xref:tutorials/index#how-to-download-a-sample)) -This document explains how to build a web API in ASP.NET Core and when it's most appropriate to use each option. +This document explains how to build a web API in ASP.NET Core and when it's most appropriate to use each feature. ## Derive class from ControllerBase From 7a43402e6cf714f564fad8b5410379a03b8e3829 Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 24 Apr 2018 13:52:29 -0500 Subject: [PATCH 32/33] Remove MVC views from sample app --- .../Views/Orders/Index.cshtml | 7 ------- .../Views/Shared/_Layout.cshtml | 13 ------------- .../WebApiSample.Api.Pre21/Views/_ViewStart.cshtml | 3 --- 3 files changed, 23 deletions(-) delete mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Orders/Index.cshtml delete mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Shared/_Layout.cshtml delete mode 100644 aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/_ViewStart.cshtml diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Orders/Index.cshtml b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Orders/Index.cshtml deleted file mode 100644 index 15ac2f2d06d0..000000000000 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Orders/Index.cshtml +++ /dev/null @@ -1,7 +0,0 @@ - -@{ - ViewData["Title"] = "Index"; -} - -

Index

- diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Shared/_Layout.cshtml b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Shared/_Layout.cshtml deleted file mode 100644 index 3c25d93a57e0..000000000000 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/Shared/_Layout.cshtml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - @ViewBag.Title - - -
- @RenderBody() -
- - diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/_ViewStart.cshtml b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/_ViewStart.cshtml deleted file mode 100644 index a5f10045db97..000000000000 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api.Pre21/Views/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} From c1b589dab5fa0edb80ac83c4bfe72119984c155e Mon Sep 17 00:00:00 2001 From: Scott Addie Date: Tue, 24 Apr 2018 13:57:26 -0500 Subject: [PATCH 33/33] Derive from ControllerBase instead of Controller --- .../samples/WebApiSample.Api/Controllers/TestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs index 3b6e26751f97..8c1a12c07e77 100644 --- a/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs +++ b/aspnetcore/web-api/define-controller/samples/WebApiSample.Api/Controllers/TestController.cs @@ -7,7 +7,7 @@ namespace WebApiSample.Api.Controllers [Route("api/[controller]")] [ApiExplorerSettings(IgnoreApi = true)] [ApiController] - public class TestController : Controller + public class TestController : ControllerBase { #region snippet_ActionsCausingExceptions // Don't do this. All of the following actions result in an exception.