Reputation: 7987
I'm using Asp.Net Core 3.1 to build my API. I'm using swagger to generate document for my API. I decided to do grouping on my swagger document based on controller. So I ended up doing like this,
Startup - ConfigureServices:
options.SwaggerDoc(
"LibraryOpenAPISpecificationCategories",
...
Startup - Configure:
options.SwaggerEndpoint(
"/swagger/LibraryOpenAPISpecificationCategories/swagger.json",
"Library API (Categories)");
Controller:
[Route("api/categories")]
[ApiController]
[ApiExplorerSettings(GroupName = "LibraryOpenAPISpecificationCategories")]
public class CategoriesController : ControllerBase
Until this point everything was working fine. When I added versioning the Swagger document stopped displaying the methods in the controller. I was trying to bring grouping inside version so that each version will have the groups like,
V1 -> LibraryOpenAPISpecificationCategories
V1 -> LibraryOpenAPISpecificationItems
V2 -> LibraryOpenAPISpecificationCategories
V2 -> LibraryOpenAPISpecificationItems
Here is what I did,
Startup - ConfigureServices:
services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VV";
});
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ReportApiVersions = true;
});
var apiVersionDescriptionProvider =
services.BuildServiceProvider().GetService<IApiVersionDescriptionProvider>();
services.AddSwaggerGen(options =>
{
foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
{
options.SwaggerDoc(
$"LibraryOpenAPISpecificationCategories{description.GroupName}",
...
Startup - Configure:
app.UseSwaggerUI(options =>
{
foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
{
options.SwaggerEndpoint(
$"/swagger/LibraryOpenAPISpecificationCategories{description.GroupName}/swagger.json",
$"Library API (Categories) {description.GroupName.ToUpperInvariant()}");
Controller:
[Route("api/categories")]
[ApiController]
[ApiExplorerSettings(GroupName = "LibraryOpenAPISpecificationCategories")]
public class CategoriesController : ControllerBase
No error is displayed in swagger document. Please assist me on where I'm going wrong. Am I missing anything?
Upvotes: 5
Views: 5332
Reputation: 1
builder.Services.AddApiVersioning( options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion( 1, 0 );
} )
.AddMvc()
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
options.FormatGroupName = ( group, version ) => $"{group} - {version}";
} );
try this one. "FormatGroupName" is the trick you need
Upvotes: 0
Reputation: 4438
Since a few people asked for this in various places, here's how you'd implement a custom IApiDescriptionProvider. It simply updates the ApiDescription.GroupName
at the end of processing. This will work completely independent of Swashbuckle or any other OpenAPI/Swagger document generator:
public class CollateApiDescriptionProvider : IApiDescriptionProvider
{
readonly IOptions<ApiExplorerOptions> options;
public CollateApiDescriptionProvider( IOptions<ApiExplorerOptions> options ) =>
this.options = options;
public int Order => 0;
public void OnProvidersExecuting( ApiDescriptionProviderContext context ) { }
public void OnProvidersExecuted( ApiDescriptionProviderContext context )
{
var results = context.Results;
var format = options.Value.GroupNameFormat;
var text = new StringBuilder();
for ( var i = 0; i < results.Count; i++ )
{
var result = results[i];
var action = result.ActionDescriptor;
var version = result.GetApiVersion();
var groupName = action.GetProperty<ApiDescriptionActionData>()?.GroupName;
text.Clear();
// add the formatted API version according to the configuration
text.Append( version.ToString( format, null ) )
// if there's a group name, prepend it
if ( !string.IsNullOrEmpty( groupName ) )
{
text.Insert( 0, ' ' );
text.Insert( 0, groupName );
}
result.GroupName = text.ToString();
}
}
}
To register your new provider, add it to the service collection:
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApiDescriptionProvider, CollateApiDescriptionProvider>() );
NOTE: this should occur after
services.AddApiVersioning()
You can get as creative as you want with the group names, but be aware that you cannot create multiple levels of grouping. It simply isn't supported out-of-the-box. In most cases, you can only have a single OpenAPI/Swagger document per API version. This is because URLs must be unique within the document.
It is technically possible to group on multiple levels, but this would require a number of changes to the UI and document generation process. I've only ever seen a handful of people willing to put that much effort in. They effectively created their own UIs and documentation generation backend.
Upvotes: 1
Reputation: 7987
After some analysis, I figured out that I missed DocInclusionPredicate
in AddSwaggerGen
in my ConfigureServices
.
Here is how I resolved,
options.DocInclusionPredicate((documentName, apiDescription) =>
{
var actionApiVersionModel = apiDescription.ActionDescriptor
.GetApiVersionModel(ApiVersionMapping.Explicit | ApiVersionMapping.Implicit);
var apiExplorerSettingsAttribute = (ApiExplorerSettingsAttribute)apiDescription.ActionDescriptor.EndpointMetadata.First(x => x.GetType().Equals(typeof(ApiExplorerSettingsAttribute)));
if (actionApiVersionModel == null)
{
return true;
}
if (actionApiVersionModel.DeclaredApiVersions.Any())
{
return actionApiVersionModel.DeclaredApiVersions.Any(v =>
$"{apiExplorerSettingsAttribute.GroupName}v{v.ToString()}" == documentName);
}
return actionApiVersionModel.ImplementedApiVersions.Any(v =>
$"{apiExplorerSettingsAttribute.GroupName}v{v.ToString()}" == documentName);
});
Hope this helps someone out there.
Upvotes: 7