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

ASP.NET控制器中Mediator调用:用try-catch还是更优异常处理方案?

你的异常处理方案可以优化,推荐更解耦的实现方式

你的当前写法能实现功能,但存在两个明显问题:

  • 业务层与Web层耦合:Handler属于业务逻辑层,却直接使用了HttpResponseException、IActionResult这类Asp.Net Core Web专属类型。如果以后换Web框架,或者想在非Web场景复用这部分业务逻辑,就得大幅修改代码。
  • 重复代码冗余:每个控制器方法都要写try-catch捕获特定异常,后续新增接口时会重复造轮子,维护成本高。

下面是两种更优的实现方案:

方案一:自定义业务异常 + 全局异常过滤器

核心思路是让业务层只关注业务逻辑,抛出纯业务异常,Web层通过全局统一处理异常转换成Http响应。

步骤1:定义业务异常类

public class AppealNotFoundException : Exception
{
    public AppealNotFoundException(string message) : base(message)
    {
    }
}

步骤2:修改Handler,解耦Web层依赖

把返回值改成MediatR的Unit(表示无业务返回值),抛出自定义业务异常:

public async Task<Unit> Handle(CloseAppealsCommand request, CancellationToken cancellationToken)
{
    var appeal = await repository.GetAsync(request.AppealId);

    if (appeal is null)
        throw new AppealNotFoundException("Such appeal does not exist");

    appeal.Status = Domain.Enums.Status.Closed;
    await repository.SaveAsync();
    return Unit.Value;
}

步骤3:简化控制器代码

去掉try-catch,直接调用MediatR,响应状态码统一在控制器返回:

// 修正路由参数写法,原来的路由无法正确捕获参数,改成{AppealId}
[HttpPost("CloseAppeals/{AppealId}")]
public async Task<IActionResult> CloseAppeal(int AppealId)
{
    await _mediator.Send(new CloseAppealsCommand(AppealId));
    return StatusCode(201);
}

步骤4:实现全局异常过滤器

在Web层统一捕获异常,转换成对应Http响应:

public class GlobalExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        // 处理业务异常
        if (context.Exception is AppealNotFoundException appealEx)
        {
            context.Result = new BadRequestObjectResult(appealEx.Message);
            context.ExceptionHandled = true;
            return;
        }

        // 处理其他未预期异常
        context.Result = new ObjectResult("服务器发生未预期错误")
        {
            StatusCode = StatusCodes.Status500InternalServerError
        };
        context.ExceptionHandled = true;
    }
}

步骤5:注册全局过滤器

在Program.cs里把过滤器添加到控制器选项:

builder.Services.AddControllers(options =>
{
    options.Filters.Add<GlobalExceptionFilter>();
});

方案二:结合MediatR管道行为做前置验证

如果多个命令都需要验证“申诉是否存在”,可以把验证逻辑抽成MediatR的管道行为,进一步复用代码,让Handler更专注业务逻辑。

步骤1:定义通用接口

让需要验证申诉ID的命令实现这个接口:

public interface IHasAppealId
{
    int AppealId { get; }
}

// 修改命令类实现接口
public class CloseAppealsCommand : IRequest<Unit>, IHasAppealId
{
    public int AppealId { get; set; }

    public CloseAppealsCommand(int appealId)
    {
        AppealId = appealId;
    }
}

步骤2:实现验证管道行为

public class ValidateAppealExistsBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>, IHasAppealId
{
    private readonly IAppealRepository _repository;

    public ValidateAppealExistsBehavior(IAppealRepository repository)
    {
        _repository = repository;
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        var appeal = await _repository.GetAsync(request.AppealId);
        if (appeal is null)
            throw new AppealNotFoundException("Such appeal does not exist");

        // 验证通过,继续执行后续Handler
        return await next();
    }
}

步骤3:注册管道行为

在Program.cs里注册这个行为:

builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidateAppealExistsBehavior<,>));

步骤4:简化Handler代码

此时Handler里完全不需要验证逻辑,直接处理业务:

public async Task<Unit> Handle(CloseAppealsCommand request, CancellationToken cancellationToken)
{
    var appeal = await repository.GetAsync(request.AppealId);
    appeal.Status = Domain.Enums.Status.Closed;
    await repository.SaveAsync();
    return Unit.Value;
}

总结

推荐采用上述两种方案,它们解决了业务层与Web层耦合的问题,同时避免了重复代码,让代码结构更清晰、维护性更强。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 01:21:18