通过这篇博客文章,我能够创建一个使用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
的源代码,你可以简单地返回一个带有"内部"IDispatchFormatter
的UriTemplateDispatchFormatter
,它对应于我在这里提供的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);
}
}
工作
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
);