ASP.NET Core 6中Mediator中间件如何处理OneOf返回结果?
问题根源
你之前的OneOf中间件思路有误:RequestDelegate的InvokeAsync方法返回的是Task(无返回值),它的作用是处理请求并通过HttpContext输出响应,而非返回业务处理结果。MediatR的请求处理结果是在Controller的Action方法中返回的,所以中间件无法直接获取这个结果,自然会出现赋值void的错误。
另外,你的ValidationCoproduct包含ValidationException类型,这和你“避免抛出异常”的初衷矛盾——既然要用OneOf替代异常,就应该直接返回验证错误的结构化数据,而非异常对象。
解决方案
我们需要调整两个核心部分:
- 重构OneOf的协积类型,用验证错误DTO替代异常
- 用ActionFilter替代中间件,在MVC管道中捕获并处理OneOf类型的返回结果
步骤1:重构ValidationCoproduct和验证错误DTO
首先定义验证错误的DTO,用来封装FluentValidation的验证失败信息:
public record ValidationError(string PropertyName, string ErrorMessage); public record ValidationResultDto(IEnumerable<ValidationError> Errors); [GenerateOneOf] public partial class Result<T> : OneOfBase<T, ValidationResultDto> { }
这里把原来的ValidationCoproduct改名为更通用的Result<T>,分支是业务响应或验证错误DTO,完全移除异常类型。
步骤2:修正ValidationBehavior
调整管道行为,直接返回验证错误DTO而非异常:
namespace TaskScheduler.Service.PipelineBehaviors; public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TResponse : IOneOf { private readonly IEnumerable<IValidator<TRequest>> _validators; public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) => _validators = validators; public async ValueTask<TResponse> Handle( TRequest request, CancellationToken cancellationToken, MessageHandlerDelegate<TRequest, TResponse> next) { if (!_validators.Any()) { return await next(request, cancellationToken); } var validationContext = new ValidationContext<TRequest>(request); var failures = (await Task.WhenAll( _validators.Select(v => v.ValidateAsync(validationContext, cancellationToken)))) .SelectMany(vf => vf.Errors) .Where(e => e != null) .Select(e => new ValidationError(e.PropertyName, e.ErrorMessage)) .ToList(); if (failures.Any()) { // 生成对应的OneOf分支实例 var validationResult = new ValidationResultDto(failures); return (TResponse)OneOf.OneOf.FromObject(validationResult); } return await next(request, cancellationToken); } }
这里泛型约束改为TResponse : IOneOf,确保处理的是OneOf类型的响应,并且通过OneOf.FromObject直接生成验证错误分支的实例,完全避免抛出异常。
步骤3:实现OneOfActionFilter
用ActionFilter来捕获Action返回的Result<T>,并根据分支生成对应的HTTP响应:
public class OneOfActionFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { } public void OnActionExecuted(ActionExecutedContext context) { if (context.Result is ObjectResult objectResult && objectResult.Value is IOneOf oneOfResult) { oneOfResult.Switch( success => { // 成功分支:保持原响应,状态码200 context.Result = new OkObjectResult(success); }, (ValidationResultDto validationErrors) => { // 验证错误分支:返回400 Bad Request context.Result = new BadRequestObjectResult(new { Errors = validationErrors.Errors, Message = "请求参数验证失败" }); } ); } } }
这个Filter会在Action执行完成后,检查返回值是否为OneOf类型,然后根据不同分支设置对应的HTTP响应状态码和内容。
步骤4:调整Request和Handler
修改你的Query和Handler,适配新的Result<T>类型:
// BrowseQuery.cs public record BrowseQuery(BrowseParameters BrowseParameters) : IRequest<Result<BrowseScheduleTaskResponse>>; // BrowseQueryHandler.cs public class BrowseQueryHandler : IRequestHandler<BrowseQuery, Result<BrowseScheduleTaskResponse>> { public async ValueTask<Result<BrowseScheduleTaskResponse>> Handle( BrowseQuery request, CancellationToken cancellationToken) { int? count = await GetCountAsync(request.BrowseParameters, cancellationToken); var tasks = await GetRequestedScheduleTasks(request, cancellationToken); // 直接返回成功分支的实例 return new BrowseScheduleTaskResponse(tasks, count); } }
Handler只需返回业务响应对象,OneOf会自动生成成功分支的实例(得益于源代码生成)。
步骤5:注册服务
在Program.cs中注册所有相关服务:
// 注册FluentValidation验证器 builder.Services.AddValidatorsFromAssemblyContaining<BrowseQuery>(); // 注册Mediator(源代码生成版) builder.Services.AddMediator(options => { options.RegisterServicesFromAssemblyContaining<BrowseQueryHandler>(); // 添加ValidationBehavior到管道 options.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); }); // 注册OneOfActionFilter builder.Services.AddControllers(options => { options.Filters.Add<OneOfActionFilter>(); });
为什么这个方案可行?
- 避免异常:整个流程从验证到业务处理都不抛出异常,用OneOf的分支类型明确表示成功/失败状态
- 适配MVC管道:ActionFilter是MVC管道的一部分,能够直接获取Action的返回结果(也就是MediatR的处理结果),无需依赖中间件的RequestDelegate
- 类型安全:通过泛型约束和OneOf的源代码生成,确保所有响应类型都是类型安全的,编译期即可发现错误
内容的提问来源于stack exchange,提问作者KUTlime

