Admin Core

ZhonTai.DynamicApi 动态 Web API 生成组件

ZhonTai.DynamicApi 是一款动态 Web API 生成组件,可将符合约定的应用类直接转换为 RESTful 风格的 API。该组件由 ASP.NET Core MVC 原生调用,全程无 Controller 代码与性能损耗。

简介

ZhonTai.DynamicApi 是一款动态 Web API 生成组件,可将符合约定的应用类直接转换为 RESTful 风格的 API。该组件由 ASP.NET Core MVC 原生调用,全程无 Controller 代码与性能损耗。

组件完美集成 Swagger,能够自动生成与手写 Controller 无任何差异的 API 文档。

应用场景

在 DDD 架构的应用逻辑层中,使用本组件可让应用服务直接暴露为 Web API,彻底省去手动编写 Controller 的重复工作。

配置动态 API

MyApp.Host 项目下的 Program.cs 文件中配置动态 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
new HostApp(new HostAppOptions
{
ConfigureDynamicApi = options =>
{
// 接口以小驼峰方式命名
options.NamingConvention = NamingConventionEnum.CamelCase;

// 指定全局默认的 api 前缀
options.DefaultApiPrefix = "api";

// 清空 API 结尾,不删除 API 结尾
// 若不清空,CreateUserAsync 将变为 CreateUser
options.RemoveActionPostfixes.Clear();

// 自定义 ActionName 处理函数
options.GetRestFulActionName = (actionName) => actionName;

// 指定程序集,配置所有的 api 请求方式都为 POST
options.AddAssemblyOptions(GetType().Assembly, httpVerb: "POST");

// 指定程序集,配置 url 前缀为 api
// 例如: http://localhost:8010/api/User/CreateUser
options.AddAssemblyOptions(GetType().Assembly, apiPreFix: "api");

// 指定程序集,配置 url 前缀为 api,且所有请求方式都为 POST
// 例如: http://localhost:8010/api/User/CreateUser
options.AddAssemblyOptions(GetType().Assembly, apiPreFix: "api", httpVerb: "POST");

// 根据前缀动词生成请求方式
AppConsts.HttpVerbs = new Dictionary<string, string>()
{
["add"] = "POST",
["create"] = "POST",
["insert"] = "POST",
["submit"] = "POST",
["post"] = "POST",

["get"] = "GET",
["find"] = "GET",
["fetch"] = "GET",
["query"] = "GET",

["update"] = "PUT",
["change"] = "PUT",
["put"] = "PUT",
["batch"] = "PUT",

["delete"] = "DELETE",
["soft"] = "DELETE",
["remove"] = "DELETE",
["clear"] = "DELETE",
};
}
}).Run(args);

[!NOTE]
根据前缀动词未匹配到请求方式时,默认使用 HttpPost 方式请求。

NamingConvention 接口命名可选值

命名方式 示例
CamelCase camelCase
PascalCase PascalCase
SnakeCase snake_case
KebabCase kebab-case
ExtensionCase extension.case
Custom 自定义,通过options.GetRestFulActionName 设置

使用动态 API

在服务上增加 [DynamicApi(Area = ApiConsts.AreaName)] 特性,并继承 IDynamicApi 接口即可自动生成动态 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <summary>
/// 模块服务
/// </summary>
[DynamicApi(Area = ApiConsts.AreaName)]
public class ModuleService : BaseService, IModuleService, IDynamicApi
{
/// <summary>
/// 查询分页
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<ModuleListOutput>> GetPageAsync(PageInput<ModuleGetPageDto> input)
{
var key = input.Filter?.Name;

var list = await _moduleRepository.Select
.WhereIf(key.NotNull(), a => a.Name.Contains(key))
.Count(out var total)
.OrderByDescending(true, c => c.Id)
.Page(input.CurrentPage, input.PageSize)
.ToListAsync<ModuleListOutput>();

var data = new PageOutput<ModuleListOutput>()
{
List = list,
Total = total
};

return data;
}
}

[!TIP]
ApiConsts.AreaName 定义 /api/app/module/get 路径中的 app

  • 动态 API 根据前缀动词自动生成请求方式,使用 [HttpPost] 特性可覆盖默认请求方式。
  • 新接口文档地址:http://localhost:8010/admin/index.html
  • Swagger 接口文档地址:http://localhost:8010/admin/swagger/index.html
  • 如果服务是动态 API,开启事务的方法务必定义为 virtual 虚方法,才能正常使用事务拦截。

高级功能

禁用动态 API

在服务方法上添加 [NonAction] 特性,可禁用生成动态 API,变为服务公共方法:

1
2
3
4
5
[NonAction]
public async Task BatchSoftDeleteAsync(long[] ids)
{
await _moduleRepository.SoftDeleteAsync(ids);
}

重命名

在服务方法上添加特性重命名。API 路径需使用绝对路径:

1
2
3
4
5
[HttpPost(template: "/api/[area]/[controller]/[action]")]
public async Task BatchSoftDeleteAsync(long[] ids)
{
await _moduleRepository.SoftDeleteAsync(ids);
}

排序

服务级别排序:

1
2
3
4
5
6
7
8
/// <summary>
/// 模块服务
/// </summary>
[Order(1010)]
[DynamicApi(Area = ApiConsts.AreaName)]
public class ModuleService : BaseService, IModuleService, IDynamicApi
{
}

[!NOTE]
值越小该服务越靠前。

方法级别排序:

1
2
3
4
5
[Order(1010)]
public async Task BatchSoftDeleteAsync(long[] ids)
{
await _moduleRepository.SoftDeleteAsync(ids);
}

[!NOTE]
值越大该服务方法越靠前。

多分组

服务级别分组:

1
2
3
4
5
6
7
/// <summary>
/// 模块服务
/// </summary>
[DynamicApi(Area = ApiConsts.AreaName, GroupNames = new string[] { AdminConsts.AreaName })]
public class ModuleService : BaseService, IModuleService, IDynamicApi
{
}

方法级别分组:

1
2
3
4
5
6
7
[ApiGroup(ApiConsts.AreaName, AdminConsts.AreaName)]
// 或使用 GroupNames 参数
// [ApiGroup(GroupNames = new string[] { ApiConsts.AreaName, AdminConsts.AreaName })]
public async Task BatchSoftDeleteAsync(long[] ids)
{
await _moduleRepository.SoftDeleteAsync(ids);
}

[!TIP]
接口不分组:[ApiGroup(NonGroup = true)]

返回当前数据

添加 [NonFormatResult] 特性可禁用统一包装的数据格式,直接返回当前数据:

1
2
3
4
5
6
7
8
9
10
// 统一包装格式
{
"success": true,
"code": null,
"msg": null,
"data": 1
}

// 使用 NonFormatResult 后直接返回
1
1
2
3
4
5
6
7
[NonFormatResult]
public async Task<long> AddAsync(ModuleAddInput input)
{
var entity = input.Adapt<ModuleEntity>();
await _moduleRepository.InsertAsync(entity);
return entity.Id;
}

授权(v8.3.0+)

在服务方法上添加 [ApiAccess("权限点编码")] 特性:

1
2
3
4
5
[ApiAccess("admin:dict:export")]
public async Task<ActionResult> ExportListAsync()
{
// ...
}

[!TIP]

  • [ApiAccess("权限点编码1", "权限点编码2", All = true)]
  • 权限点编码可设置一个或多个
  • All 默认为 false,表示满足任意一个权限点编码即可访问
  • 设置为 true 时,需要全部满足才可访问

隐藏 Swagger 文档

添加 [ApiExplorerSettings(IgnoreApi = true)] 特性,接口不会在文档中显示:

1
2
3
4
5
[ApiExplorerSettings(IgnoreApi = true)]
public async Task GetInternalDataForPublicAsync()
{
// ...
}

禁用操作日志

添加 [NoOperationLog] 特性,接口不会被记录操作日志:

1
2
3
4
5
[NoOperationLog]
public async Task GetDataListAsync()
{
// ...
}

#中台 #中台/特性注解 #中台/搭建项目框架 #中台/.NET模板 #中台/分布式微服务