2014-10-28 2 views
1

WCF Json deserialization.WCF Json deserialization сохраняет полиморфные типы сбора

Я создаю промежуточное webservice в WCF с помощью Dotnet 4.5, этот сервер возвращает полиморфный тип.

[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, который мне пришлось загрузить с Интернета.

Первые два подхода десериализации не работают, но я нашел третий подход, который работает.

Что я хотел бы знать, почему первые два подхода не подходят? В идеале я хотел бы получить первый подход к работе, так как это наиболее оптимизировано.

[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, вам придется перейти на сторону Serer, создать класс форматирования сообщений, поведение веб-http, элемент расширения поведения и конфигуратор типа веб-контента, а затем вам нужно будет подключить все это, возможно, в файлы конфигурации сети или приложения. –

+0

Я вижу. В этом случае UsePolymorphicSomethings_Test1 должен использовать другой десериализатор, совместимый с JSON (поскольку он выполняет десериализацию, просто неправильно), что вызывает несколько вопросов. Какой JSON-сериализатор использует ReadAsAsync по умолчанию? Почему он не использует DataContractJsonSerializer по умолчанию? и может ли ReadAsAsync не настроиться на использование DataContractJsonSerializer по умолчанию, поскольку его конфигурация по умолчанию бесполезна? – Neutrino

+0

Я действительно не хочу использовать Newtonsoft, если мне этого не нужно, единственная причина, по которой я пытался десериализовать использование классов Newtonsoft, заключается в том, что System.Net.Http в моей тестовой сборке интеграции создавал динамическую (runtime) зависимость от нее что заставило меня предположить, что это может быть связано с этим вопросом в некотором роде. – Neutrino

ответ

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>>(); 

По крайней мере, ваш код будет теперь снова будет один лайнер, и если вы в будущем измените свой сериализатор, вам нужно будет только изменить код клиента в одном месте. вы можете проверить код, так как у меня нет визуальной студии, открытой в настоящее время, чтобы проверить ее для вас. Но это выглядит хорошо для меня.

Я добавляю здесь новый пример-помощник, поэтому на выбор есть больше вариантов/исправлений.

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

+0

Привет, Нейтрино, спасибо, что расчистили это. Хосе на самом деле очень хорошо разбирается в этом вопросе. Проблема с клиентской стороной в точности такая же, как он сказал, и, да, отправка параметров, как он заявил, исправляет проблему, однако, в любом случае вам придется «изменить» способ, которым код будет работать в ящике. поэтому, если у вас нет только одного вызова в приложении, я бы порекомендовал вам использовать вспомогательный класс с использованием метода расширения или, по крайней мере, что-то в этом направлении. По крайней мере, как в моем примере, «настройка» находится в одном месте, и в будущем что-то изменится, изменение простое –

+0

Итак, кроме того, что приведенные выше примеры работают для вас, я собираюсь обновить его сейчас, чтобы включите еще один метод, и вы можете выбрать, что лучше всего использовать для вас. –

0

На мой взгляд, методы 1 и 2 создают экземпляр объекта типа T, а затем устанавливают его свойства, читая поток. Другими словами, эти методы только знали тип «Что-то», поэтому они могут только создавать экземпляр «Что-то». Третий метод также использует атрибуты DataContract и KnownType, поэтому он может создавать экземпляры известных типов «Something», «SomethingA» и «SomethingB».

enter image description here

+0

Это что-то происходит. Я пытаюсь понять, почему это происходит. В методе UsePolymorphicSomethings_Test1 метод ReadAsAsync должен использовать внутреннюю часть десериализации, передаваемый JSON явно содержит необходимую информацию о типе, которая облегчает полиморфную десериализацию (так как UsePolymorphicSomethings_Test3 отлично работает), поэтому остается вопрос, какой механизм десериализации используется ReadAsAsync по умолчанию и почему нет " t, который десериализуется правильно. – Neutrino

+0

ReadAsAsync не сериализуется правильно, потому что он не использует атрибуты DataContract и KnownType. По умолчанию ReadAsAsync использует [JsonMediaTypeFormatter] (http://msdn.microsoft.com/en-us/library/system.net.http.formatting.jsonmediatypeformatter%28v=vs.118%29.aspx) с свойством UseDataContractJsonSerializer, установленным для ложный.Если вы хотите сериализовать эти атрибуты, вам нужно использовать DataContractJsonSerializer или ReadAsAsync, передавая новый [] {новый JsonMediaTypeFormatter {UseDataContractJsonSerializer = true}}. Я отредактировал свой первоначальный ответ, чтобы показать пример этого. –

Смежные вопросы