By default, the JSON and XML formatters write all objects as values. If two properties refer to the same object, or if the same object appears twice in a collection, the formatter will serialize the object twice. This is a particular problem if your object graph contains cycles, because the serializer will throw an exception when it detects a loop in the graph.
在默认情况下,JSON和XML格式化器将所有对象都写成值。如果两个属性引用了同一个对象,或者,如果在一个集合同一个对象出现了两次,格式化器将对此对象做两次序列化。这是在对象图含有循环的情况下会出现的特有问题,因为,序列化器在检测到对象图中的循环时,会抛出异常(故格式化器会预先通过两个序列化,来消除这种循环对象引用 — 译者注)。
Consider the following object models and controller.
考虑以下对象模型和控制器。
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;
}
}
Invoking this action will cause the formatter to thrown an exception, which translates to a status code 500 (Internal Server Error) response to the client.
调用此动作会触发格式化器招聘异常,该异常将转换成发送给客户端的状态代码500(内部服务器错误)响应。
To preserve object references in JSON, add the following code to Application_Start method in the Global.asax file:
为了保留JSON中的对象引用,对Global.asax文件的Application_Start方法添加以下代码:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter; json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All;
Now the controller action will return JSON that looks like this:
现在,此控制器动作将返回类似于如下形式的JSON:
{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}
Notice that the serializer adds an "$id" property to both objects. Also, it detects that the Employee.Department property creates a loop, so it replaces the value with an object reference: {"$ref":"1"}.
注意,序列化器对两个对象都添加了一个“$id”。而且,它检测到Employee.Department属性产生了一个循环,因此,它用一个对象引用{"$ref":"1"}代替这个值。
Object references are not standard in JSON. Before using this feature, consider whether your clients will be able to parse the results. It might be better simply to remove cycles from the graph. For example, the link from Employee back to Department is not really needed in this example.
对象引用是不标准的JSON。在使用此特性之前,要考虑你的客户端是否能够解析这种结果。简单地去除对象图中的循环,可能是更好的办法。例如,此例中Employee链接回Department并不是真正的需要。
To preserve object references in XML, you have two options. The simpler option is to add [DataContract(IsReference=true)] to your model class. The IsReference parameter enables object references. Remember that DataContract makes serialization opt-in, so you will also need to add DataMember attributes to the properties:
为了保留XML中的对象引用,可以使用两个选项。较简单的选项是对模型类添加[DataContract(IsReference=true)]。IsReference参数启用了对象引用。记住,DataContract构成了序列化的“选入(Opt-in)”,因此,你还需要对属性添加DataMember注解属性(使用了这一注解属性的模型属性,才被选入(Opt-in)为序列化对象 — 译者注):
[DataContract(IsReference=true)]
public class Department
{
[DataMember]
public string Name { get; set; }
[DataMember]
public Employee Manager { get; set; }
}
Now the formatter will produce XML similar to following:
现在,该格式化器将产生类似于如下形式的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>
If you want to avoid attributes on your model class, there is another option: Create a new type-specific DataContractSerializer instance and set preserveObjectReferences to true in the constructor. Then set this instance as a per-type serializer on the XML media-type formatter. The following code show how to do this:
如果想避免在模型类上使用注解属性,还有另一个选项:创建亲的类型专用的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);
6.2.7 Testing Object Serialization6.2.7 测试对象序列化