5. 动态 WebAPI
5.1 什么是控制器
简单来说,控制器是一个承上启下的作用,根据用户输入,执行响应行为(动作方法),同时在行为中调用模型的业务逻辑,返回给用户结果(视图)。

在 ASP.NET Core 中,控制器有两种表现形式:
Mvc(带视图)WebAPI(RESTful API)
- Mvc 控制器
- WebAPI 控制器
Mvc 控制器和 WebAPI 控制器最大的区别是 WebAPI 控制器不带 视图 和通过 请求谓词和路由地址响应行为。
5.2 Mvc 控制器 约定和缺点
在学习动态 WebAPI 控制器之前,首先了解 ASP.NET Core 中 WebAPI 的一些约定和注意事项。
5.2.1 WebAPI 约定
在 ASP.NET Core 应用中,一个 WebAPI 控制器需遵循以下约定:
- 控制器类必须继承
ControllerBase或间接继承 - 动作方法必须贴有
[HttpMethod]特性,如:[HttpGet] - 控制器或动作方法至少有一个配置
[Route]特性 - 生成
WebAPI路由地址时会自动去掉控制器名称Controller后缀,同时也会去掉动作方法匹配的HttpVerb谓词,如GET,POST,DELETE,PUT等 - 不支持返回非
IEnumerable<T>泛型对象 - 不支持类类型参数在
GET,HEAD请求下生成Query参数
除了上述约定外,WebAPI 路由地址基本靠手工完成,不利于书写,不利于维护,再者,在移动应用对接中难以进行多版本控制。
5.2.2 .NET Core WebAPI 缺点
通过上一章节可以看出,ASP.NET Core 应用实现 WebAPI 需要遵循种种约定,而且容易出错。
除了这些约定,.NET Core WebAPI 有以下缺点:
- 路由地址基本靠手工完成
- 在现在移动为王的时代,不利于进行多版本控制
- 对接
Swagger文档分组比较复杂 - 实现
Policy策略授权也比较复杂 - 不支持控制器热拔插插件化
- 难以实现复杂自定义的
RESTful API风格
5.3 动态 WebAPI 控制器
针对以上 ASP.NET Core 提供的 WebAPI 必须遵循的约定和不可避免的缺点,Fur 框架创造出一种更加灵活创建 WebAPI 控制器的方式。
这个方式在继承了 ASP.NET Core WebAPI 所有优点,同时进行了大量拓展和优化。优化后的 WebAPI 具有以下优点:
- 具备原有的
ControllerBase所有功能 - 支持任意公开 非静态 非抽象 非泛型类转控制器
- 提供更加灵活方便的
IDynamicApiController空接口或[DynamicApiController]特性替代ControllerBase抽象类 - 无需手动配置
[HttpMethod]特性,同时支持一个动作方法多个HttpVerb - 无需手动配置
[Route]特性,支持更加灵活的配置及自动路由生成 - 支持返回泛型接口,泛型类
- 和
Swagger深度结合,提供极其方便的创建Swagger分组配置 - 支持
Basic Auth,Jwt,ApiKey等多种权限灵活配置 - 支持控制器、动作方法版本控制功能
- 支持
GET、HEAD请求自动转换类类型参数 - 支持生成
OAS3接口规范
5.4 注册动态 WebAPI 服务
备注
.AddDynamicApiControllers() 默认已经集成在 AddInject() 中了,无需再次注册。也就是下列代码可不配置。
特别注意
.AddDynamicApiControllers() 必须在 services.AddControllers() 之后注册。
5.5 第一个例子
创建一个 FurAppService 类继承 IDynamicApiController 接口 或 贴 [DynamicApiController] 特性,并在这个类中编写一个 Get 方法。
IDynamicApiController方式
[DynamicApiController]方式
如下图所示,一个 WebAPI 接口就这么生成了。

5.6 动态 WebAPI 原理解析
5.6.1 控制器特性提供器
Fur 框架会在应用启动时注册 DynamicApiControllerFeatureProvider 控制器特性提供器,该提供器继承自 ControllerFeatureProvider 类。
接着重写 bool IsController(TypeInfo typeInfo) 方法,用来标识控制器类型。在 Fur 框架中,继承自 ControllerBase 类或 IDynamicApiController 接口或 [DynamicApiController] 特性都会被标记为控制器类型。
5.6.2 应用模型转换器
Fur 框架同时在应用启动时注册 DynamicApiControllerApplicationModelConvention 应用模型转换器,该转换器继承自 IApplicationModelConvention 接口。
接着实现 void Apply(ApplicationModel application) 接口方法。在该方法中配置控制器名称、路由、导出可见性及动作方法名称、路由、导出可见性等。
实际上该方法做的就是按照 WebAPI 约定 提前帮我们配置好路由、请求谓词等信息。避免了手动配置的同时还增加了许多新特性,如版本控制。
5.7 动态 WebAPI 配置约定
5.7.1 控制器默认约定
- 生成控制器名称默认去除以
AppServices,AppService,ApiController,Controller,Services,Service作为前后缀的字符串。见第一个例子中的FurAppService -> Fur支持自定义配置 - 控制器名称带
V[0-9_]结尾的,会自动生成控制器版本号,如FurAppServiceV2 -> Fur@2,FurAppServiceV1_1_0 -> Fur@1.1.0。支持版本分隔符配置 - 控制名称以
骆驼命名(CamelCase)会自动切割成多个单词-连接。支持自定义配置
5.7.2 动作方法默认约定
- 生成的动作方法名称默认去除以
Post/Add/Create/Insert/Submit,GetAll/GetList/Get/Find/Fetch/Query/Search,Put/Update,Delete/Remove/Clear,Patch开头的字符串。支持自定义配置 - 生成的动作方法名称默认去除以
Async作为前后缀的字符串。支持自定义配置 - 动作方法名称带
V[0-9_]结尾的,会自动生成动作方法版本号,如ChangePasswordV2 -> ChangePassword@2,ChangePasswordV1_1_0 -> ChangePassword@1.1.0。支持版本分隔符配置 - 动作方法名称以
骆驼命名(CamelCase)会自动切割成多个单词-连接。支持自定义配置 - 动作方法参数将自动转为小写。支持自定义配置
5.7.3 请求谓词默认约定
- 动作方法名
- 以
Post/Add/Create/Insert/Submit开头,则添加[HttpPost]特性。 - 以
GetAll/GetList/Get/Find/Fetch/Query开头,则添加[HttpGet]特性。 - 以
Put/Update开头,则添加[HttpPut]特性。 - 以
Delete/Remove/Clear开头,则添加[HttpDelete]特性。 - 以
Patch开头,则添加[HttpPatch]特性 - 支持自定义配置
- 以
- 如果不在上面约定中,则默认添加
[HttpPost]特性。支持自定义配置
5.7.4 路由地址默认约定
- 默认以
api开头。支持自定义配置 - 默认转换为小写路由地址。支持自定义配置
- 生成控制器路由模板格式为:
api/前置参数列表/模块名或默认区域名/[controller@版本号]/后置参数列表 - 生成动作方法路由模板格式为:
前置参数列表/模块名/[action@版本号]/后置参数列表
5.7.5 其他约定
- 默认不处理
ControllerBase控制器类型。支持自定义配置 - 默认不处理
GET,HEAD请求的引用类型参数。支持自定义配置
5.8 更多例子
5.8.1 多种请求谓词方法
如下图所示:

5.8.2 多个自定义动作方法
如下图所示:

5.8.3 带参数动作方法
如下图所示:

5.8.5 GET/HEAD 类类型参数
默认情况下,ASP.NET Core 会将 GET/HEAD 请求中的 类类型参数 设置为 [FromBody] 绑定,如:
如下图所示:

但是,GET、HEAD 请求不支持 From Body 绑定。所以我们需要转换为 Query 查询参数。
Fur 框架支持以下两种方式配置:
- [FromQuery] 特性
- 配置 DynamicApiControllerSettings
如下图所示:

5.8.6 自定义参数位置
Fur 框架提供了非常方便的自定义参数位置的特性 [ApiSeat],通过 [ApiSeat] 可配置参数位置,支持以下四种位置:
ApiSeats.ControllerStart:控制器之前ApiSeats.ControllerEnd:控制器之后ApiSeats.ActionStart:动作方法之前ApiSeats.ActionEnd:动作方法之后。默认值
如下图所示:

温馨提示
多个 同位置 配置的参数将按照 定义参数顺序 进行排序。
特别注意
[ApiSeat] 只能应用于贴了 [FromRoute] 特性的参数或 基元类型、值类型、可空基元类型和可空值类型。
5.8.7 自定义请求谓词
如下图所示:

5.8.8 支持多个谓词
如下图所示:

特别注意
如果动作方法中含有 类类型参数,且含有 POST/PUT/DELETE 任意请求谓词,那么该参数会自动添加 [FromBody] 参数,即使在 GET/HEAD 请求中不支持。
5.8.9 支持自定义路由
支持控制器和动作方法自定义路由:
- 自定义控制器路由
- 自定义动作方法路由
- 同时自定义路由
- 谓词自定义路由
如下图所示:

小提示
动态方法自定义路由如果以 / 开头,则不会合并控制器路由。
推荐配置
自定义路由如果需要用到 控制器/动作方法名称,推荐使用 [controller] 或 [action] 占位符,因为该占位符已经自动处理了 前后缀、版本号、模块名称等。
5.8.10 多路由随意组合
Fur 框架提供了非常灵活的各种路由组合方式,支持一对多,多对多路由组合:
如下图所示:

特别注意
动作方法不能同时贴 [Route] 和 [HttpMethod] 特性,只能二取一。
5.8.11 支持版本控制
- 控制器版本
- 动作方法版本
如下图所示:

版本生成原理
V[0-9_] 结尾的命名自动解析成版本号,如 FurAppServiceV2 -> Fur@2。
版本复写
除了通过特定后缀方式以外,版本还直接通过 [ApiDescriptionSettings] 进行复写。如:
这时,生成版本将采用 4.0 替代 1
5.8.12 不公开控制器或动作方法
有些时候,我们无需导出某个动作方法或控制器,只需要添加 [ApiDescriptionSettings(false)] 或 [ApiExplorerSettings(IgnoreApi = true)]即可。
另外动作方法还支持 [NonAction] 标记。
推荐使用
推荐控制器或动作方法设置不导出使用 [ApiDescriptionSettings(false)] 特性。该特性默认继承自 ApiExplorerSettingsAttribute 类。
5.9 [ApiDescriptionSettings]
除了上述 ASP.NET Core 提供的配置外,Fur 框架还提供了非常强大且灵活的 [ApiDescriptionSettings] 特性。
5.9.1 内置配置
Name:自定义控制器/动作方法名称,string,默认nullKeepName:是否保持原有名称不处理,bool,默认falseSplitCamelCase:切割骆驼命名,bool,默认trueKeepVerb:是否保留动作方法请求谓词,bool,默认falseEnabled:是否导出接口,bool,默认trueModule:模块名,string,默认nullVersion:版本号,string,默认nullGroups:接口分组,可结合Swagger一起使用,string[],默认nullTags:接口标签,可结合Swagger一起使用,string[],默认nullAuthPolicies:授权策略,可结合授权,JWT一起使用,string[],默认null
5.9.2 Name 配置
Name 参数可以覆盖动态 WebAPI 自动生成的控制器或动作方法名称。如:
如下图所示:

5.9.3 KeepName 配置
KeepName 参数可以保留原有的控制器或动作方法名称。如:
如下图所示:

5.9.4 SplitCamelCase 配置
SplitCamelCase 参数默认将骆驼命名切割成多个单词并通过指定 占位符 连接起来。默认 占位符 为 -。默认为 true。如:
如下图所示:

特别注意
KeepName 优先级高于 SplitCamelCase,也就是 KeepName 设置为 true,则不会处理 SplitCamelCase 参数。
5.9.5 KeepVerb 配置
KeepVerb 参数作用于动作方法,标识是否保留动作谓词。如:
如下图所示:

5.9.6 Enabled 配置
Enabled 参数配置接口是否导出。通常用于动作方法,如果用于控制器实际作用不大。
如下图所示:

5.9.7 Module 配置
Module 参数可以配置路由分离,类似于 Mvc 区域 的作用。
如下图所示:

5.9.8 Version 配置
Version 参数可以配置接口版本,同时又可以复写特殊版本命名配置。默认版本号分隔符为 @。如:
如下图所示:

5.9.9 Groups 配置
Groups 配置主要用于配置 Swagger 分组信息。
通过配置 Groups 参数可以将控制器和动作方法 进行归类和多个分组直接共享。可通过 [ApiDescriptionSettings(params Groups)] 构造函数传入或指定 Groups 参数配置接口是否导出。通常用于动作方法,如果用于控制器实际作用不大。
如下图所示:

5.9.10 Tag 配置
Tag 配置主要用于配置 Swagger 标签分组信息及合并标签。也就是 组中组:
- 标签命名
- 合并标签
未贴标签之前
贴标签之后
如下图所示:

小知识
如果 Tag 名字一样,则会自动合并,否则只是命名。
5.10 DynamicApiControllerSettings 配置
Fur 还提供动态 WebAPI 接口一些全局配置选项,如:
DefaultRoutePrefix:默认路由前缀,string,默认apiDefaultHttpMethod:默认请求谓词,string,默认:POSTDefaultModule:默认模块名称(区域),可用作接口版本,string,默认:v1LowercaseRoute:小写路由格式,bool,默认:trueKeepVerb:是否保留动作谓词,bool,默认:falseKeepName:是非保留默认名称,bool,默认:fasleCamelCaseSeparator:骆驼命名分隔符,string,默认:-VersionSeparator:版本分隔符,string,默认:@ModelToQuery:GET/HEAD请求将类类型参数转查询参数,bool,默认falseSupportedMvcController:是否支持Mvc Controller动态配置,bool,默认falseAbandonControllerAffixes:默认去除控制器名称前后缀列表名,string[],默认:AppServicesAppServiceApiControllerControllerServicesService
AbandonActionAffixes:默认去除动作方法名称前后缀列表名,string[],默认:Async
5.10.1 支持 Mvc 控制器 动态配置
默认情况下,Fur 动态 WebAPI 接口不对 ControllerBase 类型进行任何处理。当然,我们也可以手动启用 ControllerBase 支持。
设置 SupportedMvcController: true 后,Mvc ControllerBase 类型也能和动态 WebAPI 一样的灵活了。代码如下:
注意事项
启用该配置后,如果 Mvc 控制器 没有任何 [Route] 特性,但是贴了 [ApiController] 特性将会报错。原因是 [ApiController] 特性内部做了路由特性检测。所以建议使用 [ApiDataValidation] 代替。
5.11 反馈与建议
与我们交流
给 Fur 提 Issue。