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

ASP.NET Core自定义CsvOutputFormatter无法匹配带参数的Accept头问题求助

这个问题的核心在于ASP.NET Core的内容协商默认会严格匹配媒体类型的完整字符串,包括后面的参数(比如charset、v=1.0),而你的代码里有两个关键限制导致带参数的请求无法匹配:

  1. CanWriteResult方法里直接判断context.ContentType != "text/csv",硬编码的字符串相等校验会直接排除所有带参数的情况;
  2. 试图通过添加所有可能的带参数媒体类型到SupportedMediaTypes,但这根本不可能覆盖所有用户可能发送的参数组合(比如不同的charset、版本号)。

下面是具体的修复步骤:

第一步:修复CanWriteResult的媒体类型判断逻辑

不要直接比较字符串,而是用MediaTypeHeaderValue来解析请求的媒体类型,只校验主类型和子类型(也就是text/csv),忽略后面的参数。如果需要处理版本参数,也可以在这里解析判断:

public override bool CanWriteResult(OutputFormatterCanWriteContext context)
{
    // 先尝试解析请求的Content-Type
    if (!MediaTypeHeaderValue.TryParse(context.ContentType, out var requestMediaType))
        return false;

    // 只匹配主类型+子类型,忽略所有参数(charset、version等)
    if (!requestMediaType.MediaType.Equals("text/csv", StringComparison.OrdinalIgnoreCase))
        return false;

    // 可选:如果需要校验版本参数,在这里处理
    var versionParam = requestMediaType.Parameters.FirstOrDefault(p => p.Name.Equals("v", StringComparison.OrdinalIgnoreCase));
    if (versionParam != null)
    {
        // 比如只允许v1.0和v2.0
        var allowedVersions = new[] { "1.0", "2.0" };
        if (!allowedVersions.Contains(versionParam.Value, StringComparer.OrdinalIgnoreCase))
            return false;
    }

    // 原来的业务逻辑校验
    var resource = context.Object as Resource;
    return resource != null 
        && resource.Embedded != null 
        && resource.Embedded.ContainsKey(Common.Constants.ListItems);
}

第二步:精简SupportedMediaTypes的配置

只需要添加基础的text/csv即可,不需要带任何参数——TextOutputFormatter会自动处理编码相关的参数,而我们已经在CanWriteResult里处理了版本参数:

public CsvOutputFormatter()
{
    // 只添加基础媒体类型,不用带参数
    SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv"));

    // 编码配置保持不变
    foreach (var encodingInfo in Encoding.GetEncodings())
    {
        var encoding = encodingInfo.GetEncoding();
        if (encoding == Encoding.UTF8)
            SupportedEncodings.Insert(0, encoding);
        else
            SupportedEncodings.Add(encoding);
    }
}

第三步:简化控制器的[Produces]属性

只需要保留text/csv即可,不需要带任何参数,因为参数是请求时的动态内容:

[Produces("application/hal+json", "text/csv")]
public class YourController : ControllerBase
{
    // ... 你的控制器方法
}

为什么之前的方案无效?

  • 把格式化器放首位:会导致所有请求都优先用CSV,破坏了默认返回JSON的需求;
  • 添加带参数的SupportedMediaTypes:内容协商会精确匹配完整的媒体类型字符串,而用户可能发送的参数组合是无限的(比如不同charset、版本号的组合),根本不可能穷举;
  • [Produces]加所有参数组合:同样不现实,编译时无法预知所有可能的参数。

验证效果

修改后,当用户发送text/csv; charset=utf-8、text/csv; v=2.0或者混合参数的请求时,ASP.NET Core会正确识别这属于text/csv媒体类型,然后进入你的CanWriteResult方法,通过校验后就会使用CSV格式化器。

从日志来看,之前的问题是系统认为带参数的text/csv; charset=utf-8不是显式设置的text/csv的子集——修改判断逻辑后,这个问题就解决了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 16:22:31