使用自定义WCF车身反序列化不改变URI模板反序列化 [英] Using Custom WCF Body Deserialization without changing URI Template Deserialization
问题描述
这篇博客文章,我能够创建一个自定义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屋!