【问题标题】:WCF Json deserialization preserve polymorphic collection typesWCF Json 反序列化保留多态集合类型
【发布时间】:2014-10-28 10:33:00
【问题描述】:

WCF Json 反序列化。

我正在使用 Dotnet 4.5 在 WCF 中构建中间件 web 服务,此服务器返回多态类型。

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

【问题讨论】:

  • 嗨,我相信最后一种方法有效而其他两种方法无效的原因。是因为当你使用 WebMessageFormat.Json 时,服务器端使用的序列化器是 DataContractJsonSerializer。因此,在服务器端和客户端使用相同的序列化程序最终会得到满意的结果是有道理的。例如,如果您想使用 NewstonSoft,您将不得不在服务器端创建一个消息格式化程序类、一个 Web http 行为、一个行为扩展元素和一个 Web 内容类型映射器,然后您需要将所有这些连接起来,可能在Web 或应用程序配置文件。
  • 我明白了。在这种情况下,UsePolymorphicSomethings_Test1 必须使用不同的支持 JSON 的反序列化器(因为它确实执行了反序列化,只是不正确),这引发了几个问题。 ReadAsAsync 默认使用哪个 JSON 序列化程序?为什么默认不使用 DataContractJsonSerializer?并且可以不将 ReadAsAsync 配置为默认使用 DataContractJsonSerializer,因为它的默认配置是无用的?
  • 如果不需要,我真的不想使用 Newtonsoft,我尝试使用 Newtonsoft 类反序列化的唯一原因是因为 System.Net.Http 在我的集成测试程序集中创建了一个对它的动态(运行时)依赖,这使我推测它可能在某种程度上与这个问题有关。

标签: c# json wcf serialization


【解决方案1】:

据我了解,您“担心”您编写的代码的流线型。看到您的代码在您的最后一种方法中工作,我希望我的工作原因和其他原因不能让您满意。我们仍然可以使用简单的帮助器稍微简化您的方法。

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;

我在辅助方法中对格式化程序变量调用 clear 的原因是因为 MediaTypeFormatterCollection 的构造函数创建了默认格式化程序,我们对这些不感兴趣,所以我清除它们并仅添加 1 个我们知道可以使用的格式化程序你的解决方案。

我通常会尝试坚持 DRY 规则,这就是我尝试将“定制”隔离在一个地方的原因,这样当事情发生变化时,我就不需要遍历所有源代码并尝试记住或搜索所有可能使用过它的实例。

换一种说法,虽然框架确实支持您的方案,但如果您愿意,它当然需要更改设置。如果您不使用所谓的多态类型,那么标准的开箱即用方法会很好用。我今天早上写了一个模仿你的解决方案,我找不到一种快速的方法可以帮助你在不改变客户端的情况下摆脱困境。

【讨论】:

  • 对不起,我可能不够清楚。我试图了解 ReadAsAsync 使用什么反序列化机制导致它错误地反序列化 JSON,而不仅仅是在另一种方法中隐藏解决方法代码。
  • 嗨 Neutrino,感谢您解决这个问题。事实上,何塞很好地触及了这一点。问题客户端正如他所说的那样,是的,按照他的说法发送参数确实可以解决问题,但是,无论哪种方式,您都将不得不“改变”代码的工作方式。因此,除非您在应用程序中只有一个调用,否则我仍然建议您使用带有扩展方法的辅助类,或者至少使用类似的方法。至少在我的例子中,“定制”在一个地方,如果将来发生变化,改变很简单
  • 因此,除了我为您提供的上述示例之外,我现在将对其进行更新,以包含更多方法,您可以选择最适合您的方法。跨度>
【解决方案2】:

在我看来,方法 1 和 2 实例化了一个 T 类型的对象,然后通过读取流来设置它的属性。换句话说,这些方法只知道类型“Something”,因此它们只能实例化“Something”。第三种方法也使用属性 DataContract 和 KnownType,因此它能够实例化已知类型“Something”、“SomethingA”和“SomethingB”。

【讨论】:

  • 这就是发生了什么。我试图了解为什么会发生这种情况。在 UsePolymorphicSomethings_Test1 中,ReadAsAsync 方法必须在内部使用某种形式的反序列化,传输的 JSON 清楚地包含促进多态反序列化的必要类型信息(因为 UsePolymorphicSomethings_Test3 工作正常),所以问题仍然是 ReadAsAsync 默认使用什么反序列化机制以及为什么不使用t 正确反序列化。
  • ReadAsAsync 未正确序列化,因为它不使用属性 DataContract 和 KnownType。默认情况下,ReadAsAsync 使用 JsonMediaTypeFormatter 并将 UseDataContractJsonSerializer 属性设置为 false。如果要使用这些属性进行序列化,则需要使用 DataContractJsonSerializer 或 ReadAsAsync 传递 new[]{new JsonMediaTypeFormatter{UseDataContractJsonSerializer = true}} 。我编辑了我的初始回复以设置一个例子。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-09
  • 2016-07-16
  • 1970-01-01
  • 2016-11-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多