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

