You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 6中Mediator中间件如何处理OneOf返回结果?

解决ASP.NET Core 6 WebAPI中用OneOf替代异常处理的问题

问题根源

你之前的OneOf中间件思路有误:RequestDelegate的InvokeAsync方法返回的是Task(无返回值),它的作用是处理请求并通过HttpContext输出响应,而非返回业务处理结果。MediatR的请求处理结果是在Controller的Action方法中返回的,所以中间件无法直接获取这个结果,自然会出现赋值void的错误。

另外,你的ValidationCoproduct包含ValidationException类型,这和你“避免抛出异常”的初衷矛盾——既然要用OneOf替代异常,就应该直接返回验证错误的结构化数据,而非异常对象。

解决方案

我们需要调整两个核心部分:

  1. 重构OneOf的协积类型,用验证错误DTO替代异常
  2. 用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>();
});

为什么这个方案可行?

  1. 避免异常:整个流程从验证到业务处理都不抛出异常,用OneOf的分支类型明确表示成功/失败状态
  2. 适配MVC管道:ActionFilter是MVC管道的一部分,能够直接获取Action的返回结果(也就是MediatR的处理结果),无需依赖中间件的RequestDelegate
  3. 类型安全:通过泛型约束和OneOf的源代码生成,确保所有响应类型都是类型安全的,编译期即可发现错误

内容的提问来源于stack exchange,提问作者KUTlime

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 06:20:11