0

I am building a solution with Asp.Net Boilerplate / Asp.Net Zero

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.

Any idea what is wrong, or how I can debug it?

1 Answer 1

1

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

The workaround is to create a custom Attribute for delimiting one-or-more groupnames for an appservice controller or action, and subsequently use reflection in the DocInclusionPredicateFunction option 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")] // <== Don't use this
[SwaggerDoc("HostApiv1")] // <== Use this in stead
public class MyDemoAppService : ZenDetectAppServiceBase, IHostDashboardAppService
{
        //...
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.