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”。本地时间格式的日期包括了一个时区偏移量。例如:

--27T18::.53403Z         // UTC(标准时间)
--27T11::45.53403-: // 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 = ,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
}

响应消息体将含有以下JSON:

{"Name":"Alice","Age":,"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":"","Name":"Sales","Manager":{"$id":"","Name":"Alice","Department":{"$ref":""}}}

注意,序列化器对两个对象都添加了一个“$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 = ;
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 = ; // 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 = }; 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

上一篇:css选择器的使用详解


下一篇:Rest Web Api Controller 返回JSON格式大小写