Web API 为 JSON 和 XML 提供媒体类型格式化程序。 默认情况下,框架将这些格式化程序插入管道。 客户端可以在 HTTP 请求的 Accept 标头中请求 JSON 或 XML。
测试对象序列化
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 文档
。
序列化的内容是什么?
默认情况下,所有公共属性和字段都包含在序列化的 JSON 中。 若要省略属性或字段,请使用
JsonIgnore
属性对其进行修饰。
public class Product
public string Name { get; set; }
public decimal Price { get; set; }
[JsonIgnore]
public int ProductCode { get; set; } // omitted
如果更喜欢“选择加入”方法,请使用 DataContract 属性修饰 类。 如果存在此属性,则成员将被忽略,除非它们具有 DataMember。 还可以使用 DataMember 序列化私有成员。
[DataContract]
public class Product
[DataMember]
public string Name { get; set; }
[DataMember]
public decimal Price { get; set; }
public int ProductCode { get; set; } // omitted by default
Read-Only属性
默认情况下,只读属性是序列化的。
默认情况下,Json.NET 以 ISO 8601 格式写入日期。 UTC (协调世界时) 的日期用“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
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
如果希望使用 Microsoft JSON 日期格式 ("\/Date(ticks)\/"
) 而不是 ISO 8601,请在序列化程序设置上设置 DateFormatHandling 属性:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
若要编写缩进 JSON,请将 “格式设置” 设置为 Formatting.Indented:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
Camel 外壳
若要使用 camel 大小写编写 JSON 属性名称,而不更改数据模型,请在序列化程序上设置 CamelCasePropertyNamesContractResolver :
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
匿名对象和Weakly-Typed对象
操作方法可以返回匿名对象并将其序列化为 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 格式设置由 XmlMediaTypeFormatter 类提供。 默认情况下, XmlMediaTypeFormatter 使用 DataContractSerializer 类执行序列化。
如果需要,可以将 XmlMediaTypeFormatter 配置为使用 XmlSerializer 而不是 DataContractSerializer。 为此,请将 UseXmlSerializer 属性设置为 true:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
XmlSerializer 类支持比 DataContractSerializer 更窄的类型集,但可以更好地控制生成的 XML。 如果需要匹配现有 XML 架构,请考虑使用 XmlSerializer 。
XML 序列化
本节介绍使用默认 DataContractSerializer 的 XML 格式化程序的一些特定行为。
默认情况下,DataContractSerializer 的行为如下所示:
将序列化所有公共读/写属性和字段。 若要省略属性或字段,请使用 IgnoreDataMember 属性对其进行修饰。
不序列化专用成员和受保护成员。
只读属性不会序列化。 (但是,只读集合属性的内容是序列化的。)
类名和成员名称在 XML 中编写,与它们在类声明中显示的方式完全相同。
使用默认的 XML 命名空间。
如果需要对序列化进行更多控制,可以使用 DataContract 属性修饰 类。 如果存在此属性,则按如下所示序列化类:
“选择加入”方法:默认情况下不序列化属性和字段。 若要序列化属性或字段,请使用 DataMember 属性对其进行修饰。
若要序列化私有成员或受保护成员,请使用 DataMember 属性对其进行修饰。
只读属性不会序列化。
若要更改类名在 XML 中的显示方式,请在 DataContract 属性中设置 Name 参数。
若要更改成员名称在 XML 中的显示方式,请在 DataMember 属性中设置 Name 参数。
若要更改 XML 命名空间,请在 DataContract 类中设置 Namespace 参数。
Read-Only属性
只读属性不会序列化。 如果只读属性具有后备私有字段,则可以使用 DataMember 属性标记专用字段。 此方法需要 类上的 DataContract 属性。
[DataContract]
public class Product
[DataMember]
private int pcode; // serialized
// Not serialized (read-only)
public int ProductCode { get { return pcode; } }
日期以 ISO 8601 格式编写。 例如,“2012-05-23T20:21:37.9116538Z”。
若要编写缩进 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":
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));
可以指定 XmlSerializer 或任何派生自 XmlObjectSerializer 的对象。
如果不想使用 JSON 格式化程序或 XML 格式化程序,可以从格式化程序列表中删除它们。 执行此操作的主要原因是:
将 Web API 响应限制为特定媒体类型。 例如,你可能决定仅支持 JSON 响应,并删除 XML 格式化程序。
将默认格式化程序替换为自定义格式化程序。 例如,可以将 JSON 格式化程序替换为自己的 JSON 格式化程序自定义实现。
以下代码演示如何删除默认格式化程序。 从 Global.asax 中定义的 Application_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;
调用此操作将导致格式化程序引发异常,该异常转换为状态代码 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 使序列化选择加入,因此还需要将 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 。 然后在 XML 媒体类型格式化程序上将此实例设置为每类型序列化程序。 以下代码演示如何执行此操作:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue,
false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);
测试对象序列化
在设计 Web API 时,测试数据对象的序列化方式非常有用。 无需创建控制器或调用控制器操作即可执行此操作。
string Serialize<T>(MediaTypeFormatter formatter, T value)
// Create a dummy HTTP Content.
Stream stream = new MemoryStream();
var content = new StreamContent(stream);
/// Serialize the object.
formatter.WriteToStreamAsync(typeof(T), value, stream, content, 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
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);