• 将 CLR 对象写入 HTTP 消息正文
  • Web API 为 JSON 和 XML 提供媒体类型格式化程序。 默认情况下,框架将这些格式化程序插入管道。 客户端可以在 HTTP 请求的 Accept 标头中请求 JSON 或 XML。

  • JSON Media-Type格式化程序

  • Camel 外壳
  • 匿名对象和Weakly-Typed对象
  • XML Media-Type格式化程序

  • 设置Per-Type XML 序列化程序
  • 删除 JSON 或 XML 格式化程序

  • 处理循环对象引用

  • 测试对象序列化

    JSON Media-Type格式化程序

    JSON 格式设置由 JsonMediaTypeFormatter 类提供。 默认情况下, JsonMediaTypeFormatter 使用 Json.NET 库来执行序列化。 Json.NET 是第三方开放源代码项目。

    如果需要,可以将 JsonMediaTypeFormatter 类配置为使用 DataContractJsonSerializer 而不是 Json.NET。 为此,请将 UseDataContractJsonSerializer 属性设置为 true

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.UseDataContractJsonSerializer = true;
    

    JSON 序列化

    本部分介绍使用默认 Json.NET 序列化程序的 JSON 格式化程序的一些特定行为。 这不是 Json.NET 库的综合文档;有关详细信息,请参阅 Json.NET 文档

    序列化的内容是什么?

    默认情况下,所有公共属性和字段都包含在序列化的 JSON 中。 若要省略属性或字段,请使用 JsonIgnore 属性对其进行修饰。

    public class Product
        public string Name { get; set; }
        public decimal Price { get; set; }
        [JsonIgnore]
        public int ProductCode { get; set; } // omitted
    

    如果更喜欢“选择加入”方法,请使用 DataContract 属性修饰 类。 如果存在此属性,则成员将被忽略,除非它们具有 DataMember。 还可以使用 DataMember 序列化私有成员。

    [DataContract]
    public class Product
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public decimal Price { get; set; }
        public int ProductCode { get; set; }  // omitted by default
    

    Read-Only属性

    默认情况下,只读属性是序列化的。

    默认情况下,Json.NET 以 ISO 8601 格式写入日期。 UTC (协调世界时) 的日期用“Z”后缀写。 以本地时间表示的日期包括时区偏移量。 例如:

    2012-07-27T18:51:45.53403Z         // UTC
    2012-07-27T11:51:45.53403-07:00    // Local
    

    默认情况下,Json.NET 保留时区。 可以通过设置 DateTimeZoneHandling 属性来替代此设置:

    // Convert all dates to UTC
    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
    

    如果希望使用 Microsoft JSON 日期格式 ("\/Date(ticks)\/") 而不是 ISO 8601,请在序列化程序设置上设置 DateFormatHandling 属性:

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.DateFormatHandling 
    = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
    

    若要编写缩进 JSON,请将 “格式设置” 设置为 Formatting.Indented

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
    

    Camel 外壳

    若要使用 camel 大小写编写 JSON 属性名称,而不更改数据模型,请在序列化程序上设置 CamelCasePropertyNamesContractResolver

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    

    匿名对象和Weakly-Typed对象

    操作方法可以返回匿名对象并将其序列化为 JSON。 例如:

    public object Get()
        return new { 
            Name = "Alice", 
            Age = 23, 
            Pets = new List<string> { "Fido", "Polly", "Spot" } 
    

    响应消息正文将包含以下 JSON:

    {"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}
    

    如果 Web API 从客户端接收结构松散的 JSON 对象,则可以将请求正文反序列化为 Newtonsoft.Json.Linq.JObject 类型。

    public void Post(JObject person)
        string name = person["Name"].ToString();
        int age = person["Age"].ToObject<int>();
    

    但是,通常最好使用强类型化数据对象。 然后,无需自行分析数据,即可获得模型验证的好处。

    XML 序列化程序不支持匿名类型或 JObject 实例。 如果将这些功能用于 JSON 数据,则应从管道中删除 XML 格式化程序,如本文稍后所述。

    XML Media-Type格式化程序

    XML 格式设置由 XmlMediaTypeFormatter 类提供。 默认情况下, XmlMediaTypeFormatter 使用 DataContractSerializer 类执行序列化。

    如果需要,可以将 XmlMediaTypeFormatter 配置为使用 XmlSerializer 而不是 DataContractSerializer。 为此,请将 UseXmlSerializer 属性设置为 true

    var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
    xml.UseXmlSerializer = true;
    

    XmlSerializer 类支持比 DataContractSerializer 更窄的类型集,但可以更好地控制生成的 XML。 如果需要匹配现有 XML 架构,请考虑使用 XmlSerializer

    XML 序列化

    本节介绍使用默认 DataContractSerializer 的 XML 格式化程序的一些特定行为。

    默认情况下,DataContractSerializer 的行为如下所示:

  • 将序列化所有公共读/写属性和字段。 若要省略属性或字段,请使用 IgnoreDataMember 属性对其进行修饰。
  • 不序列化专用成员和受保护成员。
  • 只读属性不会序列化。 (但是,只读集合属性的内容是序列化的。)
  • 类名和成员名称在 XML 中编写,与它们在类声明中显示的方式完全相同。
  • 使用默认的 XML 命名空间。
  • 如果需要对序列化进行更多控制,可以使用 DataContract 属性修饰 类。 如果存在此属性,则按如下所示序列化类:

  • “选择加入”方法:默认情况下不序列化属性和字段。 若要序列化属性或字段,请使用 DataMember 属性对其进行修饰。
  • 若要序列化私有成员或受保护成员,请使用 DataMember 属性对其进行修饰。
  • 只读属性不会序列化。
  • 若要更改类名在 XML 中的显示方式,请在 DataContract 属性中设置 Name 参数。
  • 若要更改成员名称在 XML 中的显示方式,请在 DataMember 属性中设置 Name 参数。
  • 若要更改 XML 命名空间,请在 DataContract 类中设置 Namespace 参数。
  • Read-Only属性

    只读属性不会序列化。 如果只读属性具有后备私有字段,则可以使用 DataMember 属性标记专用字段。 此方法需要 类上的 DataContract 属性。

    [DataContract]
    public class Product
        [DataMember]
        private int pcode;  // serialized
        // Not serialized (read-only)
        public int ProductCode { get { return pcode; } }
    

    日期以 ISO 8601 格式编写。 例如,“2012-05-23T20:21:37.9116538Z”。

    若要编写缩进 XML,请将 Indent 属性设置为 true

    var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
    xml.Indent = true;
    

    设置Per-Type XML 序列化程序

    可以为不同的 CLR 类型设置不同的 XML 序列化程序。 例如,你可能有一个特定的数据对象,该对象需要 XmlSerializer 以实现向后兼容性。 您可以对此对象使用 XmlSerializer ,并继续对其他类型使用 DataContractSerializer

    若要为特定类型设置 XML 序列化程序,请调用 SetSerializer

    var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
    // Use XmlSerializer for instances of type "Product":
    xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));
    

    可以指定 XmlSerializer 或任何派生自 XmlObjectSerializer 的对象。

    删除 JSON 或 XML 格式化程序

    如果不想使用 JSON 格式化程序或 XML 格式化程序,可以从格式化程序列表中删除它们。 执行此操作的主要原因是:

  • 将 Web API 响应限制为特定媒体类型。 例如,你可能决定仅支持 JSON 响应,并删除 XML 格式化程序。
  • 将默认格式化程序替换为自定义格式化程序。 例如,可以将 JSON 格式化程序替换为自己的 JSON 格式化程序自定义实现。
  • 以下代码演示如何删除默认格式化程序。 从 Global.asax 中定义的 Application_Start 方法调用它。

    void ConfigureApi(HttpConfiguration config)
        // Remove the JSON formatter
        config.Formatters.Remove(config.Formatters.JsonFormatter);
        // or
        // Remove the XML formatter
        config.Formatters.Remove(config.Formatters.XmlFormatter);
    

    处理循环对象引用

    默认情况下,JSON 和 XML 格式化程序将所有对象作为值写入。 如果两个属性引用同一对象,或者同一对象在集合中出现两次,则格式化程序将序列化该对象两次。 如果对象图包含周期,则这是一个特定问题,因为序列化程序在检测到图形中的循环时会引发异常。

    请考虑以下对象模型和控制器。

    public class Employee
        public string Name { get; set; }
        public Department Department { get; set; }
    public class Department
        public string Name { get; set; }
        public Employee Manager { get; set; }
    public class DepartmentsController : ApiController
        public Department Get(int id)
            Department sales = new Department() { Name = "Sales" };
            Employee alice = new Employee() { Name = "Alice", Department = sales };
            sales.Manager = alice;
            return sales;
    

    调用此操作将导致格式化程序引发异常,该异常转换为状态代码 500 (内部服务器错误) 响应客户端。

    若要在 JSON 中保留对象引用,请将以下代码添加到 Global.asax 文件中 Application_Start 方法:

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.PreserveReferencesHandling = 
        Newtonsoft.Json.PreserveReferencesHandling.All;
    

    现在,控制器操作将返回如下所示的 JSON:

    {"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}
    

    请注意,序列化程序将“$id”属性添加到这两个对象。 此外,它检测到 Employee.Department 属性创建循环,因此它将值替换为对象引用:{“$ref”:“1”}。

    对象引用在 JSON 中不是标准引用。 在使用此功能之前,请考虑客户端是否能够分析结果。 最好只是从图形中删除周期。 例如,此示例中实际上不需要从 Employee 返回到 Department 的链接。

    若要在 XML 中保留对象引用,有两个选项。 更简单的选项是将 添加到 [DataContract(IsReference=true)] 模型类。 IsReference 参数启用对象引用。 请记住, DataContract 使序列化选择加入,因此还需要将 DataMember 属性添加到属性:

    [DataContract(IsReference=true)]
    public class Department
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public Employee Manager { get; set; }
    

    现在,格式化程序将生成类似于以下内容的 XML:

    <Department xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="i1" 
                xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" 
                xmlns="http://schemas.datacontract.org/2004/07/Models">
      <Manager>
        <Department z:Ref="i1" />
        <Name>Alice</Name>
      </Manager>
      <Name>Sales</Name>
    </Department>
    

    如果要避免模型类上的属性,还有另一个选项:创建新的特定于类型的 DataContractSerializer 实例,并在构造函数中将 preserveObjectReferences 设置为 true 。 然后在 XML 媒体类型格式化程序上将此实例设置为每类型序列化程序。 以下代码演示如何执行此操作:

    var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
    var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue, 
        false, /* preserveObjectReferences: */ true, null);
    xml.SetSerializer<Department>(dcs);
    

    测试对象序列化

    在设计 Web API 时,测试数据对象的序列化方式非常有用。 无需创建控制器或调用控制器操作即可执行此操作。

    string Serialize<T>(MediaTypeFormatter formatter, T value)
        // Create a dummy HTTP Content.
        Stream stream = new MemoryStream();
        var content = new StreamContent(stream);
        /// Serialize the object.
        formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
        // Read the serialized string.
        stream.Position = 0;
        return content.ReadAsStringAsync().Result;
    T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
        // Write the serialized string to a memory stream.
        Stream stream = new MemoryStream();
        StreamWriter writer = new StreamWriter(stream);
        writer.Write(str);
        writer.Flush();
        stream.Position = 0;
        // Deserialize to an object of type T
        return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
    // Example of use
    void TestSerialization()
        var value = new Person() { Name = "Alice", Age = 23 };
        var xml = new XmlMediaTypeFormatter();
        string str = Serialize(xml, value);
        var json = new JsonMediaTypeFormatter();
        str = Serialize(json, value);
        // Round trip
        Person person2 = Deserialize<Person>(json, str);
    
  •