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
相关产品推荐
相关产品推荐

