首页 > 解决方案 > 无法使用 Swashbuckle 创建多个 OpenApi 规范

问题描述

我正在使用 Asp.Net Boilerplate / Asp.Net Zero 构建解决方案

我在 Startup.cs 中创建了两个 OpenApi 规范(HostApiv1TenantApiv1),如下所示:

services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("HostApiv1", new Info { Title = "Host API v1", Version = "v1" });
    options.SwaggerDoc("TenantApiv1", new Info { Title = "Tenant API v1", Version = "v1" });

    options.DocInclusionPredicate((docName, description) => true);
    options.IgnoreObsoleteActions();
    options.IgnoreObsoleteProperties();
    options.OrderActionsBy((apiDesc) => $"{apiDesc.RelativePath}");
    options.DescribeAllEnumsAsStrings();
});
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint(_appConfiguration["App:HostApiSwaggerEndPoint"], "Host API v1");
    options.SwaggerEndpoint(_appConfiguration["App:TenantApiSwaggerEndPoint"], "Tenant API v1");
    //...
});

但是,当我用 装饰我的 AppService 类时[ApiExplorerSettings(GroupName = "HostApiv1")],分组将被忽略,并且标记(AppService 控制器)及其所有操作(操作/方法)仍然出现在两个文档下。

知道出了什么问题,或者我该如何调试它?

标签: asp.net-coreswagger-uiswagger-2.0aspnetboilerplateswashbuckle

解决方案


Swashbuckle 依赖于ApiExplorer,并且该ApiExplorer属性的使用将我们限制为每个控制器/操作仅指定一个组名。ABP 服务代理是通过 NSwag 为 Angular 项目生成的,似乎在这个过程中,依赖关系被破坏了。

解决方法是为应用服务控制器或操作创建一个自定义Attribute来分隔一个或多个组名,然后在DocInclusionPredicateFunction选项中使用反射来检索操作或其包含控制器的组名。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class SwaggerDocAttribute: Attribute
{
    public SwaggerDocAttribute(params string[] includeInDocuments)
    {
        IncludeInDocuments = includeInDocuments;
    }

    public string[] IncludeInDocuments { get; }
}

options.DocInclusionPredicate((docName, apiDesc) =>
{
    if (!apiDesc.ActionDescriptor.IsControllerAction())
    {
        return false;
    }

    apiDesc.TryGetMethodInfo(out MethodInfo methodInfo);

    var actionDocs = methodInfo.GetCustomAttributes<SwaggerDocAttribute>()
        .SelectMany(a => a.IncludeInDocuments);

    var controllerDocs = methodInfo.DeclaringType.GetCustomAttributes<SwaggerDocAttribute>()
        .SelectMany(a => a.IncludeInDocuments);

    switch (docName)
    {
        case "HostApiv1":
            return apiDesc.GroupName == null || 
            actionDocs.Contains("HostApiv1") || 
            controllerDocs.Contains("HostApiv1");
        case "TenantApiv1":
            return apiDesc.GroupName == null ||
            actionDocs.Contains("TenantApiv1") || 
            controllerDocs.Contains("TenantApiv1");
        default:
            return true;
    }
});

用法

[DisableAuditing]
[AbpAuthorize(AppPermissions.HostSpecific.Dashboard.Access)]
//[ApiExplorerSettings(GroupName = "HostApiv1")] // <== Don't use this
[SwaggerDoc("HostApiv1")] // <== Use this in stead
public class MyDemoAppService : ZenDetectAppServiceBase, IHostDashboardAppService
{
        //...
}

推荐阅读