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

从这篇博文中 ,我能够创建一个使用JSON.NET序列化的自定义WCF IDispatchMessageFormatter 。 它有一点需要注意:与UriTemplate一起使用它并不一定按预期工作。

这是博客文章提供的实现:

 class NewtonsoftJsonDispatchFormatter : IDispatchMessageFormatter { private readonly OperationDescription od; private readonly ServiceEndpoint ep; private readonly Dictionary parameterNames = new Dictionary(); 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(); 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参数是此方法需要实例化的参数。

因此,这样做可以很好地处理这样的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实现,该实现被调用以从请求消息反序列化服务操作的输入参数。 必须从请求消息的To URI反序列化服务操作的UriTemplate中指定的参数,并且必须从请求消息的主体反序列化其他参数。

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

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

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

以下类定义:

 class UriTemplateDispatchFormatter : IDispatchMessageFormatter { internal Dictionary pathMapping; internal Dictionary> 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 pathMapping, out Dictionary> queryMapping, out int totalNumUTVars, out UriTemplate uriTemplate, OperationDescription operationDescription, QueryStringConverter qsc, string contractName) { pathMapping = new Dictionary(); queryMapping = new Dictionary>(); string utString = GetUTStringOrDefault(operationDescription); uriTemplate = new UriTemplate(utString); List neededPathVars = new List(uriTemplate.PathSegmentVariableNames); List neededQueryVars = new List(uriTemplate.QueryValueVariableNames); Dictionary alreadyGotVars = new Dictionary(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 neededPathCopy = new List(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 neededQueryCopy = new List(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(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(); WebInvokeAttribute wia = od.Behaviors.Find(); 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(); WebInvokeAttribute wia = od.Behaviors.Find(); 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); } } 

作品