Сообщение WCF: как удалить элемент заголовка SOAP?

Я пытаюсь удалить весь заголовок SOAP из сообщения WCF, просто хочу оставить тело конверта. Кто-нибудь может дать мне представление, как это сделать?

Создайте сообщение WCF следующим образом:

**string response = "Hello World!";
Message msg = Message.CreateMessage(MessageVersion.Soap11, "*", new TextBodyWriter(response));
msg.Headers.Clear();**

Отправляющее SOAP-сообщение будет:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header />
  <s:Body>
    <Binary>Hello World!</Binary>
  </s:Body>
</s:Envelope>

Но мне не нужен элемент заголовка SOAP, который мне нужен только в теле конверта. Как удалить элемент заголовка из сообщения WCF?


person user1483352    schedule 26.06.2012    source источник
comment
Можете ли вы просто создать сообщение SOAP самостоятельно, не используя Message.CreateMessage(), используя StringBuilder или XmlSerializer? Таким образом, вы можете построить строку так, как хотите, и отправить ее с помощью WebClient.   -  person Jon    schedule 26.06.2012
comment
Почему вы хотите избавиться от заголовка?   -  person John Saunders    schedule 27.06.2012
comment
Привет, Мангист, Сообщение WCF является абстрактным классом, оно не может быть экземпляром. Единственный способ создать - вызвать функцию CreateMessage.   -  person user1483352    schedule 27.06.2012
comment
Привет, Джон. Сообщение SOAP, содержащее заголовок SOAP, имеет больше смысла. но для совместимости с некоторыми другими третьими проектами. 3-я система, которая является старой системой.   -  person user1483352    schedule 27.06.2012


Ответы (4)


Вариант 1: используйте bacicHttpBinding, он не будет добавлять контент в заголовок (если он не настроен для безопасности)

Вариант 2: реализовать собственный кодировщик сообщений и удалить там заголовок. в любом месте до этого есть шанс, что wcf добавит заголовок обратно. См. пример кодировщика здесь.

person Yaron Naveh    schedule 27.06.2012

Это сложный вопрос: давайте рассмотрим его шаг за шагом.

Некоторый контекст

Класс 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

У меня не было вашего XmlBodyWriter, но вы могли бы использовать сериализатор контракта данных или средство записи тела xml. Но хитрость заключается в использовании msg.WriteBody. это пропустит заголовки

var response = "Hello";            
Message msg = Message.CreateMessage(MessageVersion.Soap11, "*",response, new DataContractSerializer(response.GetType()));
msg.Headers.Clear();
var sb = new StringBuilder();
var xmlWriter = new XmlTextWriter(new StringWriter(sb));
msg.WriteBody(xmlWriter);
person Gurpreet    schedule 21.10.2014

Должно быть примерно так:

XmlDocument xml = new XmlDocument();
xml.LoadXml(myXmlString); // suppose that myXmlString contains "<Body>...</Body>"

XmlNodeList xnList = xml.SelectNodes("/Envelope/Body");
foreach (XmlNode xn in xnList)
{
  string binary1 = xn["Binary1"].InnerText;
  string binary2 = xn["Binary2"].InnerText;
  Console.WriteLine("Binary: {0} {1}", binary1 , binary2);
}
person Erdinc Ay    schedule 22.10.2014