那个问题很棘手:让我们一步一步来
一些背景信息
Message
类在其ToString()
方法中编写其标头。然后,ToString()
调用内部重载ToString(XmlDictionaryWriter writer)
,然后开始编写:
internal void ToString(XmlDictionaryWriter writer)
{
if (this.IsDisposed)
{
throw TraceUtility.ThrowHelperError(this.CreateMessageDisposedException(), this);
}
if (this.Version.Envelope != EnvelopeVersion.None)
{
this.WriteStartEnvelope(writer);
this.WriteStartHeaders(writer);
MessageHeaders headers = this.Headers;
for (int i = 0; i < headers.Count; i++)
{
headers.WriteHeader(i, writer);
}
writer.WriteEndElement();
MessageDictionary arg_60_0 = XD.MessageDictionary;
this.WriteStartBody(writer);
}
this.BodyToString(writer);
if (this.Version.Envelope != EnvelopeVersion.None)
{
writer.WriteEndElement();
writer.WriteEndElement();
}
}
这段代码
this.WriteStartHeaders(writer);
会写入头标签,无论有多少个头标签。在 for 循环后,
writer.WriteEndElement()
与之匹配。这个
writer.WriteEndElement()
必须与被写入的头标签匹配,否则 XML 文档将无效。
因此,我们无法通过重写虚拟方法来摆脱头部信息:
WriteStartHeaders
调用虚拟方法
OnWriteStartHeaders
,但标记关闭防止了简单地关闭它。我们必须改变整个
ToString()
方法以删除任何与头相关的结构,以得到:
- write start of envelope
- write start of body
- write body
- write end of body
- write end of envelope
解决方案
在上述伪代码中,我们对一切都有控制,除了“写正文”部分。在最初的ToString(XmlDictionaryWriter writer)
中调用的所有方法都是公共的,除了BodyToString
。因此,我们需要通过反射或适合您需求的任何方法来调用它。不带头信息地编写消息变得非常简单:
private void ProcessMessage(Message msg, XmlDictionaryWriter writer)
{
msg.WriteStartEnvelope(writer);
msg.WriteStartBody(writer);
var bodyToStringMethod = msg.GetType()
.GetMethod("BodyToString", BindingFlags.Instance | BindingFlags.NonPublic);
bodyToStringMethod.Invoke(msg, new object[] {writer});
writer.WriteEndElement();
writer.WriteEndElement();
}
现在我们有一种方法可以获得没有标题的消息内容。但是,这个方法应该如何调用?
我们只想要字符串形式的没有标题的消息。
太好了,我们不需要关心覆盖 ToString() 方法然后调用消息初始编写的问题。只需在程序中创建一个方法,接受 Message 和 XmlDictionaryWriter,并调用它以获取没有标题的消息。
我们希望 ToString() 方法返回没有标题的消息。
这个比较复杂。我们不能轻易地继承 Message 类,因为我们需要从 System.ServiceModel 程序集中提取大量依赖项。在这个答案中,我不会深入讨论这个问题。
我们可以使用一些框架的能力来创建一个围绕现有对象的代理,并拦截一些对原始对象的调用,以替换/增强其行为:我习惯于使用 Castle Dynamic proxy,所以让我们使用它。
我们希望拦截 ToString() 方法,因此我们在使用的 Message 对象周围创建一个代理,并添加拦截器来替换 Message 的 ToString 方法为我们的实现:
var msg = Message.CreateMessage(MessageVersion.Soap11, "*");
msg.Headers.Clear();
var proxyGenerator = new Castle.DynamicProxy.ProxyGenerator();
var proxiedMessage = proxyGenerator.CreateClassProxyWithTarget(msg, new ProxyGenerationOptions(),
new ToStringInterceptor());
"
ToStringInterceptor
需要做的事情与初始的 ToString()
方法几乎相同,但我们将使用上述定义的 ProcessMessage 方法:
"
public class ToStringInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (invocation.Method.Name != "ToString")
{
invocation.Proceed();
}
else
{
var result = string.Empty;
var msg = invocation.InvocationTarget as Message;
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
XmlDictionaryWriter xmlDictionaryWriter =
XmlDictionaryWriter.CreateDictionaryWriter(new XmlTextWriter(stringWriter));
try
{
ProcessMessage(msg, xmlDictionaryWriter);
xmlDictionaryWriter.Flush();
result = stringWriter.ToString();
}
catch (XmlException ex)
{
result = "ErrorMessage";
}
invocation.ReturnValue = result;
}
}
private void ProcessMessage(Message msg, XmlDictionaryWriter writer)
{
}
}
在这里,我们调用消息的ToString()方法将返回一个没有头部的信封。我们可以将消息传递给框架的其他部分,并知道它应该大部分工作:对于消息的某些内部管道的直接调用仍然可以产生初始输出,但是除非完全重新实现,否则我们无法控制。
注意事项:
- 这是我找到的去除标题的最短方法。写入器中的标题序列化不仅仅是一个虚拟函数的问题。代码没有给你太多的余地。
- 此实现不使用与Message中ToString()的原始实现中使用的EncodingFallbackAwareXmlTextWriter相同的XmlWriter。这个类在System.ServiceModel中是内部的,将其拉出来留给读者作为练习。因此,由于xml未使用我使用的简单的XmlTextWriter进行格式化,输出略有不同。
- 拦截器可以简单地解析从初始ToString()调用返回的xml,并在让值冒泡之前删除标题节点。这是另一种可行的解决方案。
原始代码:
public class ToStringInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (invocation.Method.Name != "ToString")
{
invocation.Proceed();
}
else
{
var result = string.Empty;
var msg = invocation.InvocationTarget as Message;
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
XmlDictionaryWriter xmlDictionaryWriter =
XmlDictionaryWriter.CreateDictionaryWriter(new XmlTextWriter(stringWriter));
try
{
ProcessMessage(msg, xmlDictionaryWriter);
xmlDictionaryWriter.Flush();
result = stringWriter.ToString();
}
catch (XmlException ex)
{
result = "ErrorMessage";
}
invocation.ReturnValue = result;
}
}
private void ProcessMessage(Message msg, XmlDictionaryWriter writer)
{
msg.WriteStartEnvelope(writer);
msg.WriteStartBody(writer);
var bodyToStringMethod = msg.GetType()
.GetMethod("BodyToString", BindingFlags.Instance | BindingFlags.NonPublic);
bodyToStringMethod.Invoke(msg, new object[] { writer });
writer.WriteEndElement();
writer.WriteEndElement();
}
}
internal class Program
{
private static void Main(string[] args)
{
var msg = Message.CreateMessage(MessageVersion.Soap11, "*");
msg.Headers.Clear();
var proxyGenerator = new Castle.DynamicProxy.ProxyGenerator();
var proxiedMessage = proxyGenerator.CreateClassProxyWithTarget(msg, new ProxyGenerationOptions(),
new ToStringInterceptor());
var initialResult = msg.ToString();
var proxiedResult = proxiedMessage.ToString();
Console.WriteLine("Initial result");
Console.WriteLine(initialResult);
Console.WriteLine();
Console.WriteLine("Proxied result");
Console.WriteLine(proxiedResult);
Console.ReadLine();
}
}