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

.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的输出格式化管道。同时:

  1. 默认的XmlSerializer无法序列化ProblemDetails(它本质是字典类型)
  2. 中间件硬编码了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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 05:53:24