使用MediatR与C#时FluentValidation响应转换错误求助
解决FluentValidation验证中间件的类型转换问题
当前使用MediatR结合FluentValidation实现验证中间件时,遇到类型转换错误,目标是让所有IRequest请求类验证失败时,返回包含错误字段及描述的统一BaseResponse<T>格式响应,以下是问题分析及解决方案:
相关代码
请求类
public class CreateCivilStatusCommand : IRequest<BaseResponse<CivilStatusResponseDto>> { #region Properties public string? CivilStatusId { get; set; } public string? CivilStatusName { get; set; } public string? CreateBy { get; set; } public DateTime CreationDate { get; set; } public DateTime ModifiedDate { get; set; } #endregion }
Handler实现
public async Task<BaseResponse<CivilStatusResponseDto>> Handle(CreateCivilStatusCommand request, CancellationToken cancellationToken) { BaseResponse<CivilStatusResponseDto> response = new BaseResponse<CivilStatusResponseDto>(); try { CivilStatus civilStatus = await _civilStatusRepository.SaveCivilStatus(_mapper.Map<CivilStatus>(request)); response.IsSuccess = true; response.Data = _mapper.Map<CivilStatusResponseDto>(civilStatus); response.Message = GlobalMessages.MESSAGE_SAVE; } catch (Exception ex) { if (ex.Source is not null && ex.Source.Contains("SqlClient")) { response.IsSuccess = false; response.Message = ex.Message; } else { response.IsSuccess = false; response.Message = GlobalMessages.MESSAGE_EXCEPTION; WatchLogger.Log(ex.Message); } } return response; }
验证规则
public CreateCivilStatusValidator() { RuleFor(x => x.CivilStatusId) .NotNull().WithMessage("El campo Codigo no puede ser nulo.") .NotEmpty().WithMessage("El campo Codigo no puede ser vacio.") .MaximumLength(1).WithMessage("La cantidad de caracteres permitidos, para el campo Codigo es de 1 caracter."); RuleFor(x => x.CivilStatusName) .NotNull().WithMessage("El campo Estado Civil no puede ser nulo.") .NotEmpty().WithMessage("El campo Estado Civil no puede ser vacio.") .MaximumLength(15).WithMessage("La cantidad de caracteres permitidos, para el campo Estado Civil son 15 caracteres."); RuleFor(x => x.CreateBy) .NotNull().WithMessage("El campo Creado Por no puede ser nulo.") .NotEmpty().WithMessage("El campo Creado Por no puede ser vacio.") .EmailAddress().WithMessage("En el campo Creado Por se debe de ingresar un correo electronico valido.") .MaximumLength(60).WithMessage("La cantidad de caracteres permitidos, para el campo Creado por son 60 caracteres."); RuleFor(x => x.CreationDate) .NotNull().WithMessage("El campo Fecha de Creacion no puede ser nulo."); RuleFor(x => x.ModifiedDate) .NotNull().WithMessage("El campo Fecha de Modificacion no puede ser nulo."); }
基础响应类
public class BaseResponse<T> { #region Properties public bool IsSuccess { get; set; } public T? Data { get; set; } public string? Message { get; set; } public IEnumerable<BaseError>? Errors { get; set; } #endregion }
错误实体类
public class BaseError { #region Properties public string? PropertyName { get; set; } public string? ErrorMessage { get; set; } #endregion }
存在问题的验证中间件
public class ValidationMiddleware<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> { #region Properties private readonly IEnumerable<IValidator<TRequest>> _validators; #endregion #region Constructors public ValidationMiddleware(IEnumerable<IValidator<TRequest>> validators) { _validators = validators; } #endregion #region Methods public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { ValidationContext<TRequest> context = new ValidationContext<TRequest>(request); ValidationResult[] validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken))); List<ValidationFailure> failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList(); if (failures.Count > 0) { BaseResponse<TResponse> response = new BaseResponse<TResponse> { IsSuccess = false, Errors = failures.Select(error => new BaseError { PropertyName = error.PropertyName, ErrorMessage = error.ErrorMessage }).ToList(), Message = GlobalMessages.MESSAGE_VALIDATE, }; return (TResponse)(object)response; } return await next(); } #endregion }
问题原因
核心错误是类型嵌套不匹配:
- 请求类
CreateCivilStatusCommand实现的是IRequest<BaseResponse<CivilStatusResponseDto>>,因此中间件中的TResponse实际类型是BaseResponse<CivilStatusResponseDto> - 但原中间件中创建的是
BaseResponse<TResponse>,也就是BaseResponse<BaseResponse<CivilStatusResponseDto>>,与要求返回的TResponse类型完全不符,强制类型转换必然失败。
解决方案
修改中间件的泛型约束,直接创建符合要求的TResponse实例,而非嵌套包装:
修改后的验证中间件
public class ValidationMiddleware<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TResponse : BaseResponse<object>, new() { private readonly IEnumerable<IValidator<TRequest>> _validators; public ValidationMiddleware(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 validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken))); var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList(); if (failures.Any()) { var response = new TResponse { IsSuccess = false, Message = GlobalMessages.MESSAGE_VALIDATE, Errors = failures.Select(error => new BaseError { PropertyName = error.PropertyName, ErrorMessage = error.ErrorMessage }).ToList() }; return response; } return await next(); } }
优化说明
- 给
TResponse添加泛型约束:where TResponse : BaseResponse<object>, new(),确保TResponse是BaseResponse的泛型实例且具备无参构造函数 - 直接实例化
TResponse并填充错误信息,避免类型嵌套问题 - 如果需要更精准的泛型匹配,可使用三泛型参数版本:
public class ValidationMiddleware<TRequest, TResponse, TData> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TResponse : BaseResponse<TData>, new() { private readonly IEnumerable<IValidator<TRequest>> _validators; public ValidationMiddleware(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 validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken))); var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList(); if (failures.Any()) { var response = new TResponse { IsSuccess = false, Message = GlobalMessages.MESSAGE_VALIDATE, Errors = failures.Select(error => new BaseError { PropertyName = error.PropertyName, ErrorMessage = error.ErrorMessage }).ToList() }; return response; } return await next(); } }
依赖注入注册
根据使用的中间件版本,选择对应的注册方式:
// 双泛型版本注册 services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationMiddleware<,>)); // 三泛型版本注册 services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationMiddleware<,,>));
内容的提问来源于stack exchange,提问作者Ing. Jose Valera
相关产品推荐
相关产品推荐

