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