【问题标题】:Generic WCF JSON Deserialization通用 WCF JSON 反序列化
【发布时间】:2011-01-18 20:48:33
【问题描述】:

我对 WCF 有点陌生,我会尽量清楚地描述我正在尝试做的事情。

我有一个使用 JSON 请求的 WCF 网络服务。在大多数情况下,我在发送/接收 JSON 方面做得很好。例如,以下代码运行良好且符合预期。

JSON 发送:

{ "guy": {"FirstName":"Dave"} }

WCF:

    [DataContract]
    public class SomeGuy
    {
        [DataMember]
        public string FirstName { get; set; }
    }

    [OperationContract]
    [WebInvoke(Method = "POST",
               BodyStyle = WebMessageBodyStyle.WrappedRequest,
               RequestFormat = WebMessageFormat.Json,
               ResponseFormat = WebMessageFormat.Json)]
    public string Register(SomeGuy guy)
    {
        return guy.FirstName;
    }

这会按预期返回一个带有“Dave”的 JSON 对象。问题是我不能总是保证我收到的 JSON 将与我的 DataContract 中的成员完全匹配。例如,JSON:

{ "guy": {"firstname":"Dave"} }

将无法正确序列化,因为大小写不匹配。 guy.FirstName 将为空。这种行为是有道理的,但我真的不知道如何解决这个问题。我是否必须在客户端强制使用字段名称,还是有办法在服务器端进行协调?

一个可能相关的问题:我可以接受通用 JSON 对象并将其序列化为 StringDictionary 或某种简单的键值结构吗?所以无论在 JSON 中发送什么字段名称,我都可以访问已发送给我的名称和值吗?现在,我可以读取我收到的数据的唯一方法是它是否与预定义的 DataContract 完全匹配。

【问题讨论】:

  • 当然你可以保证json符合合同。只有当您无法控制客户端时,您才能控制它。如果您无法控制客户端,那么它可能在您的服务域之外,并且这一点没有实际意义。我没有看到问题。我看到的所有解决方法都是徒劳的,只会增加痛苦。阅读 DataContract/DataMember 上的规范并遵循它们。 2 比索。
  • 你是对的。从技术上讲,我可以在发送信息之前将其写入 JS。而且,我在另一条评论中暗示这可能是我最终会做的事情。不过,我希望服务能够处理任意数量的事先不一定知道的字段。那有意义吗?我只想要一个服务,它可以接受完全任意的“键”:“值”对列表,然后决定如何处理它们,而无需事先知道“键”名称。

标签: .net wcf json serialization datacontractserializer


【解决方案1】:

这是将 json 读入字典的另一种方法:

[DataContract]
public class Contract
    {
    [DataMember]
    public JsonDictionary Registration { get; set; }
    }

[Serializable]
public class JsonDictionary : ISerializable
    {
    private Dictionary<string, object> m_entries;

    public JsonDictionary()
        {
        m_entries = new Dictionary<string, object>();
        }

    public IEnumerable<KeyValuePair<string, object>> Entries
        {
        get { return m_entries; }
        }

    protected JsonDictionary(SerializationInfo info, StreamingContext context)
        {
        m_entries = new Dictionary<string, object>();
        foreach (var entry in info)
            {
            m_entries.Add(entry.Name, entry.Value);
            }
        }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
        foreach (var entry in m_entries)
            {
            info.AddValue(entry.Key, entry.Value);
            }
        }
    }

【讨论】:

【解决方案2】:

顾名思义,数据合约是一组规则。如果您想可靠地将消息映射到操作,则需要遵循这些规则。

为什么不能保证大小写正确?如果您只想使用 JavaScript 中的小写标识符,您可以使用 MessageParameter 属性 - 但您仍然必须选择 特定 名称。

理论上您可以接受原始 JSON 并手动对其进行反序列化(只需获取一个字符串参数并使用任何 JSON 库进行反序列化),但这确实不符合 WCF 的精神。

我认为您真正需要解决的不是数据协定区分大小写的事实,而是 JSON 没有在客户端正确组合的事实。


如果您想在操作中接受原始 JSON 字符串,请将 BodyStyle 更改为 WebMessageBodyStyle.Bare,并将您的方法签名更改为接受单个字符串参数,该参数将使用发送的任何 JSON 字符串填充客户。

请注意,您得到的只是一个字符串,您必须自己完成所有解析和属性映射以及验证和错误处理。这不是我会选择的路线,但如果您愿意承担所涉及的努力和风险,这是一种潜在的选择。

【讨论】:

  • “您真正需要解决的是 [..] JSON 没有在客户端正确组合在一起的事实。”我完全同意,最后,这可能就是我最终要做的。问题是我必须接受来自许多不使用“包含”或模板结构的预先存在的表单的调用,因此需要大量返回并手动更改字段名称(以及一些相应的 JS)。如何访问原始 JSON?我一直在尝试,但不想弄乱这个问题。
  • @d12:见编辑。我可能不会做出这个选择,但如果你这样做了,一旦你试图弄清楚如何处理原始 JSON,capserOne 的答案可能会帮助你了解一些更精细的细节。
【解决方案3】:

您可以尝试使用小写名称添加另一个 DataMember attribute,但我假设您需要一种不区分大小写的方法来协调成员名称,在这种情况下,使用额外的 DataMember 属性变得不合理。

可以提供IDataContractSurrogate 实现,但这将涉及您的大量额外工作以及大量反思(在静态方式,可以在编译时验证)。

就我个人而言,当涉及到 JSON 时,我已经放弃使用 DataContractSerializer class。相反,我使用Json.NET 来满足我所有的 JSON 需求。我可以看到您可以通过IDataContractSurrogate 将Json.NET 插入DataContractSerializer 的位置,但它仍然会有点粗糙。

考虑到使用DataContractSerializer 的难度,Json.NET 是一个简单的选择。此外,再加上 JSON 的 DataContractSerializer 不能正确处理 DateTimeOffset 值这一事实,这对我来说是显而易见的,特别是因为我在 ASP.NET MVC 环境中工作(这允许我以任何我想要的方式塑造结果)。

如果您使用 JSON 作为编码公开 RESTful 服务,这确实是更好的选择,您可以将其与 WCF 混合搭配,以通过您需要的所有传输和消息协议公开所有端点。

【讨论】:

  • 这很有趣,您是否以某种方式将 Json.NET 与 WCF 结合在一起,或者您是否正在使用自定义 HTTP 处理程序?
  • 我想使用其中一个库来手动反序列化,但我不确定如何覆盖服务接收到调用时发生的默认反序列化。这可能与我上面关于如何访问原始 JSON 的评论/问题相吻合。
  • @Aaronaught:你可以双向进行。 DataContractJsonSerializer 派生自 XmlObjectSerializer,您也可以这样做,只需为其逻辑注入 Json.NET(请注意,这不是微不足道的)。如果您将 ASP.NET MVC 用于 RESTful 接口,那么您要做的就是创建一个带有对象的自定义 ActionResult,并在 ExecuteResult 覆盖中使用 Json.NET 将 JSON 写回输出流。
  • @d12:MSDN 论坛上的这个问题告诉您如何将自己的序列化程序作为默认值注入:social.msdn.microsoft.com/forums/en-US/wcf/thread/…
【解决方案4】:

为了实现我的目标,让服务可以接受完全任意的“key”:“value”对列表作为原始 JSON 字符串,然后决定如何处理它们而无需知道“key”事先命名,我结合了 casper 和 aaron 的建议。

1st,访问原始 JSON 字符串,this MSDN blog was very helpful.

我无法简单地将单个方法参数更改为 String 并将 BodyStyle 更改为 WebMessageBodyStyle.Bare 没有问题。将 BodyStyle 设置为 Bare 时,请确保端点 behaviorConfiguration 设置为 &lt;webHttp/&gt; 而不是 &lt;enableWebScript/&gt;

第二个注意事项是,作为casperOne mentioned,该方法只能有1个参数。此参数需要为 Stream 才能访问原始文本(请参阅上面的 MSDN 博客)。

获得原始 JSON 字符串后,只需将其反序列化为 StringDictionary。我为此选择了 JSON.Net,它运行良好。这是一个简单的例子来说明。

[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare,
    ResponseFormat = WebMessageFormat.Json)]
public string Register(Stream rawJSON)
{ 
    // Convert our raw JSON string into a key, value
    StreamReader sr = new StreamReader(rawJSON);
    Dictionary<string, string> registration =     
        JsonConvert.DeserializeObject<Dictionary<string, string>>(
            sr.ReadToEnd());

    // Loop through the fields we've been sent.
    foreach (KeyValuePair<string, string> pair in registration)
    {
        switch (pair.Key.ToLower())
        {
            case "firstname":
                return pair.Value;
                break;
        }

    }
}

这允许我通过 JSON 接受任意字段列表,并且字段名称不区分大小写。我知道这不是数据完整性或 WCF 服务理想结构的最严格方法,但据我所知,这是到达我想去的地方的最简单方法。

【讨论】:

    【解决方案5】:

    这实际上取决于您使用数据的目的。理论上,您可以通过使用字符串返回类型来保持这种通用性。然而,数据将代表一个对象,客户端控件应该知道如何枚举返回的 JSON 对象。您可以返回类型,这也可能有所帮助。

    然后在您的客户端代码中,您可以拥有一个知道如何确定和读取不同类型的功能。

    【讨论】:

      猜你喜欢
      • 2012-09-27
      • 1970-01-01
      • 1970-01-01
      • 2013-03-01
      • 2019-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多