Serving an existing Swagger document with different operation IDs
This is a bit involved. Swashbuckle lets us filter single document at a time. We should be able to inject ISwaggerProvider
inside a document filter, which would simplify things quite a bit, but I couldn't get it to work. Nonetheless, we can decorate the existing ISwaggerProvider
implementation and serve the modified document.
Step 1: Set operation ids
Actions have no operation ids by default, so use SwaggerGenOptions.CustomOperationIds()
to populate the values.
services.AddSwaggerGen(
c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ApiPlayground", Version = "v1" });
// use action names as operations ids if present
c.CustomOperationIds(
description => description.ActionDescriptor is not ControllerActionDescriptor actionDescriptor
? null
: actionDescriptor.ActionName);
}
);
Step 2: Define two documents for Swagger UI
This gets Swagger UI to show two different documents in the doc dropdown menu. Here I've defined two documents, one with ops
suffix, and one without.
// inside Startup class
public void Configure(IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiPlayground v1");
c.SwaggerEndpoint("/swagger/v1ops/swagger.json", "ApiPlayground v1ops");
c.DisplayOperationId();
});
// ...
}
Here's how it looks:

We'll intercept the request for v1ops
and serve a modified document.
Step 3: Implement a custom ISwaggerProvider
This is necessary to serve a custom OpenAPI document while still being able to access existing documents to source the actual values. Once we get the original document, we modify each endpoint's operation id.
So, I've written a proxy that subclasses SwaggerGenerator
and implements ISwaggerProvider
at the same time. This ensures that the method we've hidden with new
keyword actually gets called.
Here, this class intercepts document requests and checks if the document name is suffixed with ops
, then serves the original document with modified operation ids.
class SwaggerDocCustomOperationIdProvider : SwaggerGenerator, ISwaggerProvider
{
public SwaggerDocCustomOperationIdProvider(SwaggerGeneratorOptions options, IApiDescriptionGroupCollectionProvider apiDescriptionsProvider, ISchemaGenerator schemaGenerator) : base(options, apiDescriptionsProvider, schemaGenerator)
{
}
public new OpenApiDocument GetSwagger(string documentName, string host = null, string basePath = null)
{
if (!documentName.EndsWith("ops"))
{
return base.GetSwagger(documentName, host, basePath);
}
var sourceDoc = documentName.Replace("ops", "");
var doc = base.GetSwagger(sourceDoc, host, basePath);
// dont mutate the info props, because Swashbuckle caches the docs
doc.Info = new OpenApiInfo
{
Title = $"{doc.Info.Title} - with operation ids",
Version = doc.Info.Version,
};
var operations = doc.Paths
.SelectMany(p => p.Value.Operations.Values)
.ToList();
foreach (var op in operations)
{
// change the operation id
op.OperationId = $"Cloned{op.OperationId}";
}
return doc;
}
}
Then, we register it in DI container.
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSwaggerGen(/* ... */);
// get swashbuckle to use our implementation
services.AddTransient<ISwaggerProvider, SwaggerDocCustomOperationIdProvider>();
}
Result
The original document:

One with custom operation ids:

Caveats:
Since we're modifying the final document, we don't have access to reflection data like the original SwaggerGenerator
. This means you can't easily refer to runtime method information. But you can always implement an IDocumentFilter
and prepare the schema from scratch.