Versionamiento de APIs en .NET: guía práctica de implementación
Guía práctica con código real sobre estrategias de versionamiento para APIs

Hey dev! En el artículo anterior te hablé toda la teoría que debes conocer para versionar un API, aquí iremos a la práctica, ¡arrancamos!

Vamos a ver cómo implementé el versionamiento en un proyecto real.

La estrategia que te enseñaré es el versionamiento por URL (URL Path Versioning)

El código está organizado siguiendo Clean Architecture:

Paso 1: Estructura de Controllers Versionados

La forma más limpia es crear controllers separados por versión:

// Controllers/ProductsV1Controller.cs
[ApiController]
[Route("api/v1/products")]
public class ProductsV1Controller : ControllerBase
{
    private readonly IProductService _productService;

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var result = await _productService.GetAllAsync();
        return Ok(result);
    }
}
// Controllers/ProductsV2Controller.cs
[ApiController]
[Route("api/v2/products")]
public class ProductsV2Controller : ControllerBase
{
    private readonly IProductService _productService;

    [HttpGet]
    public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 10)
    {
        var result = await _productService.GetAllPaginatedAsync(page, pageSize);
        return Ok(result);
    }
}

Nota: Ambos controllers consumen el mismo servicio. La diferencia está en el contrato de la API:

  • v1 devuelve todos los productos
  • v2 devuelve productos paginados

Paso 2: Configuración de Swagger

Para que Swagger muestre las versiones correctamente, configuramos múltiples documentos:

// Program.cs
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo 
    { 
        Title = "Products API", 
        Version = "v1" 
    });
    options.SwaggerDoc("v2", new OpenApiInfo 
    { 
        Title = "Products API", 
        Version = "v2" 
    });

    options.DocInclusionPredicate((docName, apiDesc) =>
    {
        var path = apiDesc.RelativePath ?? "";
        return docName == "v1" && path.StartsWith("api/v1/")
            || docName == "v2" && path.StartsWith("api/v2/");
    });
});
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/swagger/v1/swagger.json", "Products API v1");
    options.SwaggerEndpoint("/swagger/v2/swagger.json", "Products API v2");
    options.RoutePrefix = "swagger";
});

Resultado: Un dropdown en Swagger que filtra endpoints por versión.

Paso 3: Servicios y más...

Tendrías una interfaz:

public interface IProductService
{
    Task<ApiResponse<IEnumerable<ProductDto>>> GetAllAsync();
    Task<PaginatedResponse<ProductDto>> GetAllPaginatedAsync(int page, int pageSize);
    // otros...
}

Tu clase ProductService podría quedar así:

public class ProductService : IProductService
{
    private readonly IProductRepository _repository;
    private readonly IMapper _mapper;
    private readonly ILogger<ProductService> _logger;

    public ProductService(IProductRepository repository, IMapper mapper, ILogger<ProductService> logger)
    {
        _repository = repository;
        _mapper = mapper;
        _logger = logger;
    }

    public async Task<ApiResponse<IEnumerable<ProductDto>>> GetAllAsync()
    {
        _logger.LogDebug("Fetching all products");
        var products = await _repository.GetAllAsync();
        return ApiResponse<IEnumerable<ProductDto>>.SuccessResponse(_mapper.Map<IEnumerable<ProductDto>>(products));
    }

    public async Task<PaginatedResponse<ProductDto>> GetAllPaginatedAsync(int page, int pageSize)
    {
        _logger.LogDebug("Fetching paginated products - Page: {Page}, PageSize: {PageSize}", page, pageSize);
        var (products, totalCount) = await _repository.GetAllPaginatedAsync(page, pageSize);
        
        return new PaginatedResponse<ProductDto>
        {
            Items = _mapper.Map<IEnumerable<ProductDto>>(products),
            TotalCount = totalCount,
            Page = page,
            PageSize = pageSize
        };
    }
}

Tu puedes completar el repositorio para traer los datos o cambiar la clase de servicio, lo importante aquí es que sepas cómo versionar el API y eso lo hacemos en los pasos 1 y 2.

Como resultado tendrás un API que tiene dos versiones, y en swagger lo mirarías así:

Fíjate que ese dropdown funciona y el tag al costado del nombre del API ahora cambia según la versión seleccionada de v1 a v2.

Y eso es todo por ahora, nos vemos en el próximo artículo, cracks!

Si esta entrada te ha gustado, te invito a compartirla!

Créditos de imagen de portada: Foto de Hans-Jurgen Mager en Unsplash

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *