一尘不染

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

json

这篇博客文章中,我能够创建一个IDispatchMessageFormatter使用JSON.NET序列化的自定义WCF
。需要注意的是,它的效果很好:与一起使用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) { ... }
}

基本上,object[] parametersDeserializeMethod签名是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) { ... }

微软写在重写GetRequestDispatchFormatter

这是一个扩展点,派生的行为可以用来提供自己的IDispatchMessageFormatter实现,该实现被调用以从请求消息中反序列化服务操作的输入参数。必须从请求消息的To
URI中反序列化服务操作的UriTemplate中指定的参数,并且必须从请求消息的主体中反序列化其他参数。

很好。我更新了消息正文中参数的反序列化。但是我不想重写。中的反序列化参数UriTemplate。是否可以使用现有代码以默认方式UriTemplate处理传入的URI请求到参数的方法?

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


阅读 185

收藏
2020-07-27

共1个答案

一尘不染

好吧,这也许是我要做的最荒谬的事情,但是复制的源代码UriTemplateDispatchFormatter,您可以简单地返回一个UriTemplateDispatchFormatter带有“
inner” IDispatchFormatter(与IDispatchFormatter我在此处提供的代码相对应)的a
。不确定为什么将此类设为内部> _>

下面的类定义:

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

}

作品

2020-07-27