这个问题很棘手:让我们一步一步来
一些背景
该类Message
将其标头写入其ToString()
方法中。ToString()
然后调用内部重载ToString(XmlDictionaryWriter writer)
,然后开始写入:
// System.ServiceModel.Channels.Message
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);
数量如何,代码都会写入标头标签。它与writer.WriteEndElement()
for 循环之后的匹配。这writer.WriteEndElement()
必须与正在写入的 header 标签匹配,否则 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); // start of envelope
msg.WriteStartBody(writer); // start of body
var bodyToStringMethod = msg.GetType()
.GetMethod("BodyToString", BindingFlags.Instance | BindingFlags.NonPublic);
bodyToStringMethod.Invoke(msg, new object[] {writer}); // write body
writer.WriteEndElement(); // write end of body
writer.WriteEndElement(); // write end of envelope
}
现在我们有一种方法可以在没有标题的情况下获取我们的消息内容。但是应该如何调用这个方法呢?
我们只希望没有标题的消息作为字符串
太好了,我们不需要关心重写ToString()
然后调用消息的初始写入的方法。只需在您的程序中创建一个接受 aMessage
和 an的方法,XmlDictionaryWriter
然后调用它来获取没有标头的消息。
我们希望该ToString()
方法返回没有标头的消息
这个有点复杂。我们不能轻易地从Message
类继承,因为我们需要从System.ServiceModel 程序集中提取大量依赖项。我不会在这个答案中去那里。
我们可以做的是使用某些框架的功能围绕现有对象创建代理并拦截对原始对象的一些调用以替换/增强其行为:我习惯于Castle Dynamic 代理,所以让我们使用它。
我们想要拦截 ToString() 方法,所以我们围绕Message
我们正在使用的对象创建一个代理,并添加一个拦截器来用我们的实现替换该ToString
方法Message
:
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)
{
// same method as above
}
}
我们在这里:对消息的 ToString() 方法的调用现在将返回一个没有标题的信封。我们可以将消息传递给框架的其他部分,并且知道它应该大部分都可以工作:直接调用 Message 的一些内部管道仍然可以产生初始输出,但如果没有完全重新实现,我们无法控制它。
注意事项
- 这是删除我找到的标题的最短方法。编写器中的标头序列化没有仅在一个虚拟函数中处理这一事实是一个大问题。该代码没有给您太多回旋余地。
- 此实现与
XmlWriter
, 的原始实现中使用ToString()
的Message
不同EncodingFallbackAwareXmlTextWriter
。这个类在 System.ServiceModel 中是内部的,把它拉出来留给读者作为练习。结果,输出略有不同,因为 xml 没有XmlTextWriter
使用我使用的简单格式。
- 拦截器可以简单地解析从初始
ToString()
调用返回的 xml,并在让值冒泡之前删除 headers 节点。这是另一个可行的解决方案。
原始代码
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();
}
}