如何在单个ASP.NET Core MVC应用中为Web API和MVC控制器配置独立异常处理
问题描述
我有一个基于.NET 7的ASP.NET Core MVC项目,同时包含Web API控制器和MVC控制器。需要实现:
- MVC控制器抛出异常时,由ErrorController处理并返回Error或NotFound视图;
- Web API控制器抛出异常时,由全局异常中间件处理并返回JSON格式的APIResponse响应。
当前实现的问题是:根据ExceptionMiddleware在管道中的位置,只能统一使用ErrorController或ExceptionMiddleware其中一种处理所有异常,无法区分API和MVC请求分别处理。
用户提供的代码
ErrorController.cs
public class ErrorController : Controller { [Route("Error/{statusCode}")] public IActionResult HttpStatusCodeHandler(int statusCode) { switch (statusCode) { case 404: ViewBag.ErrorMessage = "Sorry, the resource you requested could not be found"; break; default: break; } return View("NotFound"); } [Route("Error")] [AllowAnonymous] public IActionResult Error() { var exceptionDetails = HttpContext.Features.Get<IExceptionHandlerPathFeature>(); TempData["ExceptionMessage"] = exceptionDetails?.Error.Message; return View("Error"); } }
ExceptionMiddleware.cs
public class ExceptionMiddleware : IMiddleware { private readonly APIResponse _apiResponse; public ExceptionMiddleware(APIResponse apiResponse) { _apiResponse = apiResponse; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { try { await next(context); } catch(CustomerNotFoundException ex) { _apiResponse.StatusCode = System.Net.HttpStatusCode.NotFound; _apiResponse.ErrorMessages = new List<string> { ex.Message }; _apiResponse.IsSuccess = false; context.Response.StatusCode = (int)_apiResponse.StatusCode; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject(_apiResponse)); } catch (Exception ex) { _apiResponse.StatusCode = System.Net.HttpStatusCode.InternalServerError; _apiResponse.ErrorMessages = new List<string> { ex.Message }; _apiResponse.IsSuccess = false; context.Response.StatusCode = (int)_apiResponse.StatusCode; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject(_apiResponse)); } } }
APIResponse.cs
public class APIResponse { public APIResponse() { ErrorMessages = new List<string>(); } public HttpStatusCode StatusCode { get; set; } public bool IsSuccess { get; set; } = true; public List<string> ErrorMessages { get; set; } public object? Result { get; set; } }
CustomersController.cs (Web API控制器)
[ApiController] public class CustomersController : ControllerBase { private readonly APIResponse _apiResponse; public CustomersController(APIResponse apiResponse) { _apiResponse = apiResponse; } [Route("api/customers/{id}")] public async Task<ActionResult<APIResponse>> Get(int id) { throw new CustomerNotFoundException("The customer is not registered in the system."); return Ok(_apiResponse); } }
HomeController.cs (MVC控制器)
public class HomeController : Controller { public IActionResult Index() { throw new Exception("Unable to render Dashboard."); return View(); } }
Program.cs
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); builder.Services.AddTransient<APIResponse>(); builder.Services.AddTransient<ExceptionMiddleware>(); var app = builder.Build(); app.UseExceptionHandler("/Error"); app.UseStatusCodePagesWithReExecute("/Error/{0}"); app.UseHsts(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseMiddleware<ExceptionMiddleware>(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
解决方案
核心思路是:让ExceptionMiddleware仅处理API请求的异常,非API请求的异常交给UseExceptionHandler转发到ErrorController处理,同时调整中间件顺序确保流程正确。
1. 修改ExceptionMiddleware,仅处理API请求
修改中间件逻辑,先判断请求是否属于API请求,仅在API请求时捕获异常并返回JSON;非API请求的异常直接传递给后续中间件处理。
using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using System.Net; public class ExceptionMiddleware : IMiddleware { private readonly ILogger<ExceptionMiddleware> _logger; public ExceptionMiddleware(ILogger<ExceptionMiddleware> logger) { _logger = logger; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { // 通过端点元数据识别ApiController标记,准确判断是否为API请求 var endpoint = context.GetEndpoint(); bool isApiRequest = endpoint?.Metadata.GetMetadata<ApiControllerAttribute>() != null; try { await next(context); } catch (Exception ex) when (isApiRequest) { _logger.LogError(ex, "API请求异常"); var apiResponse = new APIResponse { IsSuccess = false, ErrorMessages = new List<string> { ex.Message } }; if (ex is CustomerNotFoundException) { apiResponse.StatusCode = HttpStatusCode.NotFound; } else { apiResponse.StatusCode = HttpStatusCode.InternalServerError; } context.Response.StatusCode = (int)apiResponse.StatusCode; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject(apiResponse)); } // 非API请求的异常不捕获,交给UseExceptionHandler处理 } }
2. 调整Program.cs的中间件顺序
将ExceptionMiddleware注册在UseExceptionHandler之前,确保API请求的异常先被中间件捕获,非API请求的异常则传递到UseExceptionHandler处理。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); // 注册异常中间件,添加日志服务 builder.Services.AddTransient<ExceptionMiddleware>(); builder.Services.AddLogging(); var app = builder.Build(); // 先注册API异常处理中间件 app.UseMiddleware<ExceptionMiddleware>(); // 再注册MVC异常处理的ExceptionHandler app.UseExceptionHandler("/Error"); app.UseStatusCodePagesWithReExecute("/Error/{0}"); app.UseHsts(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
3. 优化说明
- API请求判断方式:通过
ApiControllerAttribute元数据判断,比路径匹配更灵活,只要控制器标记了[ApiController]就会被识别为API请求,不受路径规则限制。 - APIResponse实例化:在catch块中直接创建
APIResponse实例,避免注入带来的线程安全问题。 - 日志添加:添加日志记录异常信息,便于后续问题排查。
内容的提问来源于stack exchange,提问作者Simant
相关产品推荐
相关产品推荐

