使用自定义WCF车身反序列化不改变URI模板反序列化 [英] Using Custom WCF Body Deserialization without changing URI Template Deserialization

查看:364
本文介绍了使用自定义WCF车身反序列化不改变URI模板反序列化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这篇博客文章,我能够创建一个自定义WCF IDispatchMessageFormatter 使用JSON.NET序列化。它有一点需要注意的伟大工程:使用它与 UriTemplate 如预期并不一定正常工作

From this blog post, I was able to create a custom WCF IDispatchMessageFormatter that uses JSON.NET serialization. It works great with one caveat: using it with UriTemplate doesn't necessarily work as expected.

下面是实施。通过博客文章提供的:

Here's the implementation provided by the blog post:

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 签名是退出,这种方法需要实例化的参数。

Basically, the object[] parameters in the DeserializeMethod signature are out parameters that this method needs to instantiate.

所以,这做了了不起的工作来处理REST端点是这样的:

So, this does a great job to handle a REST endpoint like this:

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

或像这样的:

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

但它目前并不映射URI模板参数的方法参数,如是这样的:

but it currently doesn't map the URI template parameters to the method parameters, e.g. something like this:

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

微软在重写的 GetRequestDispatchFormatter

Microsoft writes on the overriden GetRequestDispatchFormatter:

这是派生的行为可以用它来提供自己的实现IDispatchMessageFormatter的是被称为可扩展性点从请求消息反序列化服务操作的输入参数。在服务操作的UriTemplate指定参数必须从反序列化到请求消息的URI和其它参数必须从请求消息的主体进行反序列化

This is an extensibility point that derived behaviors can use to supply their own implementation of IDispatchMessageFormatter that is called to deserialize the input parameters of the service operation from the request message. Parameters specified in the UriTemplate of the service operation must be deserialized from the To URI of the request message and other parameters must be deserialized from the body of the request message.

所以,太棒了。我从一个邮件的正文更新参数的反序列化。但我不希望覆盖在 UriTemplate 反序列化的参数。有没有办法用现有的代码来映​​射传入URI请求的默认方式 UriTemplate 参数的处理?

So, great. I updated the deserialization of the parameters from the body of a message. But I don't want to override deserializing the parameters in the UriTemplate. Is there any way to use existing code to map the incoming URI request to the parameters with the default way UriTemplate is handled?

看来我需要使用类似 UriTemplateDispatchFormatter ,但我不知道如何实现这一点,这是不公开的。

It seems I need to use something like the UriTemplateDispatchFormatter but I'm not sure how to implement this, and it's non-public.

推荐答案

好了,这也许是最可笑的事情,我必须做的,但对于复制 UriTemplateDispatchFormatter ,你可以简单地返回 UriTemplateDispatchFormatter 与内 IDispatchFormatter 对应于 IDispatchFormatter 我在这里提供。不知道为什么这个类是做内部> _>

Well, this is maybe the most ridiculous thing I've had to do, but copying the source code for UriTemplateDispatchFormatter, you can simply return a UriTemplateDispatchFormatter with an "inner" IDispatchFormatter that corresponds to the IDispatchFormatter I provided here. Not sure why this class was made internal >_>

下面的类定义:

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

}



随着以下行为:

along with the following behavior:

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

}



工作

works

这篇关于使用自定义WCF车身反序列化不改变URI模板反序列化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆