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

如何在单个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:17:16