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注解属性来修饰类。如果有注解属性,则成员均被忽略,除非有DataMember。DataMember也可以序列化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