その質問はトリッキーなものです: 一歩一歩考えてみましょう
いくつかのコンテキスト
クラスは、メソッド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()
必要があります。そうでない場合、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)
を除いて publicBodyToString
です。したがって、リフレクションまたはニーズに合った方法で呼び出す必要があります。ヘッダーなしでメッセージを書くと、単純に次のようになります。
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()
メッセージの最初の書き込みを呼び出すメソッドをオーバーライドする必要はありません。プログラムで と を受け取るメソッドを作成し、Message
それXmlDictionaryWriter
を呼び出してヘッダーなしでメッセージを取得するだけです。
ToString()
メソッドがヘッダーなしでメッセージを返すようにしたい
これはもう少し複雑です。System.ServiceModel アセンブリから多くMessage
の依存関係を引き出す必要があるため、クラスから簡単に継承することはできません。この回答ではそこには行きません。
私たちができることは、いくつかのフレームワークの機能を使用して、既存のオブジェクトの周りにプロキシを作成し、元のオブジェクトへの呼び出しをインターセプトして、その動作を置き換え/強化することです。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() メソッドを呼び出すと、ヘッダーのないエンベロープが返されるようになりました。メッセージをフレームワークの他の部分に渡すことができ、それがほとんど機能するはずであることがわかります。メッセージの内部配管の一部への直接呼び出しは、初期出力を生成できますが、完全な再実装がなければ、それを制御できません。
注意点
- これは、私が見つけたヘッダーを削除する最短の方法です。ライターでのヘッダーのシリアル化が 1 つの仮想関数だけで処理されていないことが大きな問題でした。このコードでは、あまり動き回る余地がありません。
- この実装は、
XmlWriter
の元の実装で使用されたものと同じものを使用しません。このクラスは System.ServiceModel の内部にあり、それを引き出すことは読者への演習として残されています。その結果、xml は単純なI 使用でフォーマットされていないため、出力はわずかに異なります。ToString()
Message
EncodingFallbackAwareXmlTextWriter
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();
}
}