简介
目录
在使用 MediatR 框架时,我们经常需要对命令对象进行验证。为了实现自动验证,我们可以使用 MediatR 的管道行为在命令处理之前执行验证逻辑。本文档将介绍如何在控制器外部使用 MediatR 的管道行为来自动验证实现了 ICommand/IRequest 接口的类,并提供正确地注册验证器和管道行为的方法。
1. MediatR项目框架
使用Mediator来构建项目框架具有一系列的优点和缺点。以下是对这些优缺点的详细分析:
优点:
- 解耦与简化通信:Mediator架构的核心思想是通过一个中介者对象来管理系统中各个组件之间的交互,从而实现了对象之间的解耦。这使得组件之间的通信更加简单,降低了系统的复杂性,提高了代码的可读性和可维护性。
- 易于扩展和修改:由于系统的组件之间是通过中介者进行通信的,因此当需要添加或删除组件时,只需要修改中介者的代码,而不需要修改其他组件的代码。这使得系统的扩展和修改变得更加容易。
- 集中控制:中介者对象可以集中控制组件之间的交互,确保它们按照预定的规则进行通信。这有助于减少系统中的错误和冲突,提高系统的稳定性和可靠性。
缺点:
- 性能开销:由于所有的组件都通过中介者进行通信,这可能会导致一定的性能开销。特别是在处理大量消息或复杂交互时,中介者可能成为性能瓶颈。
- 过度依赖中介者:如果系统过度依赖中介者对象,那么中介者的设计和实现可能会变得非常复杂。一旦中介者出现问题,整个系统可能会受到影响。
- 不适用于所有场景:虽然Mediator架构在某些场景下非常有用,但它并不适用于所有类型的项目。在某些情况下,使用其他架构可能更为合适。
ProductManage/
│
├── Controllers/ // 控制器文件夹
│ └── ProductController.cs
│
├── Models/ // 模型文件夹
│ └── Product.cs
│
├── Application/ // 应用层文件夹
│ ├── Commands/ // 命令文件夹
│ │ └── CreateProductCommand.cs
│ │
│ ├── Queries/ // 查询文件夹
│ │ └── GetProductQuery.cs
│ │
│ └── Handlers/ // 处理程序文件夹
│ ├── CreateProductCommandHandler.cs
│ └── GetProductQueryHandler.cs
│
├── Infrastructure/ // 基础设施层文件夹
│ └── Persistence/ // 持久化文件夹
│ └── DbContext.cs
│
├── Domain/ // 领域层文件夹
│ └── Entities/ // 实体文件夹
│ └── Product.cs
│
├── Validators/ // 验证器文件夹
│ └── CreateProductCommandValidator.cs
│
└── Startup.cs // 启动文件
2. 实现步骤
步骤 1:编写管道行为
编写一个管道行为来执行验证逻辑,这个管道行为将在命令处理之前执行验证。由于ICommand继承自IRequest接口,所以只需要添加IRequest管道拦截器
using FluentValidation;
using MediatR;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
public class ValidationQueryBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationQueryBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
// 执行所有验证器
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(error => error != null)
.ToList();
var failuresAsync = _validators
.Select(async v => await v.ValidateAsync(request))
.SelectMany(result => result.Result.Errors)
.Where(error => error != null)
.ToList();
failures.AddRange(failuresAsync);
if (failures.Any())
{
throw new DecBussinessExpection(failures.FirstOrDefault().ErrorMessage, Convert.ToInt32(failures.FirstOrDefault().ErrorCode));
}
// 如果通过验证,则继续执行下一个处理程序
return await next();
}
}
步骤 2:注册验证器和管道行为
接下来,我们需要将验证器和管道行为注册到 DI 容器中,以便在 MediatR 的管道中使用它们进行自动验证。
using FluentValidation.AspNetCore;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
var assemblies = Directory
.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "ProductManage.Application.*.dll", SearchOption.TopDirectoryOnly)
.Select(Assembly.LoadFrom).ToArray();
// 注册 MediatR
builder.Services.AddMediatR(assemblies);
// 注册 FluentValidation 验证器
builder.Services.AddValidatorsFromAssemblies(assemblies)
.AddFluentValidationAutoValidation()
.AddFluentValidationClientsideAdapters().AddMemoryCache().AddRuleEngine();
// 注册管道行为
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationQueryBehavior<,>));
}
}
步骤 3:定义命令类
我们需要定义一个实现了 ICommand 接口的命令类。这个命令类将在 MediatR 管道中被自动验证。
public class CreateProductCommand : ICommand<ProductDTO>
{
public string Name { get; set; }
public decimal Price { get; set; }
}
步骤 4:定义处理程序
定义一个实现ICommandHandler接口的处理程序类,该类主要实现业务逻辑。
public class CreateProductHandler : ICommandHandler<CreateProductCommand, ProductDto>
{
public Task<ProductDto> Handle(CreateProductCommand request, CancellationToken cancellationToken)
{
//实现业务逻辑
}
}
步骤 5:编写命令验证器
然后,我们需要编写一个验证器来验证命令类。这个验证器将使用 FluentValidation 等验证库来定义验证规则。
using FluentValidation;
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
private IProductService _productService;
public CreateProductCommandValidator(IProductService productService)
{
_productService=productService;
}
public override async Task<ValidationResult> ValidateAsync(ValidationContext<CreateProductCommand> context, CancellationToken cancellation = default)
{
RuleFor(it => it.Name).NotNull().CustomException(CustomerErrorMsg.名称不能为空);
var command = context.InstanceToValidate;
if (await _decCustomerMainService.CheckData())
{
return new ValidationResult(new[] { new CustomValidationFailure(ProductErrorMsg.该商品不存在) });
}
return await base.ValidateAsync(context, cancellation);
}
}
步骤 6:使用方法
[HttpPost()]
public async Task<ApiResponse<ProductDTO>> Create(CreateProductReq requestDto)
{
var productInfo= await _mediator.Send(new CreateProductCommand(requestDto));
return new ApiResponse<ProductDTO>() { Code = (int)ApiErrorCode.成功, Message = "成功", Result = productInfo};
}
3. 总结
通过以上步骤,我们成功地实现了在控制器外部使用 MediatR 的管道行为来自动验证实现了 ICommand 接口的类。首先,我们定义了命令类和命令验证器。然后,我们注册了验证器和管道行为到 DI 容器中,并编写了管道行为来执行验证逻辑。最终,我们可以在控制器中发送命令,而管道行为会自动执行验证逻辑,并在需要时抛出验证异常。
注:控制器方法参数,FluentValidation会自动拦截验证,不需要额外的在管道里配置拦截验证。