.Net6 Swagger文档设置同时支持 自定义分组+Api版本分组
.Net6 Swagger文档设置同时支持 自定义分组+Api版本分组
一叶舟轻

.Net6 Swagger文档设置同时支持 自定义分组+Api版本分组

一叶舟轻
2023-10-15 / 0 评论 / 22 阅读 / 正在检测是否收录...

.NET webApi 中Swagger默认是不会进行任何分组的,就只是罗列出来罢了。
这样当用的时间长了,api变多了,项目变得复杂了,看起来就会很难受了,这时候对swagger的分组就变得很有必要了。

网上的浏览下来,主要有两种分组方式:自定义分组、通过版本控制分组
lnrh4p1m.png
lnrh527g.png
但这两种情况实际上在开发中都会出现,所以就在想要怎么才能同时满足两种分组方式,但实际上这两种方式都需要用到GroupName参数,其中一个总会覆盖另一个,网上也没有搜到现成的,只有自己动手撸了 😅 。

(纯粹自己瞎整出来的,不一定是最佳方案,可以当作一种方向参考,也欢迎评论区讨论)

先看最终显示效果吧:
lnrhamqu.png
这里可以看到每个文档的分组条件格式是【自定义】:版本号,是不是看着还行 😋
虽然下拉框看着挺奇怪的,但在不改动前端的情况下也就只能先这么整了 😅 。


下面直接看代码吧~

首先是Swagger的配置信息

这里定义了一个自定义分组Config,其中第一个是 默认分组,给那些不设置自定义分组的api留的,这个是必须要有的,后面的自己根据实际设置就行

//Swagger 文档配置
"Swagger": {
    "Doc": {
        "Content": {
            "Name": "workbench-for-mr.liu",
            "Email": "---"
        },
        "Description": ".NET6 WebAPI 文档 By workbench",
        "Title": ".NET6 WebAPI 文档"
    },
    //自定义分组
    "Config": [
        {
            "GroupName": "",
            "Title": "【默认分组】"
        },
        {
            "GroupName": "Open",
            "Title": "【开放接口】"
        },
        {
            "GroupName": "Test",
            "Title": "【内部测试】"
        }
    ]
}

Program.cs配置Swgger

根据前一步配置的自定义分组和api的版本控制(并过滤掉不需要的分组)生成Swagger文档

//Swagger
builder.Services.AddSwaggerGen(c =>
{
    #region 文档分组(自定义+版本控制)
    //根据API版本信息生成的API文档
    var provider = builder.Services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
    var _config = AppConfig.Get_appsettting<List<SwaggerConfig.groupConfig>>("Swagger:Config");
    //当前所有的api
    var _apiDescriptionsProvider = builder.Services.BuildServiceProvider().GetService<Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupCollectionProvider>();
    var appApis = _apiDescriptionsProvider.ApiDescriptionGroups.Items
        .SelectMany(group => group.Items);

    //自定义分组
    _config.ForEach(group =>
     {
         //版本号分组
         foreach (var description in provider.ApiVersionDescriptions)
         {
             //是否存在符合的api
             var _api = appApis.Where(x => ((group.GroupName.Equals("") && !x.RelativePath.Contains("{GName-")) || (!group.GroupName.Equals("") && x.RelativePath.Contains($"GName-{group.GroupName}"))) && description.GroupName.Equals(x.GroupName));
             if (_api.Count() > 0)
             {
                 string _GName = (string.IsNullOrWhiteSpace(group.GroupName) ? "" : group.GroupName + ":") + description.GroupName;

                 c.SwaggerDoc(_GName, new OpenApiInfo
                 {
                     Contact = new OpenApiContact
                     {
                         Name = AppConfig.Get_appsettting("Swagger:Doc:Content:Name"),
                         Email = AppConfig.Get_appsettting("Swagger:Doc:Content:Email")
                     },
                     Description = AppConfig.Get_appsettting("Swagger:Doc:Description"),
                     Title = group.Title,
                     Version = "v" + description.ApiVersion.ToString()
                 });
             }

         }
     });

    //在Swagger文档显示的API地址中将版本信息参数替换为实际的版本号
    c.DocInclusionPredicate((version, apiDescription) =>
    {
        //= "api/{GName-Sys}/v{version}/WeatherForecast"
        var _GName = _config.Where(x => !string.IsNullOrWhiteSpace(x.GroupName) && apiDescription.RelativePath.Contains(x.GroupName)).FirstOrDefault()?.GroupName;
        var _GName2 = apiDescription.GroupName;
        if (!string.IsNullOrWhiteSpace(_GName))
            _GName2 = _GName + ":" + _GName2;

        if (!version.Equals(_GName2))
            return false;

        IEnumerable<string>? values = apiDescription!.RelativePath.Split('/').Select(x => Regex.Replace(x.Replace("v{version}", apiDescription.GroupName), "\\{GName\\-[a-zA-Z0-9]+\\}", _GName ?? "")).Where(x => !string.IsNullOrWhiteSpace(x));
        apiDescription.RelativePath = String.Join("/", values);
        return true;
    });
    #endregion

SwaggerUI 也需要同时设置

   app.UseSwaggerUI(c =>
   {
       #region 文档分组(自定义+版本控制)
       var provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
       var _config = AppConfig.Get_appsettting<List<SwaggerConfig.groupConfig>>("Swagger:Config");
       //当前所有的api
       var _apiDescriptionsProvider = builder.Services.BuildServiceProvider().GetService<Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupCollectionProvider>();
       var appApis = _apiDescriptionsProvider.ApiDescriptionGroups.Items
           .SelectMany(group => group.Items);

       _config.ForEach(group =>
       {
           foreach (var description in provider.ApiVersionDescriptions.Reverse())
           {
               //是否存在符合的api
               var _api = appApis.Where(x => ((group.GroupName.Equals("") && !x.RelativePath.Contains("{GName-")) || (!group.GroupName.Equals("") && x.RelativePath.Contains($"GName-{group.GroupName}"))) && description.GroupName.Equals(x.GroupName));
               if (_api.Count() > 0)
               {
                   c.SwaggerEndpoint($"/swagger/{(string.IsNullOrWhiteSpace(group.GroupName) ? "" : group.GroupName + ":") + description.GroupName}/swagger.json", group.Title + ":" + description.GroupName);  //分组显示
               }
           }
       });
       #endregion
   });

最后看看结果吧

使用方式,[ApiVersion("0")]不变,和之前一样就行,至于[ApiExplorerSettings(GroupName ="")]由于会和API版本控制冲突,所以这个就不能用了,改成直接在路由前面加上特殊识别内容{GName-Test}/,其中Test需要和前面设置的 自定义分组 配置项的GroupName一致才行,完整情况就像下面这样:

    /// <summary>
    /// 示例控制器
    /// </summary>
    [Route("{GName-Test}/v{version:apiVersion}/[controller]")]
    [ApiController]
    [ApiVersion("0")]
    //[ApiExplorerSettings(GroupName ="")]
    public class WeatherForecastController : ControllerBase
    {
        [HttpGet]
        public ReturnInfo get()
        {
            return new ReturnInfo() { message="okok!"};
        }
        
        [HttpGet("ssssss")]
        [ApiVersion("3")]
        public ReturnInfo hhh()
        {
            return new ReturnInfo() { message="okok!"};
        }
    }

可以看到,这里有两个api,其中一个将版本设置成了v3,Controller上添加了自定义分组Test,所以等会就会分成Test:v0Test:v3两个文档
lnripr99.png

但是,请求路径会多带一个Test,也会多出一个路径参数
lnriv6sx.png
第一个/Test/,必须要留着(其实看着条理也挺清晰的,留着也还不错)
第二个是路径参数,是因为我前面写的{}的关系,其实直接去掉就行了(但是我还是想跟正常的部分有个区分),也可以像下面这样,直接删除GName路径参数就行了(由于这是之前版本控制就配置好了的,所以直接就复制一份改改就行了)

/// <summary>
/// 取消swagger文档需要输入版本信息
/// </summary>
public class RemoveVersionFromParameter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var versionParameter = operation.Parameters.FirstOrDefault(p => p.Name == "version");
        if (versionParameter != null)
        {
            operation.Parameters.Remove(versionParameter);
        }

        //也删掉GName路径参数
        var versionParameter2 = operation.Parameters.FirstOrDefault(p => p.Name.Contains("gName"));
        if (versionParameter2 != null)
        {
            operation.Parameters.Remove(versionParameter2);
        }
    }
}

这时候再看也就没有多余的参数了,请求也是没问题的
lnrj6o4s.png

完事,大概就是这样 😏

0

评论 (0)

取消