EV.MediatorX 8.0.0

dotnet add package EV.MediatorX --version 8.0.0
                    
NuGet\Install-Package EV.MediatorX -Version 8.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EV.MediatorX" Version="8.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EV.MediatorX" Version="8.0.0" />
                    
Directory.Packages.props
<PackageReference Include="EV.MediatorX" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EV.MediatorX --version 8.0.0
                    
#r "nuget: EV.MediatorX, 8.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EV.MediatorX@8.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EV.MediatorX&version=8.0.0
                    
Install as a Cake Addin
#tool nuget:?package=EV.MediatorX&version=8.0.0
                    
Install as a Cake Tool

⚡ EV.MediatorX

Una implementación robusta y eficiente del patrón CQRS Mediator para .NET 8+ diseñada para aplicaciones empresariales modernas.

✨ Características

  • 🎯 Comandos y Consultas (CQRS) - Separación clara entre operaciones de lectura y escritura.
  • 🔄 Pipeline Behaviors - Middleware personalizable para logging, validación y más.
  • Validación Integrada - Sistema de validación fluido y extensible.
  • 📢 Domain Events - Manejo robusto de eventos de dominio con notificaciones.
  • 🛡️ Manejo de Excepciones - Captura automática y gestión centralizada de errores.
  • 🎁 Patrón Result<T> - Manejo funcional de errores sin excepciones.
  • Async/Await - API completamente asíncrona para máximo rendimiento.
  • 📦 Dependency Injection - Integración perfecta con el contenedor DI de .NET.
  • 🔧 Altamente Configurable - Adapta el comportamiento a tus necesidades.

🔐 Seguridad y Privacidad (DataSanitizer)

  • ✅ Cumple con GDPR y normativas de privacidad.
  • 🚫 Evita exponer credenciales en logs.
  • 🐛 Permite debug sin comprometer seguridad.
  • 🔍 Detecta automáticamente campos sensibles por nombre.
  • 📝 Campos personalizados para datos sensibles: "identification", "password", "token", "key", "secret", "authorization", "connectionstring", "apikey", "clientsecret", "bearer", "credential", "pin", "ssn", "dni", "nif", "passport", "username", "email", "value", "owner", "pwd", "createdby", "updatedby", "deletedby".

📦 Instalación

dotnet add package EV.MediatorX

O vía NuGet Package Manager:

Install-Package EV.MediatorX

🚀 Inicio Rápido

1. Configurar en Program.cs

using EV.MediatorX.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Configurar EV.MediatorX
builder.Services.AddMediatorX(config =>
{
    // Registrar handlers y validadores del assembly actual
    config.RegisterServicesFromAssembly(typeof(Program).Assembly);
    config.RegisterValidatorsFromAssembly(typeof(Program).Assembly);
    
    // Agregar behaviors predeterminados (recomendado)
    config.AddDefaultBehaviors(
        includeValidation: true,
        includePerformance: true,
        includeExceptionHandling: true,
        includeLogging: true
    );
});

// Agregar tus servicios
builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

2. Crear un Comando

using EV.MediatorX.Abstractions.Messaging;

// Definir el comando
public sealed record CreateProductCommand(
    string Name, 
    string Description, 
    decimal Price, 
    int Stock
) : ICommand<Guid>;

// Implementar el handler
public sealed class CreateProductCommandHandler : ICommandHandler<CreateProductCommand, Guid>
{
    private readonly IProductRepository _repository;
    private readonly ILogger<CreateProductCommandHandler> _logger;

    public CreateProductCommandHandler(
        IProductRepository repository, 
        ILogger<CreateProductCommandHandler> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public async Task<Result<Guid>> HandleAsync(
        CreateProductCommand request, 
        CancellationToken ct)
    {
        _logger.LogInformation("Creating product: {Name}", request.Name);

        var product = Product.Create(
            request.Name, 
            request.Description, 
            request.Price, 
            request.Stock
        );
        
        await _repository.AddAsync(product, ct);

        _logger.LogInformation("Product created with Id: {ProductId}", product.Id);

        return Result.Success(product.Id);
    }
}

3. Crear una Consulta

using EV.MediatorX.Abstractions.Messaging;

// Definir la consulta
public sealed record GetProductByIdQuery(Guid Id) : IQuery<ProductDto>;

// Implementar el handler
public sealed class GetProductByIdQueryHandler : IQueryHandler<GetProductByIdQuery, ProductDto>
{
    private readonly IProductRepository _repository;

    public GetProductByIdQueryHandler(IProductRepository repository)
    {
        _repository = repository;
    }

    public async Task<Result<ProductDto>> HandleAsync(
        GetProductByIdQuery request, 
        CancellationToken ct)
    {
        var product = await _repository.GetByIdAsync(request.Id, ct);

        if (product is null)
        {
            return Result.Failure<ProductDto>(
                Error.NotFound(
                    "Product.NotFound", 
                    $"Product with ID {request.Id} not found"
                )
            );
        }

        var dto = new ProductDto
        {
            Id = product.Id,
            Name = product.Name,
            Description = product.Description,
            Price = product.Price,
            Stock = product.Stock,
            CreatedAt = product.CreatedAt
        };

        return Result.Success(dto);
    }
}

4. Usar en un Controller

using EV.MediatorX.Core;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;

    public ProductsController(IMediator mediator) => _mediator = mediator;

    [HttpPost]
    [ProducesResponseType(typeof(Guid), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> Create([FromBody] CreateProductCommand command)
    {
        var result = await _mediator.SendAsync(command);
        
        return result.IsFailure
            ? BadRequest(new { 
                error = result.Error.Code, 
                message = result.Error.Message 
              })
            : CreatedAtAction(nameof(GetById), new { id = result.Value }, result.Value);
    }

    [HttpGet("{id}")]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetById(Guid id)
    {
        var result = await _mediator.SendAsync(new GetProductByIdQuery(id));
        
        return result.IsFailure
            ? NotFound(new { 
                error = result.Error.Code, 
                message = result.Error.Message 
              })
            : Ok(result.Value);
    }

    [HttpGet]
    [ProducesResponseType(typeof(List<ProductDto>), StatusCodes.Status200OK)]
    public async Task<IActionResult> GetAll()
    {
        var result = await _mediator.SendAsync(new GetAllProductsQuery());
        return Ok(result.Value);
    }

    [HttpPut("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> Update(
        Guid id, 
        [FromBody] UpdateProductRequest request)
    {
        var command = new UpdateProductCommand(
            id, 
            request.Name, 
            request.Description, 
            request.Price, 
            request.Stock
        );
        
        var result = await _mediator.SendAsync(command);
        
        return result.IsFailure
            ? NotFound(new { 
                error = result.Error.Code, 
                message = result.Error.Message 
              })
            : NoContent();
    }

    [HttpDelete("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> Delete(Guid id)
    {
        var result = await _mediator.SendAsync(new DeleteProductCommand(id));
        
        return result.IsFailure
            ? NotFound(new { 
                error = result.Error.Code, 
                message = result.Error.Message 
              })
            : NoContent();
    }
}

🎯 Características Avanzadas

Validación de Comandos

Implementa validadores para asegurar la integridad de los datos antes de ejecutar comandos:

using EV.MediatorX.Behaviors;

public sealed class CreateProductCommandValidator : IValidator<CreateProductCommand>
{
    public Task<ValidationResult> ValidateAsync(
        ValidationContext<CreateProductCommand> context, 
        CancellationToken cancellationToken = default)
    {
        var cmd = context.InstanceToValidate;
        var errors = new List<ValidationFailure>();

        // Validar nombre
        if (string.IsNullOrWhiteSpace(cmd.Name))
        {
            errors.Add(new ValidationFailure(
                nameof(cmd.Name), 
                "Name is required"
            ));
        }
        else if (cmd.Name.Length < 3)
        {
            errors.Add(new ValidationFailure(
                nameof(cmd.Name), 
                "Name must be at least 3 characters"
            ));
        }

        // Validar precio
        if (cmd.Price <= 0)
        {
            errors.Add(new ValidationFailure(
                nameof(cmd.Price), 
                "Price must be greater than zero"
            ));
        }

        // Validar stock
        if (cmd.Stock < 0)
        {
            errors.Add(new ValidationFailure(
                nameof(cmd.Stock), 
                "Stock cannot be negative"
            ));
        }

        return Task.FromResult(
            errors.Any() 
                ? ValidationResult.WithErrors(errors) 
                : ValidationResult.Success()
        );
    }
}

Events (Notificaciones)

Implementa eventos para reaccionar a cambios importantes:

using EV.MediatorX.Abstractions.Messaging;
using EV.MediatorX.Api.Interfaces;
using EV.MediatorX.Api.Models;
using EV.MediatorX.Core;

// 1. Definir el evento
public sealed record ProductCreatedEvent(Guid ProductId) : IEvent;

// 2. Crear el handler del evento
internal sealed class ProductCreatedEventHandler : INotificationHandler<ProductCreatedEvent>
{
    private readonly ILogger<ProductCreatedEventHandler> _logger;

    public ProductCreatedEventHandler(ILogger<ProductCreatedEventHandler> logger)
    {
        _logger = logger;
    }

    public async Task HandleAsync(
        ProductCreatedEvent @event, 
        CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Product created event received for ProductId: {ProductId}", 
            @event.ProductId
        );

        // Lógica de negocio: enviar email, actualizar caché, etc.
        // await _emailService.SendProductCreatedNotificationAsync(...);
        
        await Task.CompletedTask;
    }
}

// 3. Levantar el evento desde tu entidad
public sealed class Product : EventAggregator<Guid>
{
    public static Product Create(string name, string description, decimal price, int stock)
    {
        var product = new Product
        {
            Id = Guid.NewGuid(),
            Name = name,
            Description = description,
            Price = price,
            Stock = stock,
            CreatedAt = DateTime.UtcNow
        };

        // Agregar el evento
        product.RaiseEvent(new ProductCreatedEvent(product.Id));

        return product;
    }
}

// 4. Publicar eventos en el repositorio
public sealed class ProductService(IPublisher publisher) : IProductRepository
{
    private readonly IPublisher _publisher = publisher;
    private readonly SemaphoreSlim _lock = new(1, 1);

    ...

    public async Task AddAsync(Product product, CancellationToken cancellationToken = default)
    {
        await _lock.WaitAsync(cancellationToken);

        try
        {
            _products[product.Id] = product;

            // Obtener y publicar eventos
            var events = product.GetEvents();

            product.ClearEvents();

            foreach (var @event in events)
            {
                await _publisher.PublishAsync(@event, cancellationToken);
            }
        }
        finally
        {
            _lock.Release();
        }
    }
}

Pipeline Behaviors Personalizados

Crea behaviors personalizados para añadir funcionalidad transversal:

using EV.MediatorX.Abstractions;
using EV.MediatorX.Abstractions.Messaging;

public class TransactionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IBaseCommand
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ILogger<TransactionBehavior<TRequest, TResponse>> _logger;

    public TransactionBehavior(
        IUnitOfWork unitOfWork, 
        ILogger<TransactionBehavior<TRequest, TResponse>> logger)
    {
        _unitOfWork = unitOfWork;
        _logger = logger;
    }

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        var requestName = request.GetType().Name;

        _logger.LogInformation("Beginning transaction for {RequestName}", requestName);

        await _unitOfWork.BeginTransactionAsync(cancellationToken);

        try
        {
            var response = await next();

            await _unitOfWork.CommitAsync(cancellationToken);

            _logger.LogInformation("Transaction committed for {RequestName}", requestName);

            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Transaction failed for {RequestName}", requestName);
            await _unitOfWork.RollbackAsync(cancellationToken);
            throw;
        }
    }
}

// Registrar el behavior
builder.Services.AddMediatorX(config =>
{
    config.RegisterServicesFromAssembly(typeof(Program).Assembly);
    config.AddOpenBehavior(typeof(TransactionBehavior<,>));
    config.AddDefaultBehaviors();
});

Behavior de Performance Monitoring (Incluido en paquete)

public class PerformanceBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IBaseCommand
{
    private readonly ILogger<PerformanceBehavior<TRequest, TResponse>> _logger;
    private readonly Stopwatch _timer;

    public PerformanceBehavior(ILogger<PerformanceBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger;
        _timer = new Stopwatch();
    }

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        _timer.Start();

        var response = await next();

        _timer.Stop();

        var elapsedMilliseconds = _timer.ElapsedMilliseconds;

        if (elapsedMilliseconds > 500) // Alertar si toma más de 500ms
        {
            var requestName = typeof(TRequest).Name;
            _logger.LogWarning(
                "Long Running Request: {Name} -> {ElapsedMilliseconds:N0} milliseconds",
                requestName, 
                elapsedMilliseconds
            );
        }

        return response;
    }
}

🎁 Patrón Result<T>

El patrón Result<T> permite manejar errores de forma funcional sin excepciones:

Crear Resultados

// Resultado exitoso con valor
var successResult = Result.Success(productId);

// Resultado exitoso sin valor (para comandos void)
var emptySuccess = Result.Success();

// Resultado fallido
var failureResult = Result.Failure<Product>(
    Error.NotFound("Product.NotFound", "Product not found")
);

// Tipos de errores disponibles
var notFoundError = Error.NotFound("Resource.NotFound", "Resource not found");
var validationError = Error.Validation("Validation.Failed", "Validation failed");
var conflictError = Error.Conflict("Resource.Conflict", "Resource already exists");
var unauthorizedError = Error.Unauthorized("Auth.Unauthorized", "Not authorized");

Trabajar con Resultados

// Verificar éxito/fallo
if (result.IsSuccess)
{
    var value = result.Value;
    // Procesar el valor
}
else if (result.IsFailure)
{
    var error = result.Error;
    Console.WriteLine($"Error: {error.Code} - {error.Message}");
}

// Métodos funcionales
result.OnSuccess(value => 
    Console.WriteLine($"Success: {value}")
);

result.OnFailure(error => 
    Console.WriteLine($"Error: {error.Message}")
);

// Transformar resultados
var mappedResult = result.Map(product => new ProductDto 
{ 
    Id = product.Id, 
    Name = product.Name 
});

// Obtener valor o default
var valueOrDefault = result.GetValueOrDefault(defaultProduct);

Encadenar Operaciones

var result = await GetProductAsync(id)
    .Map(product => UpdateProduct(product, newData))
    .OnSuccess(product => LogUpdate(product))
    .OnFailure(error => LogError(error));

📋 Orden de Ejecución de Behaviors

Los behaviors se ejecutan en el siguiente orden:

  1. ExceptionHandlingBehavior - Captura y maneja excepciones
  2. LoggingBehavior - Registra la ejecución del request (opcional)
  3. ValidationBehavior - Valida el request antes de ejecutar
  4. Behaviors Personalizados - Tus behaviors (ej: Transaction, Performance)
  5. Handler - Ejecuta la lógica de negocio real
Request → Exception → Logging → Validation → Custom Behaviors → Handler → Response

🔧 Configuración Completa

Configuración Básica

builder.Services.AddMediatorX(config =>
{
    // Registrar assemblies
    config.RegisterServicesFromAssembly(typeof(Program).Assembly);
    config.RegisterValidatorsFromAssembly(typeof(Program).Assembly);

    // Behaviors predeterminados
    config.AddDefaultBehaviors(
        includeValidation: true,
        includePerformance: true,
        includeExceptionHandling: true,
        includeLogging: true
    );
});

Configuración Avanzada

builder.Services.AddMediatorX(config =>
{
    // Registrar múltiples assemblies
    config
        .RegisterServicesFromAssembly(typeof(Program).Assembly)
        .RegisterServicesFromAssembly(typeof(ApplicationLayer).Assembly)
        .RegisterValidatorsFromAssembly(typeof(Program).Assembly);

    // Agregar behaviors personalizados (se ejecutan después de los default)
    config.AddOpenBehavior(typeof(TransactionBehavior<,>));
    config.AddOpenBehavior(typeof(PerformanceBehavior<,>));
    config.AddOpenBehavior(typeof(CachingBehavior<,>));

    // Behaviors predeterminados
    config.AddDefaultBehaviors(
        includeValidation: true,
        includeExceptionHandling: true,
        includePerformance: false, // Desactivar si no deseas tener metrica de performance
        includeLogging: false // Desactivar si usas behavior personalizado
    );
});

📊 Comandos vs Consultas

Comandos (ICommand)

  • Modifican el estado del sistema
  • Retornan Result<T> o Result
  • Pueden tener validación
  • Pueden generar eventos de dominio
// Comando con respuesta
public sealed record CreateProductCommand(...) : ICommand<Guid>;

// Comando sin respuesta
public sealed record DeleteProductCommand(Guid Id) : ICommand;

Consultas (IQuery)

  • Solo leen datos
  • No modifican el estado
  • Retornan Result<T>
  • Normalmente no tienen validación
public sealed record GetProductByIdQuery(Guid Id) : IQuery<ProductDto>;
public sealed record GetAllProductsQuery : IQuery<List<ProductDto>>;

📝 Logging

EV.MediatorX usa ILogger<T> para logging completo:

Configurar Logging en appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "EV.MediatorX": "Debug"
    }
  }
}

Niveles de Log

  • Debug: Detalles de ejecución, validación, behaviors
  • Information: Inicio/fin de comandos, eventos publicados
  • Warning: Validaciones fallidas, operaciones lentas
  • Error: Excepciones, fallos de handlers

Ejemplo de Logs

[INFO] Creating product: Laptop Pro
[DEBUG] Validating CreateProductCommand
[DEBUG] Executing CreateProductCommandHandler
[INFO] Product created with Id: 3fa85f64-5717-4562-b3fc-2c963f66afa6
[INFO] Publishing ProductCreatedEvent
[DEBUG] ProductCreatedEventHandler executed successfully

🏗️ Arquitectura y Patrones

Clean Architecture

EV.MediatorX se integra perfectamente con Clean Architecture:

Presentation Layer (API/Controllers)
    ↓ (usa IMediator)
Application Layer (Handlers, Validators, Events)
    ↓ (usa interfaces)
Domain Layer (Entities, Domain Events)
    ↓
Infrastructure Layer (Repositories, DbContext)

Estructura de Carpetas Recomendada en tu Proyecto

YourProject/
├── Controllers/
│   └── ProductsController.cs
├── Application/
│   ├── Products/
│   │   ├── Commands/
│   │   │   ├── CreateProduct/
│   │   │   │   ├── CreateProductCommand.cs
│   │   │   │   ├── CreateProductCommandHandler.cs
│   │   │   │   └── CreateProductCommandValidator.cs
│   │   │   └── UpdateProduct/
│   │   │   │   ...
│   │   │   └── DeleteProduct/
│   │   │   │   ...
│   │   ├── Queries/
│   │   │   ├── GetProductById/
│   │   │   │   ├── GetProductByIdQuery.cs
│   │   │   │   └── GetProductByIdQueryHandler.cs
│   │   │   └── GetAllProducts/
│   │   └── Events/
│   │       ├── ProductCreatedEvent.cs
│   │       └── ProductCreatedEventHandler.cs
│   └── Behaviors/
│       ├── TransactionBehavior.cs
│       └── PerformanceBehavior.cs
├── Domain/
│   └── Entities/
│       └── Product.cs
└── Infrastructure/
    └── Repositories/
        └── ProductRepository.cs

🛠️ Casos de Uso Comunes

CRUD Completo

Consulta el código de ejemplo incluido que implementa un CRUD completo de productos con:

  • ✅ Create (con validación)
  • ✅ Read (por ID y listado completo)
  • ✅ Update
  • ✅ Delete
  • ✅ Events
  • ✅ Validación automática
  • ✅ Manejo de errores

Operaciones Complejas

// Comando con múltiples pasos
public sealed record ProcessOrderCommand(Guid OrderId) : ICommand<OrderResult>;

public sealed class ProcessOrderCommandHandler : ICommandHandler<ProcessOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepository;
    private readonly IPaymentService _paymentService;
    private readonly IInventoryService _inventoryService;
    private readonly IPublisher _publisher;

    public async Task<Result<OrderResult>> HandleAsync(
        ProcessOrderCommand request, 
        CancellationToken ct)
    {
        // 1. Obtener orden
        var order = await _orderRepository.GetByIdAsync(request.OrderId, ct);
        if (order is null)
            return Result.Failure<OrderResult>(
                Error.NotFound("Order.NotFound", "Order not found")
            );

        // 2. Verificar inventario
        var inventoryCheck = await _inventoryService.CheckAvailabilityAsync(order, ct);
        if (!inventoryCheck.IsSuccess)
            return Result.Failure<OrderResult>(inventoryCheck.Error);

        // 3. Procesar pago
        var payment = await _paymentService.ProcessPaymentAsync(order, ct);
        if (!payment.IsSuccess)
            return Result.Failure<OrderResult>(payment.Error);

        // 4. Actualizar orden
        order.MarkAsProcessed(payment.Value.TransactionId);
        await _orderRepository.UpdateAsync(order, ct);

        // 5. Publicar evento
        await _publisher.PublishAsync(
            new OrderProcessedEvent(order.Id, payment.Value.TransactionId), 
            ct
        );

        return Result.Success(new OrderResult(order.Id, order.Status));
    }
}

🐛 Solución de Problemas

Handlers no se registran automáticamente

Problema: Los handlers no se encuentran.

Solución: Asegúrate de registrar el assembly correcto:

config.RegisterServicesFromAssembly(typeof(YourHandler).Assembly);

Validación no se ejecuta

Problema: Los validadores no se invocan.

Solución: Verifica que:

  1. Los validadores estén registrados
  2. El behavior de validación esté activado
config.RegisterValidatorsFromAssembly(typeof(Program).Assembly);
config.AddDefaultBehaviors(includeValidation: true);

Eventos no se publican

Problema: Los eventos no se disparan.

Solución: Asegúrate de:

  1. Heredar de EventAggregator<TKey>
  2. Usar RaiseEvent() para ir agregando o creando los eventos
  3. Publicar eventos después de guardar:
var events = entity.GetEvents();
entity.ClearEvents();

foreach (var @event in events)
{
    await _publisher.PublishAsync(@event, cancellationToken);
}

Errores de inyección de dependencias

Problema: Unable to resolve service for type 'IMediator'

Solución: Llama a AddMediatorX() en Program.cs:

builder.Services.AddMediatorX(config => { ... });

🚀 Mejores Prácticas

1. Separación de Responsabilidades

  • Un handler = una responsabilidad
  • Comandos para escritura, consultas para lectura
  • Mantén los handlers pequeños y enfocados

2. Validación

  • Siempre valida en el borde (API/Controller)
  • Usa validadores para lógica de validación compleja
  • Valida comandos, no consultas (generalmente)

3. Manejo de Errores

  • Usa el patrón Result<T> en lugar de excepciones para flujo de negocio
  • Reserva excepciones para errores inesperados
  • Proporciona códigos de error descriptivos

4. Domain Events

  • Usa eventos para efectos secundarios
  • Mantén los eventos inmutables (records)
  • Publica eventos después de persistir cambios

5. Performance

  • Usa CancellationToken en todos los métodos async
  • Implementa caching en consultas frecuentes
  • Monitorea performance con behaviors personalizados

6. Testing

  • Los handlers son fáciles de testear (solo dependen de interfaces)
  • Mockea dependencias en tests unitarios
  • Testea behaviors de forma aislada
[Fact]
public async Task Handle_ValidCommand_ReturnsSuccess()
{
    // Arrange
    var mockRepo = new Mock<IProductRepository>();
    var mockLogger = new Mock<ILogger<CreateProductCommandHandler>>();
    var handler = new CreateProductCommandHandler(mockRepo.Object, mockLogger.Object);
    var command = new CreateProductCommand("Test", "Description", 10.0m, 5);

    // Act
    var result = await handler.HandleAsync(command, CancellationToken.None);

    // Assert
    Assert.True(result.IsSuccess);
    Assert.NotEqual(Guid.Empty, result.Value);
    mockRepo.Verify(x => x.AddAsync(It.IsAny<Product>(), It.IsAny<CancellationToken>()), Times.Once);
}

📄 Licencia

Copyright © 2025 EV. Todos los derechos reservados.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
8.0.0 210 10/30/2025

✨ Características

- 🎯 **Comandos y Consultas (CQRS)** - Separación clara entre operaciones de lectura y escritura.
- 🔄 **Pipeline Behaviors** - Middleware personalizable para logging, validación y más.
- ✅ **Validación Integrada** - Sistema de validación fluido y extensible.
- 📢 **Domain Events** - Manejo robusto de eventos de dominio con notificaciones.
- 🛡️ **Manejo de Excepciones** - Captura automática y gestión centralizada de errores.
- 🎁 **Patrón Result** - Manejo funcional de errores sin excepciones.
- ⚡ **Async/Await** - API completamente asíncrona para máximo rendimiento.
- 📦 **Dependency Injection** - Integración perfecta con el contenedor DI de .NET.
- 🔧 **Altamente Configurable** - Adapta el comportamiento a tus necesidades.

🔐 Seguridad y Privacidad (DataSanitizer)

- ✅ Cumple con GDPR y normativas de privacidad.
- 🚫 Evita exponer credenciales en logs.
- 🐛 Permite debug sin comprometer seguridad.
- 🔍 Detecta automáticamente campos sensibles por nombre.
- 📝 Campos personalizados para datos sensibles: "identification", "password", "token", "key", "secret", "authorization", "connectionstring", "apikey", "clientsecret", "bearer", "credential", "pin", "ssn", "dni", "nif", "passport", "username", "email", "value", "owner", "pwd", "createdby", "updatedby", "deletedby".