Saltar al contenido principal

Inyección de dependencias

Registro automático con AddValidationsFromAssembly

El método de extensión AddValidationsFromAssembly escanea un assembly y registra automáticamente todas las clases que implementan IValidator<T> en el contenedor DI.

// Program.cs
using Vali_Validation.Core.Extensions;

builder.Services.AddValidationsFromAssembly(
assembly: typeof(Program).Assembly,
lifetime: ServiceLifetime.Transient); // Transient es el valor por defecto

Registro desde múltiples assemblies

builder.Services.AddValidationsFromAssembly(typeof(Program).Assembly);
builder.Services.AddValidationsFromAssembly(typeof(ApplicationLayerMarker).Assembly);
builder.Services.AddValidationsFromAssembly(typeof(InfrastructureLayerMarker).Assembly);

ServiceLifetime: cuál elegir

LifetimeDescripciónCuándo usar
TransientNueva instancia en cada resoluciónValidadores sin dependencias o con dependencias Transient
ScopedUna instancia por petición HTTPSi el validador depende de DbContext u otro servicio Scoped
SingletonUna instancia para toda la appSolo si el validador no tiene dependencias mutables

Regla de oro

El lifetime del validador debe ser igual o más corto que el lifetime de sus dependencias más longevas.

// Correcto: DbContext es Scoped, el validador también es Scoped
builder.Services.AddValidationsFromAssembly(assembly, ServiceLifetime.Scoped);

// Incorrecto: DbContext es Scoped, el validador es Singleton → captive dependency
builder.Services.AddValidationsFromAssembly(assembly, ServiceLifetime.Singleton); // EVITAR

Inyectando IValidator<T> en constructores

En un endpoint de Minimal API

app.MapPost("/products", async (
[FromBody] CreateProductRequest request,
[FromServices] IValidator<CreateProductRequest> validator,
[FromServices] IProductRepository repository,
CancellationToken ct) =>
{
var result = await validator.ValidateAsync(request, ct);
if (!result.IsValid)
return Results.ValidationProblem(result.Errors.ToDictionary(
k => k.Key, v => v.Value.ToArray()));

var product = await repository.CreateAsync(request, ct);
return Results.Created($"/products/{product.Id}", product);
});

En un controlador MVC

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IValidator<CreateUserRequest> _validator;
private readonly IUserService _userService;

public UsersController(
IValidator<CreateUserRequest> validator,
IUserService userService)
{
_validator = validator;
_userService = userService;
}

[HttpPost]
public async Task<IActionResult> Create(
[FromBody] CreateUserRequest request,
CancellationToken ct)
{
var result = await _validator.ValidateAsync(request, ct);
if (!result.IsValid)
return BadRequest(result.Errors);

var user = await _userService.CreateAsync(request, ct);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
}

Registro manual (sin AddValidationsFromAssembly)

// Registro individual
builder.Services.AddTransient<IValidator<CreateProductRequest>, CreateProductRequestValidator>();
builder.Services.AddScoped<IValidator<CreateOrderRequest>, CreateOrderRequestValidator>();

Testing: Mockear IValidator<T>

// Con Moq
var validatorMock = new Mock<IValidator<CreateOrderRequest>>();
validatorMock
.Setup(v => v.ValidateAsync(It.IsAny<CreateOrderRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ValidationResult()); // Sin errores

var service = new OrderService(validatorMock.Object, repositoryMock.Object);
await service.CreateAsync(request, CancellationToken.None);

Siguientes pasos