2015-11-05 19 views
7

Da this blog post, sono stato in grado di creare una WCF personalizzata IDispatchMessageFormatter che utilizza la serializzazione JSON.NET. Funziona alla grande con un avvertimento: utilizzarlo con UriTemplate non funziona necessariamente come previsto.Utilizzo della deserializzazione del corpo WCF personalizzata senza modificare la deserializzazione del modello URI

Ecco l'implementazione fornita dal post del blog:

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

In sostanza, il object[] parameters nella firma DeserializeMethod sono out parametri che questo metodo ha bisogno di istanziare.

Quindi, questo fa un grande lavoro per gestire un endpoint REST come questo:

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

o come questo:

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

ma attualmente non traccia i parametri di modello URI per la parametri del metodo, ad es qualcosa di simile:

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

Microsoft scrive sul sovrascritto GetRequestDispatchFormatter:

Questo è un punto di estensibilità che derivano comportamenti possono utilizzare per fornire la propria implementazione di IDispatchMessageFormatter che è chiamata a deserializzare i parametri di ingresso l'operazione di servizio dal messaggio di richiesta. I parametri specificati nel UriTemplate dell'operazione di servizio devono essere deserializzati dall'URL del messaggio di richiesta e gli altri parametri devono essere deserializzati dal corpo del messaggio di richiesta.

Quindi, fantastico. Ho aggiornato la deserializzazione dei parametri dal corpo di un messaggio. Ma non voglio scavalcare la deserializzazione dei parametri nel UriTemplate. C'è un modo per usare il codice esistente per mappare la richiesta URI in entrata ai parametri con il modo predefinito UriTemplate viene gestito?

Sembra che ho bisogno di utilizzare qualcosa come il UriTemplateDispatchFormatter ma non sono sicuro di come implementarlo, ed è non pubblico.

risposta

4

Bene, questa è forse la cosa più ridicola che abbia avuto a che fare, ma copiare il codice sorgente per UriTemplateDispatchFormatter, si può semplicemente restituire un UriTemplateDispatchFormatter con una "interna" IDispatchFormatter che corrisponde alla IDispatchFormatter ho fornito qui. Non certo perché questa classe è stata fatta interno> _>

la seguente definizione di classe:

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

} 

insieme con il seguente comportamento:

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

} 

funziona

+1

approccio molto pragmatico! Una cosa da tenere a mente è che l'implementazione di @ carlosfigueira NewtonsoftJsonDispatchFormatter si aspetta che la prima parte del corpo sia il corpo del payload. Se il metodo è strutturato in modo tale che la parte del corpo non sia il primo argomento, risulterà probabilmente in un 'JsonReaderException'. – Tedford

+0

Ciao, ho finito per avere lo stesso problema, è ancora la migliore soluzione disponibile?Ci sto provando ma non sono un fan della copia del codice dal framework nella mia soluzione, ma oh ... – Vinhent

+1

@Vinhent la soluzione migliore potrebbe essere quella di utilizzare qualcosa di diverso da WCF per un servizio web RESTful, ma se è necessario attenersi alla WCF sì, lo è –