使用自定义WCF正文反序列化而不更改URI模板反序列化



通过这篇博客文章,我能够创建一个使用JSON.NET序列化的自定义WCF IDispatchMessageFormatter。它工作得很好,但有一点需要注意:将它与UriTemplate一起使用并不一定能像预期的那样工作。

以下是博客文章提供的实现:

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参数。

因此,这在处理这样的REST端点方面做得很好:

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

或者像这样:

[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实现,IDispatchMessage Formatter被调用来从请求消息中反序列化服务操作的输入参数。必须从请求消息的To URI反序列化服务操作的UriTemplate中指定的参数,并且必须从请求邮件的正文反序列化其他参数。

太好了。我更新了消息正文中参数的反序列化。但我不想覆盖对UriTemplate中的参数进行反序列化。是否有任何方法可以使用现有代码将传入的URI请求映射到具有默认UriTemplate处理方式的参数?

我似乎需要使用类似UriTemplateDispatchFormatter的东西,但我不确定如何实现它,而且它是非公共的。

好吧,这可能是我不得不做的最荒谬的事情,但复制UriTemplateDispatchFormatter的源代码,你可以简单地返回一个带有"内部"IDispatchFormatterUriTemplateDispatchFormatter,它对应于我在这里提供的IDispatchFormatter。不确定为什么这个类是内部的>_>

以下类别定义:

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

工作

约瑟夫·尼尔兹的答案是完美的;我只是想分享一种稍微不同的方法,使用反射来获得UriTemplateDispatchFormatter的实例,而不必复制其代码:
protected override IDispatchMessageFormatter GetRequestDispatchFormatter(
    OperationDescription operationDescription, ServiceEndpoint endpoint) =>
    (IDispatchMessageFormatter)Activator.CreateInstance(
        Type.GetType("System.ServiceModel.Dispatcher.UriTemplateDispatchFormatter, " +
            "System.ServiceModel.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"),
        operationDescription,
        new NewtonsoftJsonDispatchFormatter(operationDescription, endpoint, true),
        GetQueryStringConverter(operationDescription),
        endpoint.Contract.Name,
        endpoint.Address.Uri
    );

相关内容

  • 没有找到相关文章

最新更新