.NET6中Hellang中间件:ProblemDetails始终返回JSON而非XML问题
.NET6中Hellang ProblemDetails中间件返回XML格式问题的解决方法
问题场景
在.NET6项目中使用Hellang的ProblemDetails中间件时,即使在接口上标记了[Produces("text/xml")],抛出异常后返回的ProblemDetails始终是JSON格式,调整MVC格式化程序顺序也无法解决。相关代码如下:
接口代码
[HttpPost] [ProducesResponseType(200)] [Consumes("text/xml")] [Produces("text/xml")] public IActionResult Hook([FromBody] string content) { throw new Exception("error"); }
中间件与MVC配置
builder.Services.AddProblemDetails(options => { //options.ContentTypes.Clear(); //options.ContentTypes.Add(new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/xml")); options.IncludeExceptionDetails = (ctx, ex) => { ctx.Response.ContentType = "application/xml"; return env.IsEnvironment("Local") || env.IsDevelopment() || env.IsStaging(); }; options.MapToStatusCode<NotImplementedException>(StatusCodes.Status501NotImplemented); options.MapToStatusCode<HttpRequestException>(StatusCodes.Status503ServiceUnavailable); options.MapToStatusCode<Exception>(StatusCodes.Status500InternalServerError); }); // ... builder.Services.AddMvc(options => { options.InputFormatters.Add(new XmlSerializerInputFormatter()); options.OutputFormatters.Add(new XmlSerializerOutputFormatter()); });
模型验证响应配置
builder.Services.ConfigureApiBehaviorOptions(options => { options.InvalidModelStateResponseFactory = context => { var probleDetailsFactory = context.HttpContext.RequestServices .GetRequiredService<Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory>(); var problemDetails = probleDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState); problemDetails.Detail = "Detail"; problemDetails.Instance = context.HttpContext.Request.Path; return new BadRequestObjectResult(problemDetails) { ContentTypes = { Application.Xml } }; }; });
根源分析
Hellang的ProblemDetails中间件默认直接将ProblemDetails对象序列化为JSON写入响应,绕过了MVC的输出格式化管道。同时:
- 默认的
XmlSerializer无法序列化ProblemDetails(它本质是字典类型) - 中间件硬编码了JSON序列化逻辑,忽略了接口的
Produces属性和响应ContentType设置
解决方案
1. 配置ProblemDetails中间件支持XML内容类型
先开启中间件的ContentTypes配置,明确支持XML类型:
builder.Services.AddProblemDetails(options => { options.ContentTypes.Clear(); options.ContentTypes.Add(Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/xml")); options.ContentTypes.Add(Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/xml")); options.IncludeExceptionDetails = (ctx, ex) => { var env = ctx.RequestServices.GetRequiredService<IWebHostEnvironment>(); return env.IsEnvironment("Local") || env.IsDevelopment() || env.IsStaging(); }; // 保留原有状态码映射配置 });
2. 替换XML格式化程序,支持ProblemDetails序列化
默认XmlSerializer不支持字典类型序列化,改用DataContractSerializer:
builder.Services.AddControllers(options => { // 添加XML输入格式化程序 options.InputFormatters.Add(new XmlSerializerInputFormatter(options)); // 添加DataContract输出格式化程序(支持ProblemDetails序列化) var xmlFormatter = new DataContractSerializerOutputFormatter(); options.OutputFormatters.Add(xmlFormatter); // 调整顺序,让XML格式化程序优先执行 options.OutputFormatters.Remove(xmlFormatter); options.OutputFormatters.Insert(0, xmlFormatter); }) .AddXmlDataContractSerializerFormatters(); // 自动注册DataContract相关配置
3. 用MVC异常过滤器替代中间件的直接序列化逻辑
自定义异常过滤器,将异常转换为ObjectResult,让MVC格式化管道处理响应:
public class XmlProblemDetailsExceptionFilter : ExceptionFilterAttribute { private readonly ProblemDetailsFactory _problemDetailsFactory; private readonly IWebHostEnvironment _env; public XmlProblemDetailsExceptionFilter(ProblemDetailsFactory problemDetailsFactory, IWebHostEnvironment env) { _problemDetailsFactory = problemDetailsFactory; _env = env; } public override void OnException(ExceptionContext context) { if (context.ExceptionHandled) return; var statusCode = context.Exception switch { NotImplementedException => StatusCodes.Status501NotImplemented, HttpRequestException => StatusCodes.Status503ServiceUnavailable, _ => StatusCodes.Status500InternalServerError }; var problemDetails = _problemDetailsFactory.CreateProblemDetails( context.HttpContext, statusCode: statusCode, detail: _env.IsDevelopment() ? context.Exception.ToString() : null, title: "请求处理出错"); context.Result = new ObjectResult(problemDetails) { StatusCode = statusCode, ContentTypes = { "application/xml", "text/xml" } }; context.ExceptionHandled = true; } }
注册过滤器:
builder.Services.AddControllers(options => { options.Filters.Add<XmlProblemDetailsExceptionFilter>(); // 保留XML格式化程序配置 }) .AddXmlDataContractSerializerFormatters();
4. 调整模型验证响应的ContentType配置
确保模型验证返回的响应也正确指定XML类型:
builder.Services.ConfigureApiBehaviorOptions(options => { options.InvalidModelStateResponseFactory = context => { var problemDetailsFactory = context.HttpContext.RequestServices .GetRequiredService<Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory>(); var problemDetails = problemDetailsFactory.CreateValidationProblemDetails( context.HttpContext, context.ModelState, statusCode: StatusCodes.Status400BadRequest); problemDetails.Detail = "参数验证失败"; problemDetails.Instance = context.HttpContext.Request.Path; return new BadRequestObjectResult(problemDetails) { ContentTypes = { "application/xml", "text/xml" } }; }; });
效果验证
完成配置后,当接口抛出异常或模型验证失败时,会根据接口的Produces属性或请求的Accept头,返回XML格式的ProblemDetails响应。
内容的提问来源于stack exchange,提问作者Claudio Ferraro
相关产品推荐
相关产品推荐

