2015-11-05 12 views
7

this blog postから、私はJSON.NETシリアル化を使用するカスタムWCF IDispatchMessageFormatterを作成できました。 1つの注意点でうまくいきます。UriTemplateでそれを使用しても、必ずしも期待どおりに動作するわけではありません。ここでURIテンプレートの逆シリアル化を変更せずにカスタムWCFボディの逆シリアル化を使用する

は、ブログ記事によって提供された実装です:

class NewtonsoftJsonDispatchFormatter : IDispatchMessageFormatter 
{ 
    private readonly OperationDescription od; 
    private readonly ServiceEndpoint ep; 
    private readonly Dictionary<string, int> parameterNames = new Dictionary<string, int>(); 

    public NewtonsoftJsonDispatchFormatter(OperationDescription od, ServiceEndpoint ep, bool isRequest) 
    { 
     this.od = od; 
     this.ep = ep; 
     if (isRequest) 
     { 
      int operationParameterCount = od.Messages[0].Body.Parts.Count; 
      if (operationParameterCount > 1) 
      { 
       this.parameterNames = new Dictionary<string, int>(); 
       for (int i = 0; i < operationParameterCount; i++) 
       { 
        this.parameterNames.Add(od.Messages[0].Body.Parts[i].Name, i); 
       } 
      } 
     } 
    } 
    public void DeserializeRequest(Message message, object[] parameters) 
    { 
     if (message.IsEmpty) 
      return; 

     object bodyFormatProperty; 

     if (!message.Properties.TryGetValue(WebBodyFormatMessageProperty.Name, out bodyFormatProperty) || 
      (bodyFormatProperty as WebBodyFormatMessageProperty).Format != WebContentFormat.Raw) 
     { 
      throw new InvalidOperationException("Incoming messages must have a body format of Raw. Is a ContentTypeMapper set on the WebHttpBinding?"); 
     } 

     XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents(); 
     bodyReader.ReadStartElement("Binary"); 
     byte[] rawBody = bodyReader.ReadContentAsBase64(); 

     using (MemoryStream ms = new MemoryStream(rawBody)) 
     using (StreamReader sr = new StreamReader(ms)) 
     { 
      if (parameters.Length == 1) 
       parameters[0] = Helper.serializer.Deserialize(sr, od.Messages[0].Body.Parts[0].Type); 
      else 
      { 
       // multiple parameter, needs to be wrapped 
       using (Newtonsoft.Json.JsonReader reader = new Newtonsoft.Json.JsonTextReader(sr)) 
       { 
        reader.Read(); 
        if (reader.TokenType != Newtonsoft.Json.JsonToken.StartObject) 
         throw new InvalidOperationException("Input needs to be wrapped in an object"); 
        reader.Read(); 
        while (reader.TokenType == Newtonsoft.Json.JsonToken.PropertyName) 
        { 
         string parameterName = reader.Value as string; 
         reader.Read(); 
         if (this.parameterNames.ContainsKey(parameterName)) 
         { 
          int parameterIndex = this.parameterNames[parameterName]; 
          parameters[parameterIndex] = Helper.serializer.Deserialize(reader, this.od.Messages[0].Body.Parts[parameterIndex].Type); 
         } 
         else 
          reader.Skip(); 
         reader.Read(); 
        } 
       } 
      } 
     } 
    } 

    public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result) { ... } 
} 

は基本的には、DeserializeMethod署名でobject[] parametersは、このメソッドはインスタンス化する必要がoutパラメータです。

[WebInvoke(Method="POST", UriTemplate="foo/")] 
public Foo MakeFoo(Foo foo) { ... } 

またはこのような:

だから、これはこのようなRESTエンドポイント処理するために素晴らしい仕事をしていません

[WebInvoke(Method="POST", UriTemplate="FooBar/")] 
public FooBar FooBar(Foo foo, Bar bar) { .. } 

をそれが現在のURIテンプレートパラメータをマッピングしていませんメソッドパラメータ、例えばこのような何か:

[WebGet(UriTemplate="Foo/{id}")] 
public Foo GetFoo(string id) { ... } 

Microsoftは上書きGetRequestDispatchFormatterに書き込みます:

これは派生行動がの入力パラメータをデシリアライズするために呼び出されIDispatchMessageFormatterの独自の実装を提供するために使用できる拡張ポイントです要求メッセージからのサービス操作。サービス操作のUriTemplateで指定されたパラメータは、要求メッセージのTo URIから逆シリアル化されなければならず、他のパラメータは要求メッセージの本文から逆シリアル化されなければならない。

だから素晴らしい。メッセージの本文からパラメータの逆シリアル化を更新しました。しかし、私はUriTemplateのパラメータをデシリアライズすることを無効にしたくありません。既存のコードを使用して着信URIリクエストをデフォルトの方法でパラメータにマップする方法はありますかUriTemplateが処理されていますか?

UriTemplateDispatchFormatterのようなものを使用する必要があるようですが、これを実装する方法がわかりません。非公開です。

答えて

4

まあ、これは多分、私がやって持っていた最もばかげたことですが、UriTemplateDispatchFormatterのソースコードをコピーし、あなたは単に私がここで提供IDispatchFormatterに対応するIDispatchFormatter「内側」とUriTemplateDispatchFormatterを返すことができます。このクラスは内部作られた理由がわからない> _>

次のクラス定義:次の動作に伴う

class UriTemplateDispatchFormatter : IDispatchMessageFormatter 
{ 
    internal Dictionary<int, string> pathMapping; 
    internal Dictionary<int, KeyValuePair<string, Type>> queryMapping; 
    Uri baseAddress; 
    IDispatchMessageFormatter bodyFormatter; 
    string operationName; 
    QueryStringConverter qsc; 
    int totalNumUTVars; 
    UriTemplate uriTemplate; 

    public UriTemplateDispatchFormatter(OperationDescription operationDescription, IDispatchMessageFormatter bodyFormatter, QueryStringConverter qsc, string contractName, Uri baseAddress) 
    { 
     this.bodyFormatter = bodyFormatter; 
     this.qsc = qsc; 
     this.baseAddress = baseAddress; 
     this.operationName = operationDescription.Name; 
     Populate(
      out this.pathMapping, 
      out this.queryMapping, 
      out this.totalNumUTVars, 
      out this.uriTemplate, 
      operationDescription, 
      qsc, 
      contractName); 
    } 

    public void DeserializeRequest(Message message, object[] parameters) 
    { 
     object[] bodyParameters = new object[parameters.Length - this.totalNumUTVars]; 

     if (bodyParameters.Length != 0) 
     { 
      this.bodyFormatter.DeserializeRequest(message, bodyParameters); 
     } 
     int j = 0; 
     UriTemplateMatch utmr = null; 
     string UTMRName = "UriTemplateMatchResults"; 
     if (message.Properties.ContainsKey(UTMRName)) 
     { 
      utmr = message.Properties[UTMRName] as UriTemplateMatch; 
     } 
     else 
     { 
      if (message.Headers.To != null && message.Headers.To.IsAbsoluteUri) 
      { 
       utmr = this.uriTemplate.Match(this.baseAddress, message.Headers.To); 
      } 
     } 
     NameValueCollection nvc = (utmr == null) ? new NameValueCollection() : utmr.BoundVariables; 
     for (int i = 0; i < parameters.Length; ++i) 
     { 
      if (this.pathMapping.ContainsKey(i) && utmr != null) 
      { 
       parameters[i] = nvc[this.pathMapping[i]]; 
      } 
      else if (this.queryMapping.ContainsKey(i) && utmr != null) 
      { 
       string queryVal = nvc[this.queryMapping[i].Key]; 
       parameters[i] = this.qsc.ConvertStringToValue(queryVal, this.queryMapping[i].Value); 
      } 
      else 
      { 
       parameters[i] = bodyParameters[j]; 
       ++j; 
      } 
     } 
    } 


    public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result) 
    { 
     throw new NotImplementedException(); 
    } 

    private static void Populate(out Dictionary<int, string> pathMapping, 
    out Dictionary<int, KeyValuePair<string, Type>> queryMapping, 
    out int totalNumUTVars, 
    out UriTemplate uriTemplate, 
    OperationDescription operationDescription, 
    QueryStringConverter qsc, 
    string contractName) 
    { 
     pathMapping = new Dictionary<int, string>(); 
     queryMapping = new Dictionary<int, KeyValuePair<string, Type>>(); 
     string utString = GetUTStringOrDefault(operationDescription); 
     uriTemplate = new UriTemplate(utString); 
     List<string> neededPathVars = new List<string>(uriTemplate.PathSegmentVariableNames); 
     List<string> neededQueryVars = new List<string>(uriTemplate.QueryValueVariableNames); 
     Dictionary<string, byte> alreadyGotVars = new Dictionary<string, byte>(StringComparer.OrdinalIgnoreCase); 
     totalNumUTVars = neededPathVars.Count + neededQueryVars.Count; 
     for (int i = 0; i < operationDescription.Messages[0].Body.Parts.Count; ++i) 
     { 
      MessagePartDescription mpd = operationDescription.Messages[0].Body.Parts[i]; 
      string parameterName = XmlConvert.DecodeName(mpd.Name); 
      if (alreadyGotVars.ContainsKey(parameterName)) 
      { 
       throw new InvalidOperationException(); 
      } 
      List<string> neededPathCopy = new List<string>(neededPathVars); 
      foreach (string pathVar in neededPathCopy) 
      { 
       if (string.Compare(parameterName, pathVar, StringComparison.OrdinalIgnoreCase) == 0) 
       { 
        if (mpd.Type != typeof(string)) 
        { 
         throw new InvalidOperationException(); 
        } 
        pathMapping.Add(i, parameterName); 
        alreadyGotVars.Add(parameterName, 0); 
        neededPathVars.Remove(pathVar); 
       } 
      } 
      List<string> neededQueryCopy = new List<string>(neededQueryVars); 
      foreach (string queryVar in neededQueryCopy) 
      { 
       if (string.Compare(parameterName, queryVar, StringComparison.OrdinalIgnoreCase) == 0) 
       { 
        if (!qsc.CanConvert(mpd.Type)) 
        { 
         throw new InvalidOperationException(); 
        } 
        queryMapping.Add(i, new KeyValuePair<string, Type>(parameterName, mpd.Type)); 
        alreadyGotVars.Add(parameterName, 0); 
        neededQueryVars.Remove(queryVar); 
       } 
      } 
     } 
     if (neededPathVars.Count != 0) 
     { 
      throw new InvalidOperationException(); 
     } 
     if (neededQueryVars.Count != 0) 
     { 
      throw new InvalidOperationException(); 
     } 
    } 
    private static string GetUTStringOrDefault(OperationDescription operationDescription) 
    { 
     string utString = GetWebUriTemplate(operationDescription); 
     if (utString == null && GetWebMethod(operationDescription) == "GET") 
     { 
      utString = MakeDefaultGetUTString(operationDescription); 
     } 
     if (utString == null) 
     { 
      utString = operationDescription.Name; 
     } 
     return utString; 
    } 
    private static string MakeDefaultGetUTString(OperationDescription od) 
    { 
     StringBuilder sb = new StringBuilder(XmlConvert.DecodeName(od.Name)); 
     //sb.Append("/*"); // note: not + "/*", see 8988 and 9653 
     if (!IsUntypedMessage(od.Messages[0])) 
     { 
      sb.Append("?"); 
      foreach (MessagePartDescription mpd in od.Messages[0].Body.Parts) 
      { 
       string parameterName = XmlConvert.DecodeName(mpd.Name); 
       sb.Append(parameterName); 
       sb.Append("={"); 
       sb.Append(parameterName); 
       sb.Append("}&"); 
      } 
      sb.Remove(sb.Length - 1, 1); 
     } 
     return sb.ToString(); 
    } 
    private static bool IsUntypedMessage(MessageDescription message) 
    { 

     if (message == null) 
     { 
      return false; 
     } 
     return (message.Body.ReturnValue != null && message.Body.Parts.Count == 0 && message.Body.ReturnValue.Type == typeof(Message)) || 
      (message.Body.ReturnValue == null && message.Body.Parts.Count == 1 && message.Body.Parts[0].Type == typeof(Message)); 
    } 
    private static void EnsureOk(WebGetAttribute wga, WebInvokeAttribute wia, OperationDescription od) 
    { 
     if (wga != null && wia != null) 
     { 
      throw new InvalidOperationException(); 
     } 
    } 
    private static string GetWebUriTemplate(OperationDescription od) 
    { 
     // return exactly what is on the attribute 
     WebGetAttribute wga = od.Behaviors.Find<WebGetAttribute>(); 
     WebInvokeAttribute wia = od.Behaviors.Find<WebInvokeAttribute>(); 
     EnsureOk(wga, wia, od); 
     if (wga != null) 
     { 
      return wga.UriTemplate; 
     } 
     else if (wia != null) 
     { 
      return wia.UriTemplate; 
     } 
     else 
     { 
      return null; 
     } 
    } 
    private static string GetWebMethod(OperationDescription od) 
    { 
     WebGetAttribute wga = od.Behaviors.Find<WebGetAttribute>(); 
     WebInvokeAttribute wia = od.Behaviors.Find<WebInvokeAttribute>(); 
     EnsureOk(wga, wia, od); 
     if (wga != null) 
     { 
      return "GET"; 
     } 
     else if (wia != null) 
     { 
      return wia.Method ?? "POST"; 
     } 
     else 
     { 
      return "POST"; 
     } 
    } 

} 

class NewtonsoftJsonBehavior : WebHttpBehavior 
{ 
    protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint) 
    { 
     return new UriTemplateDispatchFormatter(
      operationDescription, 
      new NewtonsoftJsonDispatchFormatter(operationDescription, endpoint, true), 
      GetQueryStringConverter(operationDescription), 
      endpoint.Contract.Name, 
      endpoint.Address.Uri); 
    } 

    protected override IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription od, ServiceEndpoint ep) 
    { 
     return new NewtonsoftJsonDispatchFormatter(od, ep, false); 
    } 

} 

+1

非常に実用的なアプローチを作品! @ carlosfigueiraのNewtonsoftJsonDispatchFormatter実装では、最初のボディーパートがペイロードボディーであると想定しています。本体部分が最初の引数ではないようにメソッドが構造化されている場合、 'JsonReaderException'が返されます。 – Tedford

+0

こんにちは、私は同じ問題を抱えていた、それはまだ最高のソリューションをご利用いただけますか?私はそれを試していますが、私は自分のソリューションでフレームワークからコードをコピーするファンではありませんが、ああ、よく... – Vinhent

+1

@Vinhentよりよい解決策は、RESTful WebサービスのためにWCF以外のものを使用することですあなたはWCFに固執する必要がありますはいです –

関連する問題