發現問題
在 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?
