Base solution for your next web application
Open Closed

Unable to create multiple OpenApi specifications with Swashbuckle #7415


User avatar
0
mightyit created

I have created two OpenApi specifications (HostApiv1 and TenantApiv1) as follows in Startup.cs:

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");
    //...
});

However, when I decorate my AppService classes with [ApiExplorerSettings(GroupName = "HostApiv1")], the grouping is ignored and the tag (AppService controller), along with all of its operations (actions / methods), still appear under both documents.

Please advise.


4 Answer(s)
  • User Avatar
    0
    ryancyq created
    Support Team

    Hi, have you tried configuring swashbuckle as follows?

    options.DocInclusionPredicate((docName, apiDesc) =>
    {
          if (!apiDesc.ActionDescriptor.IsControllerAction())
          {
               return false;
          }
    
          switch (docName)
          {
               case "v1":
                return apiDesc.GroupName == null || apiDesc.GroupName == "v1";
               case "v2":
                return apiDesc.GroupName == null || apiDesc.GroupName == "v2";
               default:
                return false;
          }
    });
    

    it was discussed on https://github.com/aspnetboilerplate/aspnetboilerplate/issues/3814#issuecomment-418118210

  • User Avatar
    0
    mightyit created

    @ryancyq @ismcagdas That works insofar my swagger documentation is concerned, however, it now seems that whenever an app service is decorated with either [ApiExplorerSettings(GroupName = "HostApiv1")] or [ApiExplorerSettings(GroupName = "TenantApiv1")], my client side dependencies on those services are broken. It seems that somehow AbpServiceProxies has a dependency on Swagger generation?

    See below:

  • User Avatar
    0
    ryancyq created
    Support Team

    @mightyit yes. service proxies are generated via NSwag for angular project.

  • User Avatar
    0
    mightyit created

    Swashbuckle depends on ApiExplorer, and the use of the ApiExplorer attribute limits us to specifying only a single groupname per controller / action. As per @ryancyq above, "service proxies are generated via NSwag for angular project." and it seems that during this process a dependency is broken.

    The workaround was to create my own attribute for delimiting one-or-more groupnames for an appservice controller or action, and subsequently use reflection in my DocInclusionPredicateFunction to retrieve the groupnames for an action or its containing controller.

    [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;
        }
    });
    
    

    Usage

    [DisableAuditing]
    [AbpAuthorize(AppPermissions.HostSpecific.Dashboard.Access)]
    //[ApiExplorerSettings(GroupName = "HostApiv1")]
    [SwaggerDoc("HostApiv1")]
    public class HostDashboardAppService : ZenDetectAppServiceBase, IHostDashboardAppService
    {
            //...
    }