• Asp.Net Web API 2第十三课——ASP.NET Web API中的JSON和XML序列化


    前言

    阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html

    本文描述ASP.NET Web API中的JSON和XML格式化器。

    在ASP.NET Web API中,媒体类型格式化器(Media-type Formatter)是一种能够做以下工作的对象:

    • 从HTTP消息体读取CLR(公共语言运行时)对象
    • 将CLR对象写入HTTP消息体

    Web API提供了用于JSON和XML的媒体类型格式化器。框架已默认将这些格式化器插入到消息处理管线之中。客户端在HTTP请求的Accept报头中可以请求JSON或XML。

    JSON媒体类型格式化器

     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 Documentation。

    什么会被序列化?

    默认情况下,所有public属性和字段都会被包含在序列化的JSON中。为了忽略一个属性或字段,需要用JsonIgnore注解属性修饰它。

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

    如果你更喜欢“opt-in(选入)”方法,可以用DataContract注解属性来修饰类。如果有注解属性,则成员均被忽略,除非有DataMemberDataMember也可以序列化private成员。

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

    只读属性

    只读属性默认是序列化的。

    Dates(日期)

    默认情况下,Json.NET会将日期写成ISO 8601格式。UTC(Coordinated Universal Time — 世界标准时间)格式的日期书写时带有后缀“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
    // 将所有日期转换成UTC格式
    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.DateTimeZoneHandling =
         Newtonsoft.Json.DateTimeZoneHandling.Utc;

    如果你喜欢使用微软的JSON日期格式("/Date(ticks)/ ")而不是ISO 8601,可以在SerializerSettings上设置DateFormatHandling属性:

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

    Indenting(缩进)

    为了书写有缩进的JSON,可以将Formatting设置为Formatting.Indented

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

    Camel Casing(驼峰式大小写转换)

    为了在不修改数据模型的情况下,用驼峰式大小写转换JSON的属性名,可以设置序列化器上的CamelCasePropertyNamesContractResolver

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

    匿名类型与弱类型对象

    动作方法或以返回一个匿名对象,并将其序列化成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媒体类型格式化器

      XML格式化是由XmlMediaTypeFormatter类提供的。默认情况下,XmlMediaTypeFormatter使用DataContractSerializer类来执行序列化。如果喜欢,你可以将XmlMediaTypeFormatter配置成使用XmlSerializer而不是DataContractSerializer。要想这么做,可将UseXmlSerializer属性设置为true 

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

    XmlSerializer类支持的类型集要比DataContractSerializer更窄一些,但对结果XML有更多的控制。如果需要与已有的XML方案匹配,可考虑使用XmlSerializer

    XML Serialization——XML序列化

     本小节描述使用默认DataContractSerializer的时,XML格式化器的一些特殊行为。默认情况下,DataContractSerializer行为如下:

    •   序列化所有public读/写属性和字段。为了忽略一个属性或字段,请用IgnoreDataMember注解属性修饰它。
    •   private和protected成员不作序列。
    •   只读属性不作序列化
    •   类名和成员名按类声明中的确切呈现写入XML
    •   使用XML的默认命名空间

    如果需要在序列化上的更多控制,可以用DataContract注解属性修饰类。当这个注解属性出现时,该类按以策略序列化:

    •   “Opt in(选入)”方法:属性与字段默认不被序列化。为了序列化一个属性或字段,请用DataMember注解属性修饰它。
    •   要序列化private或protected成员,请用DataMember注解属性修饰它。
    •   只读属性不被序列化。
    •   要改变类名在XML中的呈现,请在DataContract注解属性中设置Name参数。
    •   要改变成员名在XML中的呈现,请设置DataMember注解属性中的Nmae参数。
    •   要改变XML命名空间,请设置DataContract类中的Namespace参数。

    Read-Only Properties——只读属性

    只读属性是不被序列化的。如果只读属性有一个支撑private字段,可以用DataMember注解属性对这个private字段进行标记。这种办法需要在类上使用DataContract注解属性。

    [DataContract]
    public class Product
    {
        [DataMember]
        private int pcode;  // serialized(序列化的)
    
        // Not serialized (read-only)
        // 不作序列化(只读)
        public int ProductCode { get { return pcode; } }
    }

    Dates——日期

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

    Indenting——缩进

    要书写缩进的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":
    // 对“Product”类型的实例使用XmlSerializer:
    xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));

    你可以指定一个XmlSerializer,或任何派生于XmlObjectSerializer的对象。

    Removing the JSON or XML Formatter——去除JSON或XML格式化器

     你可以从格式化器列表中删除JSON格式化器,或XML格式化器,只要你不想使用它们。这么做的主要原因是:

    •    将你的Web API响应限制到特定的媒体类型。例如,你可能决定只支持JSON响应,而删除XML格式化器。
    •    用一个自定义格式化器代替默认的格式化器。例如,你可能要用自己的自定义JSON格式化器实现来代替(默认的)JSON格式化器。

     以下代码演示了如何删除默认的格式化器。在Global.asax中定义的Application_Start方法中调用它。

    void ConfigureApi(HttpConfiguration config)
    {
        // Remove the JSON formatter
        // 删除JSON格式化器
        config.Formatters.Remove(config.Formatters.JsonFormatter);
    
        // or(或者)
    
        // Remove the XML formatter
        // 删除XML格式化器
        config.Formatters.Remove(config.Formatters.XmlFormatter);
    }

    Handling Circular Object References——处理循环对象引用

     在默认情况下,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构成了序列化的“选入(Opt-in)”,因此,你还需要对属性添加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

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

    Testing Object Serialization——测试对象序列化

     在设计Web API时,对如何序列化对象进行测试是有用的。不必创建控制器或调用控制器动作,便可做这种事。

    string Serialize<T>(MediaTypeFormatter formatter, T value)
    {
        // Create a dummy HTTP Content.
        // 创建一个HTTP内容的哑元
        Stream stream = new MemoryStream();
        var content = new StreamContent(stream);
    
        // Serialize the object.
        // 序列化对象
        formatter.WriteToStreamAsync(typeof(T), value, stream, content.Headers, 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
        // 解序列化成类型为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);
    }

    总结

     本课主要简单的了解一下JSON和XML的序列化和反序列的使用。

     本文的参考链接为 http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization

     同时本文已更新至 Web API导航系列 http://www.cnblogs.com/aehyok/p/3446289.html

  • 相关阅读:
    day1 生活大爆炸版石头剪刀布
    友谊赛
    再数17
    素数统计
    day1 LGTB玩THD
    day1 LGTB学分块
    day1 LGTB玩扫雷
    组合(1-m中选n个数)
    二分查找法,加递归,之前做了一个没加递归,结果就废了
    以前的一个程序,死循环,骚年,卡爆你的电脑吧
  • 原文地址:https://www.cnblogs.com/aehyok/p/3460184.html
Copyright © 2020-2023  润新知