8

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

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

我的客户 POST 对象,例如:

{
    "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 继承会清除异常,但会导致 moreArgs.Properties 在调用 MakeCircle 时为空。


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

  • 像我第一次尝试一样接受流并构造参数字典
  • 使用 MoreArgs 参数定义方法,就像我后来的尝试一样
  • 从从流中提取的字典中填充一个 MoreArgs 对象
  • 不知何故重新调用 WCF,说“如果你有这些参数,调用将被调用的方法”(指定原始参数字典,加上新的正确填充的 MoreArgs)。

然后 MoreArgs 也将包含原始参数,但这可能不是一场灾难。我想我可能可以使用反射进行我需要的调用,但是当 WCF 必须在内部具有此功能、调试和优化以启动时,这感觉很愚蠢。

4

1 回答 1

3

@Melissa Avery-Weir

I'm not happy with my "solution" but I had to move forward.

At app startup, for every method I want to call, I stuff a MethodInfo in a lookup table. It's keyed by an interface and method name. I use reflection and a custom attribute to find them, but any number of techniques could work here.

My one WCF service method accepts an interface name, a method name, and a Stream as arguments. I use JSON.NET to deserialize arguments to a Dictionary and pass that to my dispatcher.

The dispatcher looks up the MethodInfo by interface and method name. Then, matching arguments from the dictionary to parameters in the MethodInfo, I fill an argument array. If the target method actually has a Dictionary moreArgs parameter, it gets any unmatched arguments. Finally I call MethodInfo.Invoke, passing the freshly populated argument array.

It's a lot of fiddly code to do some things that WCF almost does for me, but I didn't find a better solution.

There are some benefits of controlling all this myself. My favorite is the ability to use the saved MethodInfos to automatically generate client-side call stubs in whatever language I want.

If the late binding turns out to be a performance issue I'll consider putting all the calls manually in a big switch(methodName). If my interfaces are still changing frequently, I may try to generate that boilerplate binding code as a build step. Probably I'll never bother since I'll be bottlenecked on DB access.

于 2013-01-09T00:13:04.873 回答