From a8f42772f17a0245417499a026d723c8f98110a9 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 2 Jan 2019 10:14:12 +0800 Subject: [PATCH] Auto-API-Controllers.md --- .../AspNetCore/Auto-API-Controllers.md | 138 ++++++++++++++++++ .../Tutorials/AspNetCore-Mvc/Part-I.md | 2 +- docs/zh-Hans/docs-nav.json | 8 +- docs/zh-Hans/images/bookstore-apis.png | Bin 0 -> 17154 bytes 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 docs/zh-Hans/AspNetCore/Auto-API-Controllers.md create mode 100644 docs/zh-Hans/images/bookstore-apis.png diff --git a/docs/zh-Hans/AspNetCore/Auto-API-Controllers.md b/docs/zh-Hans/AspNetCore/Auto-API-Controllers.md new file mode 100644 index 0000000000..afe8ea700b --- /dev/null +++ b/docs/zh-Hans/AspNetCore/Auto-API-Controllers.md @@ -0,0 +1,138 @@ +# 自动API控制器 + +创建[应用程序服务](Application-Services.md)后, 通常需要创建API控制器以将此服务公开为HTTP(REST)API端点. 典型的API控制器除了将方法调用重定向到应用程序服务并使用[HttpGet],[HttpPost],[Route]等属性配置REST API之外什么都不做. + +ABP可以按照惯例 **自动** 将你的应用程序服务配置为MVC API控制器. 大多数时候你不关心它的详细配置,但它可以完全被自定义. + +## 配置 + +基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示: + +````csharp +[DependsOn(BookStoreApplicationModule)] +public class BookStoreWebModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.ConventionalControllers.Create(typeof(BookStoreApplicationModule).Assembly); + }); + } +} +```` + +此示例代码配置包含类`BookStoreApplicationModule`的程序集中的所有应用程序服务.下图显示了[Swagger UI](https://swagger.io/tools/swagger-ui/)上的API内容. + +![bookstore-apis](../images/bookstore-apis.png) + +### 例子 + +一些示例方法名称和按约定生成的相应路由: + +| 服务方法名称 | HTTP Method | 路由 | +| ----------------------------------------------------- | ----------- | -------------------------- | +| 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在确定服务方法的HTTP Method时使用命名约定: + +- **Get**: 如果方法名称以`GetList`,`GetAll`或`Get`开头. +- **Put**: 如果方法名称以`Put`或`Update`开头. +- **Delete**: 如果方法名称以`Delete`或`Remove`开头. +- **Post**: 如果方法名称以`Create`,`Add`,`Insert`或`Post`开头. +- **Patch**: 如果方法名称以`Patch`开头. +- 其他情况, **Post** 为 **默认方式**. + +如果需要为特定方法自定义HTTP Method, 则可以使用标准ASP.NET Core的属性([HttpPost], [HttpGet], [HttpPut]... 等等.). 这需要添加[Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core)的Nuget包. + +### 路由 + +路由根据一些惯例生成: + +* 它始终以 **/api**开头. +* 接着是**路由路径**. 默认值为"**/app**", 可以进行如下配置: + +````csharp +Configure(options => +{ + options.ConventionalControllers + .Create(typeof(BookStoreApplicationModule).Assembly, opts => + { + opts.RootPath = "volosoft/book-store"; + }); +}); +```` + +然后获得一本书的路由将是'**/api/volosoft/book-store/book/{id}**'. 此示例使用两级根路径,但通常使用单个级别的深度. + +* 接着 **标准化控制器/服务名称**. 会删除`AppService`,`ApplicationService`和`Service`的后缀并将其转换为 **camelCase**. 如果你的应用程序服务类名称为`BookAppService`.那么它将变为`/book`. + * 如果要自定义命名, 则设置`UrlControllerNameNormalizer`选项. 它是一个委托允许你自定义每个控制器/服务的名称. +* 如果该方法具有 '**id**'参数, 则会在路由中添加'**/{id}**'. +* 如有必要,它会添加操作名称. 操作名称从服务上的方法名称获取并标准化; + * 删除'**Async**'后缀. 如果方法名称为'GetPhonesAsync',则变为`GetPhones`. + * 删除**HTTP method前缀**. 基于的HTTP method删除`GetList`,`GetAll`,`Get`,`Put`,`Update`,`Delete`,`Remove`,`Create`,`Add`,`Insert`,`Post`和`Patch`前缀, 因此`GetPhones`变为`Phones`, 因为`Get`前缀和GET请求重复. + * 将结果转换为**camelCase**. + * 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称,它将为空,因为`GetPhonesAsync`方法名称将为`phone`. + * 可以通过设置`UrlActionNameNormalizer`选项来自定义.It's an action delegate that is called for every method. +* 如果有另一个带有'Id'后缀的参数,那么它也会作为最终路线段添加到路线中(例如'/phoneId'). + +## 服务选择 + +创建的HTTP API控制器并不是应用服务所独有的功能. + +### IRemoteService 接口 + +如果一个类实现了`IRemoteService`接口, 那么它会被自动选择为API控制器. 由于应用程序服务本身实现了`IRemoteService`接口, 因此它自然就成为API控制器. + +### RemoteService Attribute + +`RemoteService`可用于将实现`IRemoteService`接口的类标记为远程服务或禁用它. 例如: + +````csharp +[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] +public class PersonAppService : ApplicationService +{ + +} +```` + +### TypePredicate 选项 + +你可以通过提供`TypePedicate`选项进一步过滤类以成为API控制器: + +````csharp +services.Configure(options => +{ + options.ConventionalControllers + .Create(typeof(BookStoreApplicationModule).Assembly, opts => + { + opts.TypePredicate = type => { return true; }; + }); +}); +```` + +如果你不想将此类型公开为API控制器, 则可以在类型检查时返回`false`. + +## API Explorer + +API Explorer是可以由客户端获取API结构的服务. Swagger使用它为endpoint创建文档和test UI. + +默认情况下, HTTP API控制器会自动启用API Explorer, 可以使用`RemoteService`按类或方法的级别控制它. 例如: + +````csharp +[RemoteService(IsMetadataEnabled = false)] +public class PersonAppService : ApplicationService +{ + +} +```` + +禁用`IsMetadataEnabled`从而从API Explorer中隐藏此服务, 并且无法被发现. 但是它仍然可以被知道确切API路径/路由的客户端使用. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md index a9e333690f..34acc3f01f 100644 --- a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md +++ b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md @@ -238,7 +238,7 @@ namespace Acme.BookStore 你通常需要创建 **Controllers** 将应用服务暴露为 **HTTP API**.这样浏览器或第三方客户端可以通过AJAX的方式访问它们. -ABP可以 **自动地** 将应用服务转换成MVC API Controllers. +ABP可以 **自动地** (../../AspNetCore/Auto-API-Controllers.md)将应用服务转换成MVC API Controllers. #### Swagger UI diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 6d66e651ec..9e614b1897 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -187,7 +187,13 @@ "text": "ASP.NET Core MVC", "items": [ { - "text": "API 版本控制" + "text": "API", + "items": [ + { + "text": "自动API控制器", + "path": "AspNetCore/Auto-API-Controllers.md" + } + ] }, { "text": "用户界面", diff --git a/docs/zh-Hans/images/bookstore-apis.png b/docs/zh-Hans/images/bookstore-apis.png new file mode 100644 index 0000000000000000000000000000000000000000..aedd79c7f1d9bd6732e7c71ea432c4e3c06e034c GIT binary patch literal 17154 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