2015-11-05 7 views
7

Von this blog post konnte ich eine benutzerdefinierte WCF IDispatchMessageFormatter erstellen, die JSON.NET-Serialisierung verwendet. Es funktioniert gut mit einem Vorbehalt: die Verwendung mit UriTemplate funktioniert nicht unbedingt wie erwartet.Benutzerdefinierte WCF-Body-Deserialisierung ohne Änderung der URI-Template-Deserialisierung verwenden

Hier ist die Umsetzung durch die Blog-Post zur Verfügung gestellt:

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) { ... } 
} 

Grundsätzlich ist die object[] parameters in der DeserializeMethod Signatur sind out Parameter, die diese Methode instanziiert muss.

Also, tut dies eine große Aufgabe einen REST-Endpunkt wie folgt zu behandeln:

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

oder so:

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

aber derzeit Karte nicht die Vorlage URI-Parameter die Methodenparameter, z so etwas wie dieses:

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

Microsoft schreibt auf die GetRequestDispatchFormatter außer Kraft gesetzt:

Dies ist ein Erweiterungspunkt, die Verhaltensweisen abgeleitet verwenden können, ihre eigene Implementierung von IDispatchMessageFormatter zu liefern, die die Eingangsparameter deserialisieren genannt wird die Dienstoperation von der Anforderungsnachricht. Parameter, die in der UriTemplate der Dienstoperation angegeben sind, müssen aus dem To-URI der Anforderungsnachricht deserialisiert werden, und andere Parameter müssen aus dem Hauptteil der Anforderungsnachricht deserialisiert werden.

Also, großartig. Ich habe die Deserialisierung der Parameter aus dem Nachrichtentext aktualisiert. Aber ich möchte die Deserialisierung der Parameter in UriTemplate nicht außer Kraft setzen. Gibt es eine Möglichkeit, vorhandenen Code zu verwenden, um die eingehende URI-Anforderung den Parametern mit der Standardmethode UriTemplate zuzuordnen?

Es scheint, dass ich etwas wie die UriTemplateDispatchFormatter verwenden muss, aber ich bin nicht sicher, wie man das umsetzt, und es ist nicht öffentlich.

Antwort

4

Nun, das ist vielleicht das lächerlichste, was ich zu tun haben musste, aber den Quellcode für UriTemplateDispatchFormatter kopieren, können Sie einfach ein UriTemplateDispatchFormatter mit einem „inneren“ Rückkehr IDispatchFormatter, die ich hier zur Verfügung gestellt, um die IDispatchFormatter entsprechen. Nicht sicher, warum wurde diese Klasse intern gemacht> _>

die folgende Klassendefinition:

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"; 
     } 
    } 

} 

zusammen mit folgendem Verhalten:

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

} 

arbeitet

+1

sehr pragmatischen Ansatz! Beachten Sie, dass die Implementierung von NewtonsoftJsonDispatchFormatter von @ carlosfigeira erwartet, dass der erste Körperteil der Nutzlastkörper ist. Wenn die Methode so strukturiert ist, dass der Körperteil nicht das erste Argument ist, wird dies wahrscheinlich zu einer "JsonReaderException" führen. – Tedford

+0

Hallo, ich hatte das gleiche Problem, ist es immer noch die beste verfügbare Lösung?Ich versuche es, aber ich bin nicht Fan von Code kopieren aus dem Framework in meiner eigenen Lösung, aber na ja ... – Vinhent

+1

@Vinhent die bessere Lösung könnte etwas anderes als WCF für einen RESTful Web-Service verwenden, aber wenn Sie müssen bei WCF bleiben ja ist es –

Verwandte Themen