zoukankan      html  css  js  c++  java
  • Web API <五> 序列化

    Asp.Net Web Api 中提供了两种 媒体类型格式化器(mime-type formatter),分别用于支持 JSONXML 数据的格式化处理。默认两种格式化器已集成到了 Asp.Net Web Api 的请求处理管道(pipline) 中,客户端可以在请求报文头中通过设置 Accept 参数来指定获取数据的格式类型(JSON或 XML)。

    媒体类型格式化器 是指具有如下功能的类型:

    1. 从 Http 消息中读取 CLR 对象
    2. CLR 对象写入到Http 消息中

    JSon 格式化器

      Json 格式化是通过 JsonMediaTypeFormatterl类实现的 ,默认情况下使用的是第三方开源库 JSon.Net 进行序列化操作的。如果愿意,还可以使用 Net 内置的 **DataContractJsonSerializer ** 来替代默认的 Json.Net ,只要在 Global.asax进行如下的配置即可。

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

    Json 序列化

      默认情况下,所有的公有的 属性(property)字段(field) 都会被序列化,包括那些只读的属性或字段,而忽略那些标记有 JsonIgnore 属性(Attribute)的属性和字段。

    	public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
        [JsonIgnore]
        public int ProductCode { get; set; } // 序列化时被忽略
    }
    

      当然,还存在一种与上述情况相反的情况,即出去指定的属性或字段,忽略其它的。这种情况可以搭配使用 DataContractDataMember 属性(Attribute) 来实现。对于标记了 DataMember 的属性或字段,即使它是私有的,仍然会被序列化。

    [DataContract]
    public class Product
    {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public decimal Price { get; set; }
        public int ProductCode { get; set; }  // 默认被忽略
        [DataMember]
        private string PrivateField{ get; set; }//即使是私有属性也会被序列化
    }
    
    日期序列化

      默认情况下,Json.Net 会将 UTC 时间序列化为带有一个 Z 后缀的字符串,本地时间会被处理为一个包含时区偏移值(time-zone offset)的字符串,如下所示
      

    • 2017-08-10T16:24:03.5739377+08:00 //本地时间
    • 2017-08-10T08:24:42.8873723Z //UTC 时间

    默认情况下,Json.Net 会在序列化时间(本地时间)会保留一个时区的偏移量,我们可以通过设置 **DateTimeZoneHandling ** 来进行更改。该属性是一个 DateTimeZoneHandling 类型的枚举,如下所示

    	 //
        public enum DateTimeZoneHandling
        {
            //
            // 摘要:
            //    作为本地时间处理,如果是一个 UTC 时间会被转换为本地时间
            //     Time (UTC), it is converted to the local time.
            Local = 0,
            //
            // 摘要:
            //    作为UTC 时间来处理,如果是一个本地时间会被转换为 UTC 时间
            //     converted to a UTC.
            Utc = 1,
            //
            // 摘要:
            // 在序列化时总是将 System.Date 对象当作本地时间来处理,在反序列化时,如果指定了特定的时区,则将其转换为本地时间
            //     a string is being converted to System.DateTime, convert to a local time if a
            //     time zone is specified.
            Unspecified = 2,
            //
            // 摘要
            //当转换时总是保留时区信息,默认值
            RoundtripKind = 3
    

      除了使用默认的 ISO 8601 的格式来显示时间,还可以选择使用 Miscrosoft JSon Date Format (Date(tickets)) ,这个可以通过设置 DateFormatHandling 来实现,如下所示:

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

      如果设置了 DateTimeZoneHandling 属性,那么在这里显示的时候是不同的,本地时间会有一个时区偏移值,

       Date(1502433077121+0800)//本地时间
       Date(1502433077121)//UTC时间
    

    Json 的格式缩进

      默认情况下返回的 JSon 数据时没有格式缩紧的,如果需要产生具有缩进格式的Json,可以通过设置 **CamelCasePropertyNamesContractResolver **,如下所示:

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

    没有进行格式缩进的 JSon

    	[{"id":1,"myname":"Computer","marketDate":"/Date(1502433578649+0800)/","type"  :0},{"id":2,"myname":"Telphone","marketDate":"/Date(1502433578649)/","type":1}]
    

    具有格式缩进的 JSon

    [
      {
        "id": 1,
        "myname": "Computer",
        "marketDate": "/Date(1502433708247+0800)/",
        "type": 0
      },
      {
        "id": 2,
        "myname": "Telphone",
        "marketDate": "/Date(1502433708247)/",
        "type": 1
      }
    ]
    

    变量的驼峰式(Camel Casing)写法

      这里的驼峰式写法指的是 小驼峰写法,例如变量 MyName 会被转换为 myName
    如果要启用变量的 驼峰式写法,可以通过如下的设置来实现:

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

    关于更多的驼峰式写法的信息看这里

    返回匿名类型

      在 Action 方法中,可以直接返回一个匿名的 Object 对象,如下所示

    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"]}
      当接受一些客户端传的结构松散 [1]Json 对象数据时,可以使用 Newtonsoft.Json.Linq.JObject 来接受,如下所示:

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

    [1] 这里所说的结构松散是指json 对象在服务器端没有对应的 model 对象来供其进行反序列化

      但接受客户端的 json 对象的最好方法还是使用 Model 实体对象,因为这样不仅可以将 Json 对象与 Model 实体对象自动绑定,还可以自动进行必要的 Model 验证。

    XML 格式化器

      XML 的格式化是通过 **XmlMediaTypeFormatter ** 类来实现的,默认是 **DataContractSerializer ** 执行 XML序列化。如果愿意,还可以配置 XmlMediaTypeFormat 使用 XmlSerializer 去替代默认的 DataContractSerializer

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

      XmlSerializer 仅支持 DataContractSerializer 全部功能的一部分,但却可以对结果的 Xml进行更多的控制。 当你需要匹配现有 xml 模式(Schema)

    Xml 序列化

      使用默认的 DataContractSerializer 时,会按照如下的规则进行序列化:

    • 所有的字段或 Get/Set 的属性都将被序列化,忽略那些标记有 **IgnoreDataMember ** 属性的属性或字段
    • 所有私有(Private) 和 受保护(Protected) 的成员不会被序列化
    • 只读属性不会被序列化,但只读集合成员的集合元素会被序列化
    • 被序列化的类型的类型名称和成员名称将出现在序列化产生中的 Xml
    • 会有一个默认的 Xml 命名空间

      下面通过一个例子对上述的规则进行进一步的说明:定义的 Model 实体 Product 如下所示

    	/// <summary>
        /// 商品类
        /// </summary>
        public class Product
        {
            private string[] _Tags;
            public Product()
            {
                _Tags = new string[]
                {
                    "Tag1","Tag2"
                };
            }
            /// <summary>
            /// 商品的Id
            /// </summary>
            public int Id { get; set; }
            /// <summary>
            /// 商品的名称
            /// </summary>
            public string myname { get; set; }
    
            /// <summary>
            /// 上市时间
            /// </summary>
    
            public DateTime MarketDate
            {
                get; set;
            }
            //只读集合属性
            public string[] Tags
            {
                get
                {
                    return this._Tags;
                }
            }
            private string PrivateField { get; set; }
    
            public ProductType Type { get; set; }
        }
    
        public enum ProductType
        {
            Type1,
            Type2
        }
    

      调用 Web Api 方法返回 Xml 序列化后的 Product 数组,返回的 Xml 如下所示:

    	<ArrayOfProduct xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/WebApi.Models">
        <Product>
            <Id>1</Id>
            <MarketDate>2017-08-11T16:54:39.4142813+08:00</MarketDate>
            <Tags xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
                <d3p1:string>Tag1</d3p1:string>
                <d3p1:string>Tag2</d3p1:string>
            </Tags>
            <Type>Type1</Type>
            <myname>Computer</myname>
        </Product>
        <Product>
            <Id>2</Id>
            <MarketDate>2017-08-11T08:54:39.4142813Z</MarketDate>
            <Tags xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
                <d3p1:string>Tag1</d3p1:string>
                <d3p1:string>Tag2</d3p1:string>
            </Tags>
            <Type>Type2</Type>
            <myname>Telphone</myname>
        </Product>
    </ArrayOfProduct>
    

      如果需要对序列化的结果又更多的控制,那么可以对将要序列化的类型使用 DataContract 进行标记,被标记的类型将按照如下的规则进行序列化:

    • 属性和字段默认不被序列化,只有标记为 DataMember 的成员才会被序列化
    • 私有(Private) 和 保护(Protected) 的成员如果被标记为 DataMember 也将会被序列化
    • 只读属性不会被序列化
    • 设置 DataContract 属性的 Name 的参数可以自定义序列化结果 Xml 中的类型名称
    • 设置 DataMember 属性的 Name 参数可以自定义序列化 后结果 Xml 中对应字段或属性的名称
    • 设置 DataContract 属性的 NameSpace 参数可以自定义序列化后结果中的命名空间的名称
    只读属性

      只读属性不会被序列化,但如果该属性后面有一个私有的字段的话,那么可以在该私有字段上加以 DataMember 修饰 ,
      那么该私有字段便可以被序列化。

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

      Xml序列化中 ** Date** 全部使用 ISO 8601 格式,例如 2012-05-23T20:21:37.9116538Z

    Xml 格式化缩进

      为了产生具有缩进格式的 Xml ,可以 将Intent 属性设置为 TRUE

    	var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
        xml.Indent = true;
    
    为每个 类型设置不同的序列化器

      可以为不同的 CLR 类型设置不同的序列化器,例如,在某种情况下,某个特定的数据对象为了保持良好的向后兼容 行,需要使用 XmlSerializer,这时便可为该类型使用 XmlSerializer,而为其它类型使用 DataConstractSerializer.
      可以调用 SetSerializer 方法来为某个类型设置特定的序列化器

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

    移除 Json 或 Xml 格式化器

      我们可以从格式化器(formatter) 列表中移除 JsonXml 格式化器,例如,基与下面的原因可能需要采取这种操作

    • 默认情况下,客户端可以通过设置 **Http ** 请求头部的 Accept 字段来设置其接受的数据的媒体类型,如果要限制 Web API 仅返回指定的媒体类型(media type),例如 Json,那么便可以移除 XmlSerializer
    • 使用自定义的格式化器取代默认的格式化器。

    下面的代码展示了如何移除默认的格式化器,需要在 App_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;
        }
    }
    

      调用上面的 Action 方法 Get 会导致序列化器抛出一个异常,然后返回一个 500
    的状态码响应给客户端。
      为了在生成的 JSon 数据中保留对象的引用,可以在 Global.asax 文件的 App_Start 方法中添加如下的代码:

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

      此时,该Action 返回的 Json 数据如下所示:

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

      序列化器给两个相互引用的对象都添加了一个 $id 属性,检测到 Employee.Department 创造了一个循环引用,一次使用 $ref 替代了引用的值。
      为了在 Xml 序列化过程中保留这用对象的引用关系,有两种可行的方法,第一种将 DataContract 属性的 IsReference 属性设置为 True,如下所示:

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

      另一种方式是创建一个 **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);
    

      生成的 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>
    

     *注意* 在实用上述的保留对象引用的功能时,要考虑接受数据的客户端是否能够解析这样格式的数据,在多数情况下应该尽量避免对象之间的循环引用。

  • 相关阅读:
    java第二次作业 数组和String类
    java第一次作业
    选择
    latex math
    sum的写法
    qt 4.8.5 vs 2012编译
    物联网笔记四:物联网网络及协议
    物联网学习笔记三:物联网网关协议比较:MQTT 和 Modbus
    物联网学习笔记二:物联网网关
    物联网学习笔记一:物联网入门的必备 7 大概念和技能
  • 原文地址:https://www.cnblogs.com/ITusk/p/7677010.html
Copyright © 2011-2022 走看看