我想实现这样的服务方法:
[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 必须在内部具有此功能、调试和优化以启动时,这感觉很愚蠢。