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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM