2015-11-05 14 views
7

Od this blog post udało mi się utworzyć niestandardową funkcję WCF IDispatchMessageFormatter, która korzysta z serializacji JSON.NET. Działa to świetnie z jednym zastrzeżeniem: używanie go z UriTemplate niekoniecznie działa zgodnie z oczekiwaniami.Używanie niestandardowej deserializacji ciał WCF bez zmiany deserializacji szablonu URI

Oto realizacja zapewnia blogu:

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

Zasadniczo object[] parameters w podpisie DeserializeMethodout parametry, że ta metoda musi instancji.

Tak, to robi wielką pracę do obsługi punktu końcowego REST tak:

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

lub takiego:

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

ale w tej chwili nie ma map parametrów szablonów do URI parametry metody, np coś takiego:

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

Microsoft pisze na nadpisane GetRequestDispatchFormatter:

Jest to punkt rozciągliwość że pochodzi zachowania można użyć do zasilania własnego realizację IDispatchMessageFormatter że jest powołany do deserializowania parametrów wejściowych operacja usługi z komunikatu żądania. Parametry określone w UriTemplate operacji usługi muszą zostać przekształcone do postaci szeregowej z identyfikatora To URI komunikatu żądania, a inne parametry muszą zostać przekształcone z postaci szeregowej z treści komunikatu żądania.

Świetne. Zaktualizowałem deserializację parametrów z treści wiadomości. Ale nie chcę przesłonić deserializacji parametrów w UriTemplate. Czy istnieje sposób użycia istniejącego kodu do odwzorowania przychodzącego żądania URI do parametrów przy użyciu domyślnego sposobu obsługi UriTemplate?

Wygląda na to, że muszę użyć czegoś takiego, jak UriTemplateDispatchFormatter, ale nie jestem pewien, jak to zaimplementować, i jest niepubliczne.

Odpowiedz

4

Cóż, to jest chyba najbardziej absurdalna rzecz jaką musiałem zrobić, ale skopiowanie kodu źródłowego UriTemplateDispatchFormatter, można po prostu wrócić do UriTemplateDispatchFormatter z „wewnętrzną” IDispatchFormatter że odpowiada IDispatchFormatter I dostarczonych tutaj. Nie wiem, dlaczego ta klasa została wykonana wewnętrzna> _>

następującą definicję klasy:

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

} 

wraz z następującym zachowaniem:

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

} 

działa

+1

bardzo pragmatyczne podejście! Należy pamiętać o tym, że implementacja NewtonsoftJsonDispatchFormatter @ carlosfigueira oczekuje, że pierwsza część ciała będzie bryłą ładunku. Jeśli metoda jest skonstruowana w taki sposób, że część ciała nie jest pierwszym argumentem, prawdopodobnie spowoduje to 'JsonReaderException'. – Tedford

+0

Witam, skończyłem z tym samym problemem, czy nadal jest to najlepsze dostępne rozwiązanie?Próbuję go wypróbować, ale nie jestem fanem kopiowania kodu z frameworka w moim własnym rozwiązaniu, ale no cóż ... – Vinhent

+1

@Jeżeli lepszym rozwiązaniem może być użycie czegoś innego niż WCF dla usługi internetowej RESTful, ale jeśli musisz trzymać się WCF, tak jest –

Powiązane problemy