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?
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
{
//...
}