导读:本期聚焦于小伙伴创作的《C#中内容协商机制是什么?如何实现C# ContentNegotiation进阶功能?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C#中内容协商机制是什么?如何实现C# ContentNegotiation进阶功能?》有用,将其分享出去将是对创作者最好的鼓励。

C#中的内容协商机制是Web API框架根据客户端请求头中的Accept等信息,自动选择最合适的响应数据格式返回给客户端的功能,默认支持JSON和XML等常见格式,进阶场景下可以通过自定义扩展实现更多个性化需求。

C#中内容协商机制是什么?如何实现C# ContentNegotiation进阶功能?

C#内容协商机制的基本原理

内容协商的核心逻辑是服务器匹配客户端请求的媒体类型与自身支持的格式化器,默认情况下C# Web API会注册JsonMediaTypeFormatter和XmlMediaTypeFormatter两种格式化器,当客户端发送Accept: application/json请求时,框架会优先选择JSON格式化器序列化数据。

我们可以通过以下代码查看当前Web API项目默认注册的格式化器:

using System.Collections.Generic;
using System.Web.Http;
using System.Web.Http.Controllers;

public class FormatterController : ApiController
{
    public List<string> GetFormatters()
    {
        // 获取当前配置的所有媒体类型格式化器
        var formatters = this.Configuration.Formatters;
        List<string> formatterNames = new List<string>();
        foreach (var formatter in formatters)
        {
            formatterNames.Add(formatter.GetType().Name);
        }
        return formatterNames;
    }
}

进阶功能一:自定义媒体类型格式化器

如果我们需要支持比如text/csv这类自定义格式,就需要自己实现媒体类型格式化器,核心是实现MediaTypeFormatter基类的相关方法。

实现自定义CSV格式化器

下面的代码实现了一个简单的CSV格式化器,用于将集合类型的数据序列化为CSV格式返回:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

public class CsvMediaTypeFormatter : MediaTypeFormatter
{
    public CsvMediaTypeFormatter()
    {
        // 添加支持的媒体类型
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
        // 设置默认字符集
        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
    }

    public override bool CanWriteType(Type type)
    {
        // 支持所有可枚举的类型
        return typeof(IEnumerable<>).IsAssignableFrom(type) || type.GetInterface(typeof(IEnumerable<>).FullName) != null;
    }

    public override bool CanReadType(Type type)
    {
        // 暂不实现读取功能
        return false;
    }

    public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, System.Net.TransportContext transportContext)
    {
        var encoding = SelectCharacterEncoding(content.Headers);
        using (var writer = new StreamWriter(writeStream, encoding))
        {
            var enumerable = value as IEnumerable<object>;
            if (enumerable == null) return;
            // 写入表头:获取第一个元素的属性名
            var firstItem = new List<object>(enumerable).GetEnumerator();
            if (firstItem.MoveNext() && firstItem.Current != null)
            {
                var properties = firstItem.Current.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
                List<string> headers = new List<string>();
                foreach (var prop in properties)
                {
                    headers.Add(prop.Name);
                }
                await writer.WriteLineAsync(string.Join(",", headers));
                // 写入第一个元素的值
                List<string> firstValues = new List<string>();
                foreach (var prop in properties)
                {
                    var val = prop.GetValue(firstItem.Current);
                    firstValues.Add(val == null ? "" : val.ToString());
                }
                await writer.WriteLineAsync(string.Join(",", firstValues));
                // 写入剩余元素的值
                while (firstItem.MoveNext())
                {
                    if (firstItem.Current == null) continue;
                    List<string> values = new List<string>();
                    foreach (var prop in properties)
                    {
                        var val = prop.GetValue(firstItem.Current);
                        values.Add(val == null ? "" : val.ToString());
                    }
                    await writer.WriteLineAsync(string.Join(",", values));
                }
            }
        }
    }
}

实现完成后需要将自定义格式化器注册到Web API的配置中:

using System.Web.Http;

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // 注册自定义CSV格式化器
        config.Formatters.Add(new CsvMediaTypeFormatter());
        // 其他路由配置
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

进阶功能二:修改默认内容协商逻辑

默认的内容协商逻辑是按照格式化器注册顺序匹配,我们可以通过实现IContentNegotiator接口自定义协商规则,比如优先匹配客户端请求的特定参数而不是Accept头。

下面的代码实现了一个自定义内容协商器,优先根据请求中的format参数选择格式:

using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;

public class CustomContentNegotiator : IContentNegotiator
{
    private readonly IContentNegotiator _defaultNegotiator;

    public CustomContentNegotiator(IContentNegotiator defaultNegotiator)
    {
        _defaultNegotiator = defaultNegotiator;
    }

    public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
    {
        // 优先检查请求中的format参数
        var formatParam = request.GetQueryNameValuePairs();
        string format = null;
        foreach (var pair in formatParam)
        {
            if (pair.Key == "format")
            {
                format = pair.Value;
                break;
            }
        }
        if (!string.IsNullOrEmpty(format))
        {
            // 根据format参数匹配格式化器
            MediaTypeFormatter matchedFormatter = null;
            MediaTypeHeaderValue matchedMediaType = null;
            foreach (var formatter in formatters)
            {
                if (format == "json" && formatter is JsonMediaTypeFormatter)
                {
                    matchedFormatter = formatter;
                    matchedMediaType = new MediaTypeHeaderValue("application/json");
                    break;
                }
                if (format == "xml" && formatter is XmlMediaTypeFormatter)
                {
                    matchedFormatter = formatter;
                    matchedMediaType = new MediaTypeHeaderValue("application/xml");
                    break;
                }
                if (format == "csv" && formatter is CsvMediaTypeFormatter)
                {
                    matchedFormatter = formatter;
                    matchedMediaType = new MediaTypeHeaderValue("text/csv");
                    break;
                }
            }
            if (matchedFormatter != null)
            {
                return new ContentNegotiationResult(matchedFormatter, matchedMediaType);
            }
        }
        // 没有匹配到format参数时走默认协商逻辑
        return _defaultNegotiator.Negotiate(type, request, formatters);
    }
}

注册自定义协商器到配置中:

using System.Web.Http;

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // 注册自定义内容协商器
        var defaultNegotiator = config.Services.GetService(typeof(IContentNegotiator)) as IContentNegotiator;
        config.Services.Replace(typeof(IContentNegotiator), new CustomContentNegotiator(defaultNegotiator));
        // 注册格式化器
        config.Formatters.Add(new CsvMediaTypeFormatter());
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

进阶功能三:处理特殊客户端请求场景

有些老旧客户端可能不会正确设置Accept头,我们可以通过全局配置设置默认的响应格式,比如默认返回JSON格式:

using System.Net.Http.Formatting;
using System.Web.Http;

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // 移除XML格式化器,默认返回JSON
        config.Formatters.Remove(config.Formatters.XmlFormatter);
        // 或者调整格式化器顺序,把JSON格式化器放在第一个
        // var jsonFormatter = config.Formatters.JsonFormatter;
        // config.Formatters.Remove(jsonFormatter);
        // config.Formatters.Insert(0, jsonFormatter);
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

还可以针对单个接口自定义内容协商行为,比如在接口方法中使用HttpResponseMessage直接指定响应格式:

using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Web.Http;

public class UserController : ApiController
{
    public HttpResponseMessage GetUsers()
    {
        var users = new List<object>
        {
            new { Id = 1, Name = "张三", Age = 20 },
            new { Id = 2, Name = "李四", Age = 22 }
        };
        // 强制返回XML格式,忽略内容协商
        return Request.CreateResponse(HttpStatusCode.OK, users, new XmlMediaTypeFormatter());
    }
}

注意事项

  • 自定义格式化器时需要注意处理不同类型的数据,避免出现序列化异常
  • 修改默认内容协商逻辑后需要做好测试,确保不影响现有接口的正常运行
  • 自定义媒体类型的名称需要符合MIME类型规范,避免客户端无法识别
  • 如果项目中同时使用ASP.NET Core,内容协商的实现方式和传统Web API有所不同,需要参考对应版本的文档

ContentNegotiationC#_Web_API内容协商媒体类型修改时间:2026-06-10 21:18:47

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。