2014-10-28 77 views
1

WCF Json反序列化。WCF Json反序列化保存多態集合類型

我正在WCF中使用Dotnet 4.5構建一箇中間件webservice,該服務器返回一個多態類型。

[DataContract] 
[KnownType(typeof(SomethingA))] 
[KnownType(typeof(SomethingB))] 
public class Something 
{ 
    [DataMember] 
    public int Item1 { get; set; } 

    [DataMember] 
    public string Item2 { get; set; } 
} 

[DataContract] 
public class SomethingA : Something 
{ } 

[DataContract] 
public class SomethingB : Something 
{ } 


/// <summary> 
/// Contract for a service for testing various web operations. 
/// </summary> 
[ServiceContract] 
[ServiceKnownType(typeof(SomethingA))] 
[ServiceKnownType(typeof(SomethingB))] 
public interface ITesting 
{ 
    /// <summary> 
    /// Test passing in and returning an object using POST and json. 
    /// </summary> 
    [OperationContract] 
    [WebInvoke(
     RequestFormat = WebMessageFormat.Json, 
     ResponseFormat = WebMessageFormat.Json, 
     BodyStyle = WebMessageBodyStyle.Bare, 
     UriTemplate = "use-polymorphic-somethings", 
     Method = "POST")] 
    List<Something> UsePolymorphicSomethings(); 
} 

/// <summary> 
/// Implementation of the ITesting service contract. 
/// </summary> 
public class Testing : ITesting 
{ 
    public List<Something> UsePolymorphicSomethings() 
    { 
     List<Something> retVal = new List<Something>(); 
     retVal.Add(new SomethingA { Item1 = 1, Item2 = "1" }); 
     retVal.Add(new SomethingB { Item1 = 1, Item2 = "1" }); 
     return retVal; 
    } 
} 

在客戶端,我試圖反序列化這種方式來保存集合中的不同類型。 MSDN文檔對我來說似乎非常薄弱。我遇到的第一個問題是添加一個對System.Web.Http的引用,在第三方開源組件Newtonsoft.Json上創建了一個未公開的動態依賴關係,我必須從Web上下載它。

前兩種反序列化方法失敗,但我發現了第三種方法。

我想知道的是爲什麼前兩種方法失敗?理想情況下,我希望得到第一種工作方式,因爲這是最簡化的。

[TestMethod] 
public void UsePolymorphicSomethings_Test1() 
{ 
    using (HttpClient http = new HttpClient()) 
    { 
     http.BaseAddress = new Uri("http://localhost:8733/"); 

     HttpResponseMessage response = http.PostAsJsonAsync(
     "Design_Time_Addresses/InSite8WebServiceLib2/Testing/use-polymorphic-somethings", 
     new StringContent(string.Empty)).Result; 

     List<Something> ret = response.Content.ReadAsAsync<List<Something>>().Result; 

     // FAILS. 
     Assert.AreEqual(typeof(SomethingA), somethings[0].GetType()); 
     Assert.AreEqual(typeof(SomethingB), somethings[1].GetType()); 
    } 
} 

[TestMethod] 
public void UsePolymorphicSomethings_Test2() 
{ 
    using (HttpClient http = new HttpClient()) 
    { 
     http.BaseAddress = new Uri("http://localhost:8733/"); 

     HttpResponseMessage response = http.PostAsJsonAsync(
     "Design_Time_Addresses/InSite8WebServiceLib2/Testing/use-polymorphic-somethings", 
     new StringContent(string.Empty)).Result; 

     string ret1 = response.Content.ReadAsStringAsync().Result; 
     Newtonsoft.Json.JsonSerializerSettings s = new Newtonsoft.Json.JsonSerializerSettings(); 
     s.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All; 
     List<Something> r = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Something>>(ret1, s); 

     // FAILS. 
     Assert.AreEqual(typeof(SomethingA), somethings[0].GetType()); 
     Assert.AreEqual(typeof(SomethingB), somethings[1].GetType()); 
    } 
} 

[TestMethod] 
public void UsePolymorphicSomethings_Test3() 
{ 
    using (HttpClient http = new HttpClient()) 
    { 
     http.BaseAddress = new Uri("http://localhost:8733/"); 

     HttpResponseMessage response = http.PostAsJsonAsync(
     "Design_Time_Addresses/InSite8WebServiceLib2/Testing/use-polymorphic-somethings", 
     new StringContent(string.Empty)).Result; 

     Stream stream = response.Content.ReadAsStreamAsync().Result; 
     DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(List<Something>)); 
     List<Something> somethings = (List<Something>)serializer.ReadObject(stream); 

     // SUCCEEDS. 
     Assert.AreEqual(typeof(SomethingA), somethings[0].GetType()); 
     Assert.AreEqual(typeof(SomethingB), somethings[1].GetType()); 
    } 
} 
+0

嗨,我相信最後一種方法工作的原因,其他兩個沒有。是因爲當您使用WebMessageFormat.Json時,使用服務器端的序列化程序是DataContractJsonSerializer。所以使用相同的串行器服務器和客戶端都是有道理的,最終會取得令人滿意的結果。例如,如果你想使用NewstonSoft,你必須側身,創建一個消息格式化類,一個Web http行爲,一個行爲擴展元素和一個web內容類型映射器,然後你需要把所有這些都綁定起來,可能是網頁或應用程序配置文件。 – 2014-11-04 05:56:07

+0

我明白了。在這種情況下,UsePolymorphicSomethings_Test1必須使用不同的具有JSON能力的反序列化器(因爲它確實執行反序列化,只是不正確),這引發了幾個問題。哪個JSON序列化程序默認使用ReadAsAsync?爲什麼它默認不使用DataContractJsonSerializer?並且可以將ReadAsAsync配置爲默認使用DataContractJsonSerializer,因爲它的默認配置是無用的? – Neutrino 2014-11-07 13:33:57

+0

如果我不必使用Newtonsoft,我真的不想使用Newtonsoft,我嘗試使用Newtonsoft類反序列化的唯一原因是因爲我的集成測試程序集中的System.Net.Http創建了一個動態(運行時)對它的依賴這讓我猜想它可能以某種方式與這個問題有關。 – Neutrino 2014-11-07 13:34:26

回答

2

根據我的理解,您對您編寫的代碼的流內容感到「擔心」。看到你的代碼在你的最後一個方法中工作,並且我希望我的理由能夠工作,而其他人不會讓你滿意。我們仍然可以使用一個簡單的幫助程序對您的方法進行流式處理。

public T Deserialize<T>(Stream stream) where T : class 
    { 
     var serializer = new DataContractJsonSerializer(typeof(T)); 
     return (T)serializer.ReadObject(stream); 
    } 

然後你可以簡單地調用這個方法,像這樣

List<Something> somethings = Deserialize<List<Something>>(stream); 

要在某種意義上使事情也許更容易,你可以寫輔助方法爲擴展方法, 像這樣

public static class Helpers 
{ 
    public static T Deserialize<T>(this Stream stream) where T : class 
    { 
     var serializer = new DataContractJsonSerializer(typeof(T)); 
     return (T)serializer.ReadObject(stream); 
    } 
} 

然後,您可以調用這個方法這樣

var result = stream.Deserialize<List<Something>>(); 

要一路走下去,你可以對HttpResponseMessage創建擴展方法

public static class Helpers 
{ 
    public static T Deserialize<T>(this HttpResponseMessage response) where T : class 
    { 
     var stream = response.Content.ReadAsStreamAsync().Result; 
     var serializer = new DataContractJsonSerializer(typeof(T)); 
     return (T)serializer.ReadObject(stream); 
    } 
} 

你可以調用這個方法這樣

var result = response.Deserialize<List<Something>>(); 

至少你的代碼將現在又是一個班輪,如果你將來改變你的序列化程序,你只需要在一個地方改變你的客戶代碼。您可能需要檢查代碼,因爲我目前沒有打開Visual Studio爲您測試它。但對我來說看起來不錯。

我在這裏添加了一個新的幫助程序示例,因此有更多的選項/修復程序供您選擇。

public static class Helpers 
{ 
    public static Task<T> ReadAsAsyncCustom<T>(this HttpContent content) 
    { 
     var formatters = new MediaTypeFormatterCollection(); 
     formatters.Clear(); 
     formatters.Add(new JsonMediaTypeFormatter { UseDataContractJsonSerializer = true }); 
     return content.ReadAsAsync<T>(formatters); 
    } 
} 

和如下

List<Something> ret = response.Content.ReadAsAsyncCustom<List<Something>>().Result; 

我之所以呼籲在輔助方法格式化變量明確這一個可以被使用,是因爲MediaTypeFormatterCollection的構造函數創建默認格式化,我們是不是對這些內容感興趣,所以我清除它們並添加一個我們知道適用於您的解決方案的格式化程序。

我一般儘量堅持到乾燥的原則,這是我努力保持「定製」分離只是一個單一的地方,所以,當事情發生變化,我並不需要經過所有的源代碼並試着記住的原因或搜索可能已被使用的所有實例。

把東西用另一種方式爲好,而框架並支持你的情況下,它當然需要,如果你將一個設置更改。如果你不使用你所謂的多態類型,那麼標準的開箱即用方法將會很好。我今天早上寫了一個模擬你的解決方案,我看不到一個快速的方法,可以幫助你在客戶端沒有改變的情況下離開。

+0

對不起,我可能不夠清楚。我試圖瞭解什麼反序列化機制ReadAsAsync使用它導致它反序列化JSON不正確,不只隱藏另一種方法的解決方法代碼。 – Neutrino 2014-11-07 13:44:50

+0

嗨中微子,謝謝你的清理。事實上,Jose實際上觸及了這一點。客戶端的問題與他所說的完全相同,並且按照他所述發送參數確實解決了問題,但是,無論哪種方式,您都必須「改變」代碼的工作方式。所以除非你只在應用程序中有一個調用,否則我仍然建議你使用帶有擴展方法的輔助類,或者至少沿着這些方向。至少在我的例子中,「定製」是在一個地方,應該在未來發生變化,變化很簡單 – 2014-11-08 02:51:02

+0

因此,除了上述示例工作,我爲你準備,現在我要更新它,以包括一種方法,你可以選擇最適合你的方式。 – 2014-11-08 02:51:57

0

在我看來,方法1和2實例化一個T類型的對象,然後通過讀取流來設置它的屬性。換句話說,這些方法只知道類型「Something」,因此它們只能實例化「Something」。第三種方法也使用屬性DataContract和KnownType,因此它能夠實例化已知類型「Something」,「SomethingA」和「SomethingB」。

enter image description here

+0

這正在發生。我試着去了解_爲什麼會發生這種情況。在UsePolymorphicSomethings_Test1的ReadAsAsync方法必須使用某種形式的反序列化的內部,該JSON傳遞明確包含有利於多態的反序列化所需的類型信息(因爲UsePolymorphicSomethings_Test3正常工作),所以反序列化機制在默認情況下使用,爲什麼沒有按什麼ReadAsAsync」問題仍然是t正確地反序列化。 – Neutrino 2014-11-07 13:41:17

+0

ReadAsAsync未正確序列化,因爲它沒有使用DataContract和KnownType屬性。默認情況下,ReadAsAsync使用UseDataContractJsonSerializer屬性設置爲[JsonMediaTypeFormatter](http://msdn.microsoft.com/en-us/library/system.net.http.formatting.jsonmediatypeformatter%28v=vs.118%29.aspx)假。如果要使用這些屬性進行序列化,則需要使用DataContractJsonSerializer或ReadAsAsync傳遞new [] {new JsonMediaTypeFormatter {UseDataContractJsonSerializer = true}}。我編輯了我最初的迴應,爲此設置了一個例子。 – 2014-11-07 23:19:18