
Cuando tienes que versionar una API REST en AspNet, lo primero que piensas es en el Routing y en como construirlo adecuadamente para gestionar dicho versionado. De la misma manera, generar un Swagger que soporte cada una de estas nuevas versiones.
Durante estos días he estado revisando este tema que hacia ya tiempo que no lo hacía y bueno, como era de esperar ya tenemos algunos paquetes NuGet que nos facilitan todo el trabajo.
En este post te indico los pasos que he seguido para versionar el API de ejemplo «WeatherForecast«, por supuesto adecuando Swagger a dicho versionado.
Versionado + Multi-Swagger
En primer lugar creamos una nueva API REST («WeatherForecastApi») y, ¡por supuesto ya en .NET 7.0!
Añadimos las dos dependencias que nos van a facilitar el trabajo: «Microsoft.AspNetCore.Mvc.Versioning» y «Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer«

A continuación, creamos algunas clases para configurar y ajustar todo a nuestras necesidades:
ApiVersioningExtensions. Extension para: configurar nuestra API y dotar a swagger de información/documentación extra.
public static class ApiVersioningExtensions { public static IServiceCollection AddCustomApiVersioning(this IServiceCollection services) { services.AddApiVersioning(config => { config.AssumeDefaultVersionWhenUnspecified = true; config.DefaultApiVersion = new ApiVersion(1, 0); // allows API return versions in the response header (api-supported-versions). config.ReportApiVersions = true; //config.ApiVersionReader = ApiVersionReader.Combine( // new UrlSegmentApiVersionReader(), // new HeaderApiVersionReader("x-api-version"), // new MediaTypeApiVersionReader("x-api-version")); }); // Allows to discover versions services.AddVersionedApiExplorer(config => { config.GroupNameFormat = "'v'VVV"; config.SubstituteApiVersionInUrl = true; }); services.AddSwaggerGen(config => { config.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"), true); config.OperationFilter<SwaggerDefaultValuesFilter>(); }); services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>(); return services; } }
donde:
- Establecemos configuración predeterminada de la version. Al no especificar nada más, asumimos que los controllers tienen versión 1.0. ¡Muy buen punto como partida para una API que acabamos de crear o para una ya existente que no tiene versionado, así, nos aseguraremos que a futuro esta puede crecer con facilidad y mantener un versionado estandar y adecuado!.
- El formato de la vesión la establecemos siguiedno Semantic Versioning ApiVersion(M,m.»status»), por lo que podemos tener números de version tales como: v1.0 / 1.0, 1.1, 2.x, 3.x, …. e incluso, 1.0-RC, 1.11-RC1, etc.
- Igualmente, para la generación del Swagger necesitamos especificar el formato de version a usar, por lo que siguiendo Semantic Versioning, daremos a la propiedad GroupNameFormat el valor: «‘v’VVV«.
- ApiVersionReader. Permite indicar donde situar la parametrización de versionado:
- UrlSegmentApiVersionReader. En la URL, que es el valor predeterminado.
- HeaderApiVersionReader. En la cabecera (o Header) como nuevo atributo, «x-api-version«
- MediaTypeApiVersionReader. O bien, en la cabecera junto al parámetro Content-Type, por lo que tendríamos algo asi: «Accept/Content-Type: application/json; x-api-version=1.0».
- IncludeXmlComments. Nos permite dotar al Swagger con documentación extra incluyendo los comentarios de código. ¡Es importante por tanto documentar los Controller, Models y ViewModels de la aplicación!
Añadimos la siguiente configuración al .csproj para indicarle que queremos generar esta documentación en tiempo de compilación y a la par excluimos el warning 1591. ¡De otra manera y opcionalmente, evitamos recibir wanings constántes sobre documentar «tooooodo» el código!.
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
Añadimos esta otra clase, ConfigureSwaggerOptions, que va dotar al Swagger resultante de multi-versionado. Es decir, vamos a poder disponer de mas de una versión.

public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions> { private readonly IApiVersionDescriptionProvider apiVersionDescriptionProvider; public ConfigureSwaggerOptions(IApiVersionDescriptionProvider apiVersionDescriptionProvider) { this.apiVersionDescriptionProvider = apiVersionDescriptionProvider; } public void Configure(SwaggerGenOptions options) { // TODO: Add authenticacion stuffs //options.AddSecurityDefinition(...); //options.AddSecurityRequirement(...); foreach (var description in this.apiVersionDescriptionProvider.ApiVersionDescriptions) { options.SwaggerDoc(description.GroupName, CreateOpenApiInfo(description)); options.OrderActionsBy((apiDesc) => $"{apiDesc.RelativePath}"); options.DescribeAllParametersInCamelCase(); options.CustomSchemaIds(this.DefaultSchemaIdSelector); } } private string DefaultSchemaIdSelector(Type modelType) { if (!modelType.IsConstructedGenericType) return modelType.Name; var prefix = modelType.GetGenericArguments() .Select(this.DefaultSchemaIdSelector) .Aggregate((previous, current) => previous + current); return modelType.Name.Split('`').First() + "Of" + prefix; } private static OpenApiInfo CreateOpenApiInfo(ApiVersionDescription description) { var info = new OpenApiInfo() { Title = "Weather Forecast API", Version = description.ApiVersion.ToString(), }; if (description.IsDeprecated) { info.Description += " (deprecated)"; } return info; } }
Donde:
- El método «DefaultSchemaIdselector«, permite que la generación del Swagger cumpla los estandares y, en concreto, adapta la generación de enumerados genéricos del tipo IEnumerable<MyClass<T>> y similares a un formato estandar permitido por OpenAPI/Swagger.
- Title. Es recomendable indicar un titulo, entre otras cosas porque es requerido para muchas integraciones con otras herramientas de terceros.
Finalmente, añadimos una clase más, «Filters/SwaggerDefaultValuesFilter.cs«, cuya finalidad es realizar transformaciones en las operaciones del API estableciendo las propiedades Description y Schema de cada uno de los parámetros para la generación de Swagger.
public class SwaggerDefaultValuesFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { var apiDescription = context.ApiDescription; operation.Deprecated |= apiDescription.IsDeprecated(); if (operation.Parameters == null) { return; } foreach (var parameter in operation.Parameters) { var description = apiDescription.ParameterDescriptions.First(p => p.Name.Equals(parameter.Name, System.StringComparison.CurrentCultureIgnoreCase)); parameter.Description ??= description.ModelMetadata?.Description; if (parameter.Schema.Default is null && description.DefaultValue is not null) { parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString()); } parameter.Required |= description.IsRequired; } } }
Actualizando Controllers
Actualizamos los contraladores indicando la versión teniendo en cuenta lo siguiente:
- Si disponemos de una única versión (v1), podemos optar por actualizar únicamente el Routing como «v1/[Controller]» y no hacer nada mas. En cuyo caso y como ya hemos visto, el controller tomara el valor prederminado v1. Por contra, podemos, establecer la notación «[ApiVersion(«1.0»)]» lo que en mi opinión, hace mas legible al Controlador cuando dispongamos de multiples versiones.
- Por otro lado, podemos dotar al Routing de una configuración mas general y permitirle estar disponbile para el multiversionado: [Route(«v{version:apiVersion}/[controller]»)].
- También podemos versionar acciones (o Actions) usando para ello la notación: [MapToApiVersion(«x.x-xxx»)]
- Y, de la misma manera, para cuando queremos deprecar una Accion o un Controlador, empleamos la notación: [ApiExplorerSettings(IgnoreApi = true)].
Actualizando Program.cs
Finalmente, actualizamos el Program.cs y lo dejamos tal y como muestro a continuación. En negrita lo mas destacado que se corresponde prácticamente con la invocación y uso de las clases creadas anteriormente.
using Microsoft.AspNetCore.Mvc.ApiExplorer; using WeatherForecastApi.Extensions; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddEndpointsApiExplorer() .AddCustomApiVersioning(); var app = builder.Build(); var apiVersionDescriptionProvider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(config => { // Allows multiple versions of our routes. // .Reverse(), first shown most recent version. foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions.Reverse()) { config.SwaggerEndpoint( url: $"/swagger/{description.GroupName}/swagger.json", name: $"Weather Forecast API - {description.GroupName.ToUpper()}"); } }); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
Swagger Generator CLI
Llegado a este punto, ya tenemos nuestra api versionada y un Swagger que permite multi-version.
Estando en un entorno de Desarrollo (variable de entorno, ASPNETCORE_ENVIRONMENT=Development) nuestro Swagger estará disponible y podremos usarlo explicitamente. En Producción esto no es recomendable, por lo que no estará disponible. Si bien, podemos querer integrar nuestra API con herramientas de terceros y necesitamos generar el Swagger (Swagger.json o incluso en formato .yaml). De la misma forma, necesitaríamos indicar el servidor donde se encuentra realmente el API. De esta manera, estas herramientas de terceros podrán acceder a la/s APIs y ejecutar peticiones.
Nota: Te recomiendo que eches un vistazo a Readme.com, o a alguna otra similar. Se trata una herramienta bastante usada para exponer información de APIs a clientes, permitiendo conocer su uso, monitorización, control de accesos, facilitar acciones adicionales de versionado, publicación de endpoints, etc.
Es en estas situaciones donde entra en juego «Swagger CLI«. Muy muy sencillo de instalar y usar:
# Install dotnet tool install -g Swashbuckle.AspNetCore.Cli # Verify dotnet tool list -g # Generate Swagger.json file swagger tofile --host myserver.com --output .\Swagger.json .\bin\Debug\net7.0\WeatherForecastApi.dll v1
A vemos una imagen donde si la dividimos verticalmente en dos columnas:
- A la izquierda tenemos a Visual Studio y el Swagger generado automaticamente con su ejecución, y,
- A la derecha, Swagger CLI con el Swagger.json generado y editado/visualizado en Swagger editor.

Hasta aquí todo lo que quería contar por el momento.
El código de ejemplo, como siempre, puedes encontrarlo aquí, en mi Github.
Happy Versioning!