From 62a10af8330cf59c8d33fac094ccc3d0735aa7e3 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Sat, 7 Mar 2026 20:41:26 -0300 Subject: [PATCH] feat: configurar swagger con soporte para multiples versiones v1 y v2 --- .../ApiVersioningDemo.Api.csproj | 1 + .../ConfigureSwaggerOptions.cs | 42 +++++++++++++++++++ .../WeatherForecastV2Controller.cs | 22 ++++++---- ApiVersioningDemo.Api/Program.cs | 42 +++++++++++-------- 4 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 ApiVersioningDemo.Api/ConfigureSwaggerOptions.cs diff --git a/ApiVersioningDemo.Api/ApiVersioningDemo.Api.csproj b/ApiVersioningDemo.Api/ApiVersioningDemo.Api.csproj index d678ca6..42e6c42 100644 --- a/ApiVersioningDemo.Api/ApiVersioningDemo.Api.csproj +++ b/ApiVersioningDemo.Api/ApiVersioningDemo.Api.csproj @@ -10,6 +10,7 @@ + diff --git a/ApiVersioningDemo.Api/ConfigureSwaggerOptions.cs b/ApiVersioningDemo.Api/ConfigureSwaggerOptions.cs new file mode 100644 index 0000000..a0e094c --- /dev/null +++ b/ApiVersioningDemo.Api/ConfigureSwaggerOptions.cs @@ -0,0 +1,42 @@ +using Asp.Versioning.ApiExplorer; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace ApiVersioningDemo.Api; + +public class ConfigureSwaggerOptions : IConfigureOptions +{ + private readonly IApiVersionDescriptionProvider _provider; + + public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) + { + _provider = provider; + } + + public void Configure(SwaggerGenOptions options) + { + // Por cada versión de API detectada, crea un documento Swagger + foreach (var description in _provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); + } + } + + private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) + { + var info = new OpenApiInfo() + { + Title = "Mi API Versionada", + Version = description.ApiVersion.ToString(), + Description = "Ejemplo de API REST con versionado en .NET" + }; + + if (description.IsDeprecated) + { + info.Description += " (Esta versión está obsoleta)"; + } + + return info; + } +} \ No newline at end of file diff --git a/ApiVersioningDemo.Api/Controllers/WeatherForecastV2Controller.cs b/ApiVersioningDemo.Api/Controllers/WeatherForecastV2Controller.cs index 9b2273e..b80e45d 100644 --- a/ApiVersioningDemo.Api/Controllers/WeatherForecastV2Controller.cs +++ b/ApiVersioningDemo.Api/Controllers/WeatherForecastV2Controller.cs @@ -11,8 +11,11 @@ public class WeatherForecastV2Controller : ControllerBase { private static readonly string[] Summaries = [ "Helado", "Frío", "Fresco", "Templado", "Cálido", "Caluroso", "Sofocante", "SuperScorching" - ]; [HttpGet(Name = "GetWeatherForecastV2")] - public IActionResult Get() // Cambiamos a IActionResult para devolver un objeto anónimo + ]; + + [HttpGet(Name = "GetWeatherForecastV2")] + // CAMBIO 1: Cambiamos IActionResult por ActionResult + public ActionResult Get() { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast { @@ -21,11 +24,12 @@ public class WeatherForecastV2Controller : ControllerBase Summary = Summaries[Random.Shared.Next(Summaries.Length)] }).ToArray(); - // 3. EL BREAKING CHANGE: Ahora devolvemos un objeto con la "Ciudad" y la lista adentro - return Ok(new - { - Ciudad = "Buenos Aires", - Pronosticos = forecast - }); + // CAMBIO 2: Devolvemos el record tipado en lugar del anónimo + var response = new WeatherForecastResponseV2("Buenos Aires", forecast); + + return Ok(response); } -} \ No newline at end of file +} + +// Definición fuerte de la respuesta para que Swagger la entienda +public record WeatherForecastResponseV2(string Ciudad, IEnumerable Pronosticos); \ No newline at end of file diff --git a/ApiVersioningDemo.Api/Program.cs b/ApiVersioningDemo.Api/Program.cs index 2f59fd7..6250496 100644 --- a/ApiVersioningDemo.Api/Program.cs +++ b/ApiVersioningDemo.Api/Program.cs @@ -1,44 +1,52 @@ -// ApiVersioningDemo.api/Program.cs +using ApiVersioningDemo.Api; using Asp.Versioning; var builder = WebApplication.CreateBuilder(args); -// CONFIGURACIÓN DE VERSIONADO +// 1. Configuración de Versionado (YA LO TENÍAS) builder.Services.AddApiVersioning(options => { - // Si el cliente no especifica versión, usaremos la 1.0 por defecto options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; - - // Esto añade una cabecera HTTP en las respuestas diciendo qué versiones existen (ej: api-supported-versions: 1.0, 2.0) options.ReportApiVersions = true; }) -.AddMvc() // Integra el versionado con los Controladores +.AddMvc() .AddApiExplorer(options => { - // Configura el formato para Swagger (ej: "v1", "v2") - options.GroupNameFormat = "'v'VVV"; + // IMPORTANTE: El formato "'v'VVV" hace que la versión se llame "v1", "v2", etc. + options.GroupNameFormat = "'v'VVV"; options.SubstituteApiVersionInUrl = true; }); +// 2. Configuración de Swagger +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// Esta lógica crea un documento Swagger por cada versión descubierta automáticamente +builder.Services.ConfigureOptions(); -// Add services to the container. builder.Services.AddControllers(); -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi -builder.Services.AddOpenApi(); var app = builder.Build(); -// Configure the HTTP request pipeline. +// 3. Activar la Interfaz Gráfica if (app.Environment.IsDevelopment()) { - app.MapOpenApi(); + app.UseSwagger(); + app.UseSwaggerUI(options => + { + // Genera un endpoint JSON por cada versión que exista en la API + var descriptions = app.DescribeApiVersions(); + foreach (var description in descriptions) + { + var url = $"/swagger/{description.GroupName}/swagger.json"; + var name = description.GroupName.ToUpperInvariant(); + options.SwaggerEndpoint(url, name); + } + }); } app.UseHttpsRedirection(); - app.UseAuthorization(); - app.MapControllers(); - -app.Run(); +app.Run(); \ No newline at end of file