发现问题
在 ASP.NET WebAPI 项目中,有这样的 ViewModel 类:
[Serializable]
class Product
{
public int Id { get; set; }
public decimal Price { get; set; }
public DateTime ProductDate { get; set; }
}
Controller 和 Action 代码如下:
public class ProductController : ApiController
{
public Product Get(int id)
{
return new Product()
{
Id = 1,
Price = 12.9m,
ProductDate = new DateTime(1992, 1, 1)
};
}
}
客户端请求该资源: http://localhost:5000/api/product/1
,结果发现 WebAPI 返回这样的JSON,如下:
{
"<Id>k__BackingField": 1,
"<Price>k__BackingField": 12.9,
"<ProductDate>k__BackingField": "1992-01-01T00:00:00"
}
我们知道,自动属性虽然没有定义字段,但是C#编译器会生成相应的私有字段,类似 private int <Id>k__BackingField
。
我们期望 WebAPI 序列化时将属性名作为 JSON 的键,而这里 WebAPI 序列化的却是编译器生成的私有字段,显然不符合我们的要求。
奇怪的地方是,如果单独用 Json.NET 类库去序列化,则能得到期望的 JSON,如下:
{
"Id": 1,
"Price": 12.9,
"ProductDate": "1992-01-01T00:00:00"
}
找到原因
经过 Google 一番,原来是和 SerializableAttribute 有关。
从 Json.NET 4.5 Release 2 版本开始,新增这样的特性:
如果检测到类型有 SerializableAttribute,将序列化该类型的所有私有/公开字段,并且忽略其属性。
如果不想要这个新特性,可以对类应用 JsonObjectAttribute 来覆盖,或者在全局范围内 设置 DefaultContractResolver 的 IgnoreSerializableAttribute 为 true。
而从 release 3 版本开始 IgnoreSerializableAttribute 默认为 true。
而 ASP.NET WebAPI 依赖 Json.NET,但是却将 IgnoreSerializableAttribute 设置为 false,也就是不忽略 SerializableAttribute,导致如果类用 [SerializableAttribute] 修饰,就只(反)序列化字段而忽略属性,于是当用自动属性时,输出的就是编译器自动生成的字段名。
解决问题
了解到问题的原因后,可以通过以下方式解决:
- 去掉 [Serializable]
- 应用 [JsonObjectAttribute]
- 设置 IgnoreSerializableAttribute
最简单的方法就是去掉 [Serializable],如果由于某些原因不能去除,可以用其他两种办法。
应用 JsonObjectAttribute
[Newtonsoft.Json.JsonObject]
[System.Serializable]
class Product
{
public int Id { get; set; }
public decimal Price { get; set; }
public DateTime ProductDate { get; set; }
}
设置 IgnoreSerializableAttribute
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// 其他代码省略 .........
// 将 SerializerSettings 重置为默认值 IgnoreSerializableAttribute = true
config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings();
}
}
参考:
Json.NET 4.5 Release 2 – Serializable support and bug fixes
Why won't Web API deserialize this but JSON.Net will?