次のようなサービスメソッドを実装したいと思います。
[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"、および2つのキーと値のペアを含む "annotation"という名前の辞書)があります。
私がこれまでに得た最高のものは次のとおりです。
//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
もう1つのオプションは、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がこの関数を内部でデバッグし、起動するように最適化する必要がある場合、それはばかげていると感じます。