如何在REST WCF服务中接受任意JSON对象?

时间:2012-07-13 07:18:19

标签: c# .net json wcf rest

我想实现这样的服务方法:

[OperationContract]
[WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json)]
public void MakeShape(string shape, string color, IDictionary<string, object> moreArgs)
{
    if (shape == "circle")
    {
        MakeCircle(color, moreArgs);
    }
}

我的客户发布了以下对象:

{
    "shape":"circle",
    "color": "blue",    
    "radius": 42,
    "filled":true,
    "annotation": {"date":"1/1/2012", "owner":"George"}
}

在调用MakeCircle时,moreArgs将有3个条目(“radius”,“filled”和一个名为“annotation”的字典,其中包含2个键值对。)


我到目前为止所做的最好的是:

//Step 1: get the raw JSON by accepting a Stream with BodyStyle=WebMessageBodyStyle.Bare
[OperationContract]
[WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle=WebMessageBodyStyle.Bare)]
public void MakeShape(Stream jsonStream)
{
    //Step 2: parse it into a Dictionary with JavaScriptSerializer or JSON.net
    StreamReader reader = new StreamReader(jsonStream);
    JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
    Dictionary<string, object> args = jsSerializer.Deserialize<Dictionary<string,object>>(reader.ReadToEnd());            

    //Step 3: manually lookup and cast the "standard" arguments, and remove them from the Dictionary
    string shape = (string)args["shape"];
    string color = (string)args["color"];            

    //Step 4: make the original call, passing the remaining Dictionary as moreArgs
    MakeShape(shape,color,args);
}

我可以忍受像这样的解决方案,除了第3步将是一个很难与几十种方法保持同步。显然某些东西必须打开字典并使用额外的参数,但我宁愿将该代码保留在我的通信层之外。 IMO它进入了解参数的业务逻辑(在本例中由MakeCircle表示)。

我非常喜欢WCF的自动绑定,因为它消除了这些容易出错的手动翻译。我希望有一种方法可以将它用于几乎所有内容,除了为它不知道如何映射的参数指定一些额外的逻辑。也许有某种服务行为表示“将它们传递给[此代码]并且我会处理它”?


我已经考虑过IExtensibleDataObject提供的“往返”支持,但它似乎没有让我的代码访问未知属性 - 它们仅仅是为了发送回客户端的唯一目的。 http://msdn.microsoft.com/en-us/library/ms731083.aspx


另一种选择是使用包含IDictionary的自定义类,并以某种方式自己接管反序列化。所以服务方法是: [OperationContract的] [WebInvoke(RequestFormat = WebMessageFormat.Json,ResponseFormat = WebMessageFormat.Json,BodyStyle = WebMessageBodyStyle.WrappedRequest)] public void MakeShape(string shape,string color,MoreArgs moreArgs)

我必须强迫客户进入更严格的结构:

{
    "shape":"circle",
    "color": "blue",
    "moreArgs":{
        "radius": 42,
        "filled":true
        "annotation": {"date":"1/1/2012", "owner":"George"}
        }
}

这不太理想,但我可以忍受它。问题变成了如何定义MoreArgs并正确填充一个。我的下一次尝试:

[DataContract]
public class MoreArgs : ISerializable
{
    public Dictionary<string, object> Properties;
    public MoreArgs(SerializationInfo info, StreamingContext context)  
    {
        Properties = new Dictionary<string, object>();  
        foreach (var entry in info)  
        {                      
        Properties.Add(entry.Name, entry.Value);  
        }
    }  
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        foreach (string key in Properties.Keys)
        {
        info.AddValue(key, Properties[key]);
        }  
    }
}

这会在服务启动时抛出InvalidDataContractException(... MoreArgs'不能是ISerializable并且具有DataContractAttribute属性。)

删除[DataContract]属性会抛出我期望的InvalidDataContractException(...无法序列化MoreArgs'。请考虑使用DataContractAttribute属性标记它...)。

同样如预期的那样,删除ISerializable继承会清除异常,但会在调用MakeCircle时导致moreArgs.Properties为null。


我也想知道是否有一些我可以使用的混合解决方案?也许:

  • 接受流并按照我的第一次尝试构建参数字典
  • 使用MoreArgs参数定义方法,如我稍后的尝试
  • 从流中提取的字典中填充MoreArgs对象
  • 以某种方式重新调用WCF,说“如果你有这些参数,则调用将被调用的方法”(指定原始参数字典,加上新的正确填充的MoreArgs)。

MoreArgs也将包含原始参数,但这可能不是灾难。我想我可能会使用反射进行调用,但是当WCF必须在内部调试并调试并优化以启动时,这感觉很愚蠢。

1 个答案:

答案 0 :(得分:3)

@Melissa Avery-Weir

我对我的“解决方案”不满意,但我必须继续前进。

在应用启动时,对于我想调用的每个方法,我在查找表中填充一个MethodInfo。它由接口和方法名称键入。我使用反射和自定义属性来查找它们,但是这里可以使用任何数量的技术。

我的一个WCF服务方法接受接口名称,方法名称和Stream作为参数。我使用JSON.NET反序列化Dictionary的参数并将其传递给我的调度程序。

调度程序按接口和方法名称查找MethodInfo。然后,将字典中的参数与MethodInfo中的参数进行匹配,我填充一个参数数组。如果目标方法实际上有一个Dictionary moreArgs参数,它将获得任何不匹配的参数。最后,我调用MethodInfo.Invoke,传递新填充的参数数组。

要做一些WCF 几乎为我做的事情,有很多繁琐的代码,但我找不到更好的解决方案。

自己控制这一切有一些好处。我最喜欢的是能够使用保存的MethodInfos以我想要的任何语言自动生成客户端调用存根。

如果后期绑定结果是性能问题,我会考虑手动将所有调用放在一个大开关中(methodName)。如果我的接口仍在频繁更改,我可能会尝试生成该样板绑定代码作为构建步骤。可能我永远不会打扰,因为我会在数据库访问上遇到瓶颈。