处理命令的常见要求之一是输入验证。如果没有适当的验证,无效的命令可能会影响应用程序的状态。在这里,我们将集成 FluentValidation,这是一个用于声明性验证的常用 .NET 库。
首先安装 FluentValidation 包:
dotnet add package FluentValidation
dotnet add package FluentValidation.DependencyInjectionExtensions
让我们为 .这可确保任何商品创建请求在处理之前都遵守特定规则。CreateProductCommand
using FluentValidation;
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
public CreateProductCommandValidator()
{
RuleFor(x => x.Name).NotEmpty().WithMessage("Product name is required.");
RuleFor(x => x.Price).GreaterThan(0).WithMessage("Product price must be greater than zero.");
}
}
接下来,我们将此验证器集成到 MediatR 管道中,以便每个命令在到达处理程序之前都经过验证。
在:Program.cs
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
现在,定义运行验证器的 :ValidationBehavior
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var context = new ValidationContext<TRequest>(request);
var validationFailures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (validationFailures.Any())
{
throw new ValidationException(validationFailures);
}
return await next();
}
}
在实际应用程序中,您通常需要解决横切关注点,例如日志记录、缓存和事务管理。MediatR 允许我们通过管道行为优雅地处理这些问题。
让我们实现一个简单的日志记录行为来记录所有传入的命令和查询:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
_logger.LogInformation($"Handling {typeof(TRequest).Name}");
var response = await next();
_logger.LogInformation($"Handled {typeof(TRequest).Name}");
return response;
}
}
您现在可以在文件中注册此行为:Program.cs
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
使用 CQRS 时,处理域事件是一项常见要求。例如,在创建产品时,您可能希望触发其他操作,例如发送通知或更新外部系统。
让我们定义一个在成功创建产品后引发的 a:ProductCreatedEvent
public class ProductCreatedEvent : INotification
{
public int ProductId { get; }
public ProductCreatedEvent(int productId)
{
ProductId = productId;
}
}
修改 以引发事件:CreateProductCommandHandler
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, int>
{
private readonly ApplicationDbContext _context;
private readonly IMediator _mediator;
public CreateProductCommandHandler(ApplicationDbContext context, IMediator mediator)
{
_context = context;
_mediator = mediator;
}
public async Task<int> Handle(CreateProductCommand request, CancellationToken cancellationToken)
{
var product = new Product { Name = request.Name, Price = request.Price };
_context.Products.Add(product);
await _context.SaveChangesAsync(cancellationToken);
// Publish domain event
await _mediator.Publish(new ProductCreatedEvent(product.Id));
return product.Id;
}
}
现在,定义一个处理程序来响应 :ProductCreatedEvent
public class ProductCreatedEventHandler : INotificationHandler<ProductCreatedEvent>
{
private readonly ILogger<ProductCreatedEventHandler> _logger;
public ProductCreatedEventHandler(ILogger<ProductCreatedEventHandler> logger)
{
_logger = logger;
}
public Task Handle(ProductCreatedEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation($"Product with ID {notification.ProductId} has been created.");
return Task.CompletedTask;
}
}
CQRS 允许您独立于命令优化查询。您可以使用缓存、读取优化数据库甚至投影来提高性能。
以下是如何向查询处理程序添加缓存的示例:
public class CachedGetProductByIdHandler : IRequestHandler<GetProductByIdQuery, Product>
{
private readonly IMemoryCache _cache;
private readonly ApplicationDbContext _context;
public CachedGetProductByIdHandler(IMemoryCache cache, ApplicationDbContext context)
{
_cache = cache;
_context = context;
}
public async Task<Product> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
{
return await _cache.GetOrCreateAsync($"Product_{request.Id}", async entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(10);
return await _context.Products.FindAsync(request.Id);
});
}
}
在本文中,我们探讨了在 ASP.NET Core 中使用 Mediator 模式的高级 CQRS 技术。我们学习了如何实现输入验证、处理管道行为的横切关注点,以及使用域事件来启用事件驱动的架构。最后,我们介绍了如何使用缓存优化查询性能。
借助这些高级技术,您的 CQRS 实施已准备好用于实际生产,确保您的 ASP.NET Core 应用程序保持可扩展性、可维护性和高性能。