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

如何在ASP.NET Core MVC异常处理中间件中实现内容协商响应?

在ASP.NET Core MVC中间件中实现内容协商的异常响应

要实现和控制器ObjectResult完全一致的内容协商与序列化行为,核心是复用ASP.NET Core框架内置的输出格式化器选择机制,而不是硬绑定单个JsonOutputFormatter。下面是具体的实现方案:

1. 调整中间件的依赖注入

我们需要替换掉单个JsonOutputFormatter的依赖,改用框架提供的IOutputFormatterSelector(负责根据请求选择合适的输出格式化器),同时保留IHttpResponseStreamWriterFactory和全局的MvcOptions配置。修改后的中间件构造函数如下:

public class UnhandledExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IOutputFormatterSelector _outputFormatterSelector;
    private readonly IHttpResponseStreamWriterFactory _streamWriterFactory;
    private readonly MvcOptions _mvcOptions;

    public UnhandledExceptionMiddleware(
        RequestDelegate next,
        IOutputFormatterSelector outputFormatterSelector,
        IHttpResponseStreamWriterFactory streamWriterFactory,
        IOptions<MvcOptions> mvcOptions)
    {
        _next = next;
        _outputFormatterSelector = outputFormatterSelector;
        _streamWriterFactory = streamWriterFactory;
        _mvcOptions = mvcOptions.Value;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    // 异常处理逻辑见下一步
}

2. 实现内容协商的异常响应逻辑

在HandleExceptionAsync方法中,我们会创建标准的OutputFormatterWriteContext,然后通过IOutputFormatterSelector匹配请求对应的格式化器,最后执行写入操作,完全对齐控制器的处理逻辑:

private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
    var error = new ErrorResultModel(
        "Internal Server Error", 
        exception.Message, 
        exception.StackTrace);

    // 重置响应状态与内容,避免残留数据
    context.Response.Clear();
    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

    // 创建符合框架规范的输出上下文
    var writeContext = new OutputFormatterWriteContext(
        context,
        _streamWriterFactory.CreateWriter,
        typeof(ErrorResultModel),
        error);

    // 选择匹配请求的格式化器(和控制器返回ObjectResult的逻辑一致)
    var selectedFormatter = _outputFormatterSelector.SelectFormatter(
        writeContext,
        _mvcOptions.OutputFormatters,
        _mvcOptions.FormatterMappings);

    if (selectedFormatter != null)
    {
        await selectedFormatter.WriteAsync(writeContext);
    }
    else
    {
        // 降级处理:无匹配格式化器时返回纯文本
        context.Response.ContentType = "text/plain";
        await context.Response.WriteAsync("An unexpected error occurred.");
    }
}

3. 配置MVC支持多格式输出

要让内容协商生效,必须在全局配置中启用所需的输出格式化器(比如XML),确保中间件和控制器使用同一套序列化规则:

var builder = WebApplication.CreateBuilder(args);

// 添加控制器并启用JSON+XML格式化支持
builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        // 这里保留你自定义的JSON序列化配置
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    })
    .AddXmlSerializerFormatters(); // 启用XML输出格式化器

// 注册异常中间件
builder.Services.AddTransient<UnhandledExceptionMiddleware>();

var app = builder.Build();

// 注意中间件顺序:要放在UseRouting之后,UseEndpoints之前
app.UseMiddleware<UnhandledExceptionMiddleware>();

app.UseRouting();
app.UseAuthorization();

app.MapControllers();

app.Run();

4. 确保错误模型支持序列化

你的ErrorResultModel需要兼容所选的格式化器,比如XML序列化可能需要无参构造函数,可按需调整:

// 可选:添加XML序列化特性优化输出结构
[XmlRoot("Error")]
public class ErrorResultModel
{
    public string ResultMessage { get; }
    public string ExceptionMessage { get; }
    public string ExceptionStackTrace { get; }

    public ErrorResultModel(string resultMessage, string exceptionMessage, string exceptionStackTrace)
    {
        ResultMessage = resultMessage;
        ExceptionMessage = exceptionMessage;
        ExceptionStackTrace = exceptionStackTrace;
    }

    // 为XmlSerializer添加无参构造函数(可选)
    protected ErrorResultModel() { }
}

工作原理说明

  • IOutputFormatterSelector是框架内部处理内容协商的核心服务,会根据请求的Accept头、全局配置的格式化器列表,以及FormatterMappings规则选择最合适的输出格式,和控制器返回ObjectResult的逻辑完全一致。
  • 通过注入IOptions<MvcOptions>,中间件可以复用全局的序列化配置(比如JSON的驼峰命名、XML的序列化规则),确保异常响应和正常接口响应的格式统一。

这样配置后,当请求携带Accept: text/xml头时,中间件会返回XML格式的异常响应;如果全局默认格式改为XML,或者请求携带Accept: application/json,则会返回JSON格式,完全满足你的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:58:27