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>

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

转载于:https://www.cnblogs.com/ITusk/p/7677010.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值