From a8e38ffa57550adeca3b1a0b66032faa2181bbf3 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Fri, 21 Dec 2018 15:04:42 +0300 Subject: [PATCH] Resolved #650: Document how to create API controllers from application services conventionally. --- docs/en/AspNetCore/Auto-API-Controllers.md | 138 ++++++++++++++++++ docs/en/Index.md | 2 +- docs/en/Tutorials/AspNetCore-Mvc/Part-I.md | 2 +- docs/en/docs-nav.json | 8 +- docs/en/images/bookstore-apis.png | Bin 0 -> 17154 bytes .../Mvc/Conventions/AbpServiceConvention.cs | 4 +- .../ConventionalControllerSetting.cs | 13 +- 7 files changed, 155 insertions(+), 12 deletions(-) create mode 100644 docs/en/AspNetCore/Auto-API-Controllers.md create mode 100644 docs/en/images/bookstore-apis.png diff --git a/docs/en/AspNetCore/Auto-API-Controllers.md b/docs/en/AspNetCore/Auto-API-Controllers.md new file mode 100644 index 0000000000..48567b6919 --- /dev/null +++ b/docs/en/AspNetCore/Auto-API-Controllers.md @@ -0,0 +1,138 @@ +# Auto API Controllers + +Once you create an [application service](Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. + +ABP can **automagically** configures your application services as MVC API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible fully customize it. + +## Configuration + +Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below: + +````csharp +[DependsOn(BookStoreApplicationModule)] +public class BookStoreWebModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.ConventionalControllers.Create(typeof(BookStoreApplicationModule).Assembly); + }); + } +} +```` + +This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/). + +![bookstore-apis](../images/bookstore-apis.png) + +### Examples + +Some example method names and the corresponding routes calculated by convention: + +| Service Method Name | HTTP Method | Route | +| ----------------------------------------------------- | ----------- | -------------------------- | +| GetAsync(Guid id) | GET | /api/app/book/{id} | +| GetListAsync() | GET | /api/app/book | +| CreateAsync(CreateBookDto input) | POST | /api/app/book | +| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} | +| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} | +| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors | +| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor | + +### HTTP Method + +ABP uses a naming convention while determining the HTTP method for a service method (action): + +- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'. +- **Put**: Used if the method name starts with 'Put' or 'Update'. +- **Delete**: Used if the method name starts with 'Delete' or 'Remove'. +- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'. +- **Patch**: Used if the method name starts with 'Patch'. +- Otherwise, **Post** is used **by default**. + +If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service. + +### Route + +Route is calculated based on some conventions: + +* It always starts with '**/api**'. +* Continues with a **route path**. Default value is '**/app**' and can be configured as like below: + +````csharp +Configure(options => +{ + options.ConventionalControllers + .Create(typeof(BookStoreApplicationModule).Assembly, opts => + { + opts.RootPath = "volosoft/book-store"; + }); +}); +```` + +Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth. + +* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **camelCase**. If your application service class name is 'BookAppService' then it becomes only '/book'. + * If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service. +* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route. +* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by; + * Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'. + * Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request. + * Converting the result to **camelCase**. + * If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name is will be 'phones'. + * Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. +* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). + +## Service Selection + +Creating conventional HTTP API controllers are not unique to application services actually. + +### IRemoteService Interface + +If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers. + +### RemoteService Attribute + +`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example: + +````csharp +[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] +public class PersonAppService : ApplicationService +{ + +} +```` + +### TypePredicate Option + +You can further filter classes to become an API controller by providing the `TypePedicate` option: + +````csharp +services.Configure(options => +{ + options.ConventionalControllers + .Create(typeof(BookStoreApplicationModule).Assembly, opts => + { + opts.TypePredicate = type => { return true; }; + }); +}); +```` + +Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller. + +## API Explorer + +API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint. + +API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example: + +````csharp +[RemoteService(IsMetadataEnabled = false)] +public class PersonAppService : ApplicationService +{ + +} +```` + +Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route. \ No newline at end of file diff --git a/docs/en/Index.md b/docs/en/Index.md index df0cab524a..bd65ed5c7f 100644 --- a/docs/en/Index.md +++ b/docs/en/Index.md @@ -16,7 +16,7 @@ Easiest way to start a new project with ABP is to use the Startup templates: * [ASP.NET Core MVC Template](Getting-Started-AspNetCore-MVC-Template.md) -If you want to start from scratch (with an empty project) then manually install the ABP framework, use following tutorials: +If you want to start from scratch (with an empty project) then manually install the ABP framework, use the following tutorials: * [Console Application](Getting-Started-Console-Application.md) * [ASP.NET Core Web Application](Getting-Started-AspNetCore-Application.md) diff --git a/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md index 9a48022e27..9f766ae3b0 100644 --- a/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md +++ b/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md @@ -236,7 +236,7 @@ namespace Acme.BookStore You normally create **Controllers** to expose application services as **HTTP API** endpoints. Thus allowing browser or 3rd-party clients to call them via AJAX. -ABP can **automagically** configures your application services as MVC API Controllers by convention. +ABP can [**automagically**](../../AspNetCore/Auto-API-Controllers.md) configures your application services as MVC API Controllers by convention. #### Swagger UI diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index ff05d3934a..3d16381f01 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -189,7 +189,13 @@ "text": "ASP.NET Core MVC", "items": [ { - "text": "API Versioning" + "text": "API", + "items": [ + { + "text": "Auto API Controllers", + "path": "AspNetCore/Auto-API-Controllers.md" + } + ] }, { "text": "User Interface", diff --git a/docs/en/images/bookstore-apis.png b/docs/en/images/bookstore-apis.png new file mode 100644 index 0000000000000000000000000000000000000000..aedd79c7f1d9bd6732e7c71ea432c4e3c06e034c GIT binary patch literal 17154 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.FirstOrDefault(); - if (areaAttribute.RouteValue != null) + if (areaAttribute?.RouteValue != null) { return areaAttribute.RouteValue; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs index ac504b6546..fac627d182 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs @@ -1,12 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using JetBrains.Annotations; +using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Versioning; -using Volo.Abp.Application.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Volo.Abp.Reflection; namespace Volo.Abp.AspNetCore.Mvc.Conventions @@ -17,7 +16,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions public Assembly Assembly { get; } [NotNull] - public HashSet ControllerTypes { get; } + public HashSet ControllerTypes { get; } //TODO: Internal? [NotNull] public string RootPath