M4 ‐ Avanzado: Técnicas y Herramientas Complementarias - sjperalta/Web-Service-C-sharp GitHub Wiki
Estrategias para Versionar Servicios:
-
Versionado en la URL:
- Se incluye la versión en la URL del servicio.
- Ejemplo:
/api/v1/products /api/v2/products
-
Versionado en el Header:
- Se utiliza un header HTTP para especificar la versión.
- Ejemplo:
GET /api/products Accept: application/vnd.myapi.v1+json
-
Versionado en Parámetros de Consulta:
- Se pasa la versión como un parámetro en la consulta.
- Ejemplo:
/api/products?version=1
Manejo de Cambios y Compatibilidad:
- Backward Compatibility: Mantener la compatibilidad con versiones anteriores tanto como sea posible.
- Deprecación Controlada: Informar a los usuarios sobre versiones que serán obsoletas, dando tiempo suficiente para la migración.
- Documentación Completa: Proveer documentación clara sobre cambios en nuevas versiones y cómo migrar a ellas.
Implementar el versionado de API en .NET Core 8 implica algunos pasos. A continuación, te guiaré sobre cómo configurar el versionado de API en un proyecto de .NET Core 8.
Primero, necesitas instalar los paquetes NuGet necesarios. Puedes hacerlo a través del Administrador de Paquetes NuGet o usando la CLI de .NET.
Usando la CLI de .NET:
dotnet add package Microsoft.AspNetCore.Mvc.Versioning
dotnet add package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
En tu archivo Program.cs
, configura los servicios de versionado de API.
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using TodoApiRestfull.Data;
using TodoApiRestfull.Services;
using TodoApiRestfull.Services.Interfaces;
var builder = WebApplication.CreateBuilder(args);
var key = Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"] ?? "");
// Configurar Versionado de API
builder.Services.AddApiVersioning(options =>
{
options.ReportApiVersions = true; // Para enviar versiones de API en los encabezados de respuesta
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0); // Versión de API predeterminada cuando no se especifica
options.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("api-version"), // Versionado usando cadena de consulta
new HeaderApiVersionReader("X-Version") // Versionado usando encabezado de solicitud
);
});
// Configurar el Explorador de API para versionado
builder.Services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
LogValidationExceptions = false,
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
context.NoResult();
context.Response.StatusCode = 401;
context.Response.ContentType = "text/plain";
return context.Response.WriteAsync(context.Exception.ToString());
}
};
});
//AddControllers es una funcion que permite utilizar controllers para mapear las llamadas
builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddScoped<ITodoService, TodoService>();
builder.Services.AddEndpointsApiExplorer();
// Configurar Swagger
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JwtAuthApi", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
});
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseSwagger();
app.UseSwaggerUI();
// Seed the database with the default user
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<TodoContext>();
context.Database.EnsureCreated();
}
app.Run();
Anota tus controladores con los atributos de versión de API adecuados.
Ejemplo para un controlador de la versión 1.0:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApiRestfull.Data;
using TodoApiRestfull.Models;
using TodoApiRestfull.Services.Interfaces;
namespace TodoApiRestfull.Controllers
{
[ApiVersion("1", Deprecated = true)]
[ApiVersion("2")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
[Authorize]
public class TodoItemsController : ControllerBase
{
private readonly ITodoService _todoService;
public TodoItemsController(ITodoService service)
{
_todoService = service;
}
// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
var items = await _todoService.GetTodoItemsAsync();
return Ok(items);
}
// GET: api/TodoItems/5
[HttpGet("{id}")]
[MapToApiVersion("1")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _todoService.GetTodoItemAsync(id);
if (todoItem == null)
{
return NotFound();
}
return Ok(todoItem);
}
[HttpGet("{id}")]
[MapToApiVersion("2")]
public async Task<ActionResult<TodoItem>> GetTodoItemV2(long id)
{
return NotFound("error intencional");
}
[HttpGet("search/{name}")]
public async Task<ActionResult<List<TodoItem>>> Search(string name)
{
var result = await _todoService.Search(name);
if(!result.Any()) {
return NotFound();
}
return Ok(result);
}
// PUT: api/TodoItems/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
var result = await _todoService.UpdateTodoItemAsync(id, todoItem);
if (!result)
{
return NotFound();
}
return NoContent();
}
// POST: api/TodoItems
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
var createdItem = await _todoService.CreateTodoItemAsync(todoItem);
return CreatedAtAction("GetTodoItem", new { id = createdItem.Id }, createdItem);
}
// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var result = await _todoService.DeleteTodoItemAsync(id);
if (!result)
{
return NotFound();
}
return NoContent();
}
}
}
Ejemplo para un controlador de la versión 2.0:
using Microsoft.AspNetCore.Mvc;
namespace MyApi.Controllers
{
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("2.0")]
public class MyControllerV2 : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok("Esta es la versión 2.0");
}
}
Ahora puedes probar tu API haciendo solicitudes a diferentes versiones.
- Versión 1.0:
GET /api/v1.0/todoitems/{id}
- Versión 2.0:
GET /api/v2.0/todoitems/{id}
También puedes usar la cadena de consulta o los encabezados si están configurados:
- Cadena de consulta:
GET /api/mycontroller?api-version=1.0
- Encabezado:
GET /api/mycontroller
con el encabezadoX-Version: 1.0
-
Integración con Swagger: Si estás usando Swagger, puedes configurarlo para que muestre APIs versionadas usando
Swashbuckle.AspNetCore
. Esto implica una configuración adicional enProgram.cs
para generar y documentar Swagger. -
Deprecación: Puedes marcar una versión de API como obsoleta usando
[ApiVersion("1.0", Deprecated = true)]
.
Siguiendo estos pasos, puedes implementar efectivamente el versionado de API en tu proyecto de .NET Core 8, permitiendo que múltiples versiones de tu API coexistan y se mantengan con el tiempo.
Herramientas para Pruebas:
-
Postman:
- Una herramienta versátil para realizar pruebas a servicios RESTful.
- Permite guardar y organizar colecciones de solicitudes, automatizar pruebas y generar documentación.
-
SoapUI:
- Ideal para probar servicios SOAP y REST.
- Permite crear, ejecutar y automatizar pruebas, así como validar respuestas y simular comportamientos de servicios.