Это сложный вопрос: давайте рассмотрим его шаг за шагом.
Некоторый контекст
Класс 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)
, являются общедоступными, кроме 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()
, который затем вызывает начальную запись сообщения. Просто создайте в своей программе метод, который принимает Message
и XmlDictionaryWriter
, и вызовите его, чтобы получить сообщение без заголовков.
Мы хотим, чтобы метод ToString()
возвращал сообщение без заголовков
Это немного сложнее. Мы не можем легко наследоваться от класса Message
, потому что нам нужно будет извлечь множество зависимостей из сборки System.ServiceModel. Я не буду идти туда в этом ответе.
Что мы можем сделать, так это использовать возможности некоторых фреймворков для создания прокси вокруг существующего объекта и для перехвата некоторых вызовов исходного объекта, чтобы заменить/улучшить его поведение: я привык к динамическому прокси-серверу Castle так что воспользуемся.
Мы хотим перехватить метод 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
, который я использую.
- Перехватчик мог просто проанализировать xml, возвращенный из исходного вызова
ToString()
, и удалить узел заголовков, прежде чем позволить всплыть значению. Это еще одно жизнеспособное решение.
Необработанный код
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();
}
}
person
samy
schedule
22.10.2014