为控制器生成OpenAPI注释

发布时间 2023-07-04 14:30:22作者: 波多尔斯基

非常喜欢. NET 的 /// 注释,写代码的时候就顺道完成写文档的过程,简直不要太爽了。 ASP. NET CORE 也是一样的,通过 Swagger 工具,可以自动生成 API 的接口文档(OpenAPI规范),提供给前端使用,也可以用过 APIPOST/APIFOX 之类的工具提供给前端同学直接调用。

生成 OpenAPI 注释

只需要安装 swashbuckle.aspnetcore 包,在项目上设置生成 XML 格式的注释,并且如下配置即可自动生成 OpenAPI 的文档,对我这个例子,可以通过 swagger/v{version}/swagger.json 访问。

            services.AddSwaggerGen(options =>
            {
                // options.CustomSchemaIds(type => type.AssemblyQualifiedName);
                var fileName = Assembly.GetExecutingAssembly().GetName().Name + ".xml";
                var filePath = Path.Combine(AppContext.BaseDirectory, fileName);

                // integrate xml comments
                options.IncludeXmlComments(filePath);
            });


                app.UseSwagger();
                app.UseSwaggerUI(
                    options =>
                    {
                        foreach (var description in app.DescribeApiVersions())
                        {
                            var url = $"/swagger/{description.GroupName}/swagger.json";
                            var name = description.GroupName.ToUpperInvariant();
                            options.SwaggerEndpoint(url, name);
                        }
                    });

注释如下:

    /// <summary>
    /// 这个接口
    /// </summary>
    public class CoverageDataController : ODataController
	{
        /// <summary>
        /// 获取盖度数据
        /// </summary>
        /// <param name="key"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        [HttpGet]
        [ProducesResponseType(typeof(CoverageDataDto), Status200OK)]
        public async Task<IActionResult> Get(string key, ODataQueryOptions<CoverageDataDto> options)
        {
        }
    }

生成 Tags 注释

在使用 APIFOX 导入 swagger.json 导入时,我发现,对每一个 path 的注释能够正常显示,但是对的控制器的注释不能正常被识别。

image

查看生成的 swagger.json,这个 CoverageData 被解释成了 OpenAPI 的 Tags,那对应控制器的相关注释,是需要使用另外的标注实现的,而不能直接使用///的注释实现。

paths": {
        "/api/v{version}/CoverageData({key})": {
            "get": {
                "tags": [
                    "CoverageData"
                ],
                "summary": "获取盖度数据",

安装的新的包 swashbuckle.aspnetcore.annotations,然后增加启用语句,如下:

            services.AddSwaggerGen(options =>
            {
                // options.CustomSchemaIds(type => type.AssemblyQualifiedName);
                var fileName = Assembly.GetExecutingAssembly().GetName().Name + ".xml";
                var filePath = Path.Combine(AppContext.BaseDirectory, fileName);

                // integrate xml comments
                options.IncludeXmlComments(filePath);
                options.EnableAnnotations();
            });

在控制器的声明上面,添加 [SwaggerTag("接受盖度数据")] 注解:

    /// <summary>
    /// 这个接口
    /// </summary>
    [SwaggerTag("接受盖度数据")]
    public class CoverageDataController : ODataController
	{
    }

最后生成的 swagger.json 文件在末尾多了几行:

    "tags": [
        {
            "name": "CoverageData",
            "description": "接受盖度数据"
        }
    ]

Swagger 里面就可以看到注释了:

image

但是导入到 APIFOX 中,显示的组别名称依然是 CoverageData ,没有达到我想要的效果,我想将其替换成可以显示友好的名称。实质上是为 CoverageData 取一个别名。

注:这种方法不能与 swagger 配置的 TagActionsBy 方法的一起使用。

Tags 注解

在 ASP. NET CORE 中,可以在控制器上使用 [Tags("盖度接口")],对控制器的组别进行标注。这样生成的 tag 名称直接就换成了的中文名称。

"paths": {
        "/api/v{version}/CoverageData({key})": {
            "get": {
                "tags": [
                    "盖度接口"
                ],
                "summary": "获取盖度数据",

....

    "tags": [
        {
            "name": "CoverageData",
            "description": "接受盖度数据"
        }
    ]

但是 swagger 变得非常奇怪:

image

出现了两个不同的 tag,其中 CoverageData 名称的下面没有从属的 api。

如果没有对 Tag 写 description 的要求,那么使用这个方案是最简单的:设置[Tags],不要设置[SwaggerTag]。

DisplayName 注解

这么看应该是通过 swagger 生成的 tag 与通过 [Tags] 注解生成的 tag 对象不能匹配,导致 swagger 生成的没用被引用。

查了很久资料,说这个是一个现在的 Bug,有人通过重写 DisplayName,在帖子中给了临时的解决方案

  1. 先增加一个新的类型。
/// <summary>
/// Uses the [DisplayName] attribute as the Controller name in OpenAPI spec and Swagger/ReDoc UI if available else the default Controller name.
/// </summary>
public class ControllerDocumentationConvention : IControllerModelConvention
{
    void IControllerModelConvention.Apply(ControllerModel controller)
    {
        if (controller == null)
        {
            return;
        }
        
        foreach (var attribute in controller.Attributes)
        {
            if (attribute is DisplayNameAttribute displayNameAttribute && !string.IsNullOrWhiteSpace(displayNameAttribute.DisplayName))
            {
                controller.ControllerName = displayNameAttribute.DisplayName;
            }
        }
    }
}
  1. 给 Controller 配置这个命名转换。
services.AddControllers(o =>
{
   o.Conventions.Add(new ControllerDocumentationConvention());
});
  1. 在需要调整名称的控制器上添加 [DisplayName("targetNames")] 即可。可以看到名称与注释都得到的保留,最终效果如下:

image

导入 APIFOX 也可以正常识别了。

image

参考资料