Журнал SOAP-сообщений из консольного приложения

Я пытаюсь записать запросы и ответы (необработанный конверт XML SOAP) между консольным приложением, разработанным мной, и конкретным сторонним удаленным веб-сервисом SOAP в базу данных для целей аудита, и я не могу найти способ сделать это,

В идеале я хотел бы получить запрос

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
   <soapenv:Header/>
   <soapenv:Body>
      <tem:SayHello>
         <tem:name>Albireo</tem:name>
      </tem:SayHello>
   </soapenv:Body>
</soapenv:Envelope>

и ответ

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <SayHelloResponse xmlns="http://tempuri.org/">
         <SayHelloResult>Hello, Albireo.</SayHelloResult>
      </SayHelloResponse>
   </s:Body>
</s:Envelope>

и сохранить их в базе данных.

Пока что каждый учебник в сети, который я нашел, сводится к двум подходам: метод SoapExtension и метод трассировки.

Метод SoapExtension

Метод SoapExtension основан на руководстве " Модификация сообщений SOAP с использованием расширений SOAP". В этом методе вы создаете класс, унаследованный от SoapExtension, и подключаете его в конфигурации приложения, а метод класса ProcessMessage позволит вам перехватывать сообщения SOAP.

Это пример класса, унаследованного от SoapExtension:

namespace Playground.Client
{
    using System;
    using System.Web.Services.Protocols;

    public class SoapLogger : SoapExtension
    {
        public override object GetInitializer(System.Type serviceType)
        {
            throw new NotImplementedException();
        }

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            throw new NotImplementedException();
        }

        public override void Initialize(object initializer)
        {
            throw new NotImplementedException();
        }

        public override void ProcessMessage(SoapMessage message)
        {
            throw new NotImplementedException();
        }
    }
}

И вот как это подключено в конфигурации:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0"
                          sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IGreeterService" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:8080/greeter"
                      binding="basicHttpBinding"
                      bindingConfiguration="BasicHttpBinding_IGreeterService"
                      contract="Services.IGreeterService"
                      name="BasicHttpBinding_IGreeterService" />
        </client>
    </system.serviceModel>
    <system.web>
        <webServices>
            <soapExtensionTypes>
                <add group="0"
                     priority="1"
                     type="Playground.Client.SoapLogger" />
            </soapExtensionTypes>
        </webServices>
    </system.web>
</configuration>

Проблема этого метода в том, что он работает только для веб-приложений, и попытка реализовать его в консольном приложении не дает результата.

Метод отслеживания

Метод трассировки основан на руководстве по настройке ведения журнала сообщений. В этом методе вы включаете трассировку.NET для каждого соединения SOAP/WCF в приложении и куда-то записываете журнал (более подробную информацию о конфигурации можно найти в разделе "Рекомендуемые настройки для трассировки и сообщений"). Ведение журнала).

Это пример конфигурации трассировки:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0"
                          sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.diagnostics>
        <sources>
            <source name="System.ServiceModel"
                    propagateActivity="true"
                    switchValue="Verbose, ActivityTracing">
                <listeners>
                    <add initializeData="ServiceModel.svclog"
                         name="ServiceModel"
                         type="System.Diagnostics.XmlWriterTraceListener" />
                </listeners>
            </source>
            <source name="System.ServiceModel.MessageLogging">
                <listeners>
                    <add initializeData="MessageLogging.svclog"
                         name="MessageLogging"
                         type="System.Diagnostics.XmlWriterTraceListener" />
                </listeners>
            </source>
        </sources>
    </system.diagnostics>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IGreeterService" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:8080/greeter"
                      binding="basicHttpBinding"
                      bindingConfiguration="BasicHttpBinding_IGreeterService"
                      contract="Services.IGreeterService"
                      name="BasicHttpBinding_IGreeterService" />
        </client>
        <diagnostics>
            <endToEndTracing activityTracing="true"
                             messageFlowTracing="true"
                             propagateActivity="true" />
            <messageLogging logEntireMessage="true"
                            logKnownPii="true"
                            logMalformedMessages="true"
                            logMessagesAtServiceLevel="true"
                            logMessagesAtTransportLevel="true" />
        </diagnostics>
    </system.serviceModel>
</configuration>

Содержимое ServiceModel.svclog и MessageLogging.svclog можно найти в Gist GubHub, поскольку оно слишком велико, чтобы помещаться здесь.

Проблема с этим методом заключается в том, что он регистрирует каждое сообщение SOAP/WCF в приложении, и кажется, что сгенерированные журналы не очень полезны, они содержат множество информации, и я не могу понять, фильтрует ли и только то, что мне интересно, кажется, что единственным практическим способом их прочтения является Microsoft Service Trace Viewer.

Я попытался добавить собственный TraceListener тоже:

namespace Playground.Client
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Text;
    using System.Xml;
    using System.Xml.Linq;

    public class CustomTraceListener : TraceListener
    {
        public override void Write(string message)
        {
            File.AppendAllLines("CustomTraceListener.txt", new[] { message });
        }

        public override void WriteLine(string message)
        {
            message = this.FormatXml(message);

            File.AppendAllLines("CustomTraceListener.txt", new[] { message });
        }

        private string FormatXml(string message)
        {
            using (var stringWriter = new StringWriter())
            {
                var xmlWriterSettings = new XmlWriterSettings();
                xmlWriterSettings.Encoding = Encoding.UTF8;
                xmlWriterSettings.Indent = true;
                xmlWriterSettings.OmitXmlDeclaration = true;

                using (var xmlTextWriter = XmlWriter.Create(stringWriter, xmlWriterSettings))
                {
                    XDocument.Parse(message).Save(xmlTextWriter);
                }

                return Convert.ToString(stringWriter);
            }
        }
    }
}

Но хотя он позволяет мне перехватывать сообщения, он не сохраняет метаданные:

<MessageLogTraceRecord Time="2013-07-16T10:50:04.5396082+02:00" Source="ServiceLevelSendRequest" Type="System.ServiceModel.Channels.BodyWriterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
  <HttpRequest>
    <Method>POST</Method>
    <QueryString></QueryString>
    <WebHeaders>
      <VsDebuggerCausalityData>uIDPo4bOsuSXlSVEkmfof4AP2psAAAAAlEIoNto3KEWKgCnIGryjp9f3wbRlp+ROhY9Oy6bed/cACQAA</VsDebuggerCausalityData>
    </WebHeaders>
  </HttpRequest>
  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
      <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IGreeterService/SayHello</Action>
      <ActivityId CorrelationId="964a7c4f-3b18-4b5d-8085-e00ae03b58d1" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">80101cc1-dfb5-4c8e-8d19-ec848ab69100</ActivityId>
    </s:Header>
    <s:Body>
      <SayHello xmlns="http://tempuri.org/">
        <name>Albireo</name>
      </SayHello>
    </s:Body>
  </s:Envelope>
</MessageLogTraceRecord>
<MessageLogTraceRecord Time="2013-07-16T10:50:04.6176897+02:00" Source="TransportSend" Type="System.ServiceModel.Channels.BodyWriterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
  <Addressing>
    <Action>http://tempuri.org/IGreeterService/SayHello</Action>
    <To>http://localhost:8080/greeter</To>
  </Addressing>
  <HttpRequest>
    <Method>POST</Method>
    <QueryString></QueryString>
    <WebHeaders>
      <VsDebuggerCausalityData>uIDPo4bOsuSXlSVEkmfof4AP2psAAAAAlEIoNto3KEWKgCnIGryjp9f3wbRlp+ROhY9Oy6bed/cACQAA</VsDebuggerCausalityData>
    </WebHeaders>
  </HttpRequest>
  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
      <ActivityId CorrelationId="964a7c4f-3b18-4b5d-8085-e00ae03b58d1" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">80101cc1-dfb5-4c8e-8d19-ec848ab69100</ActivityId>
    </s:Header>
    <s:Body>
      <SayHello xmlns="http://tempuri.org/">
        <name>Albireo</name>
      </SayHello>
    </s:Body>
  </s:Envelope>
</MessageLogTraceRecord>

С этой информацией невозможно перестроить поток запросов / ответов, так как все сообщения смешаны вместе.

Сравните их с *.svclog, сгенерированным собственным XmlWriterTraceListener:

<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
    <System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
        <EventID>0</EventID>
        <Type>3</Type>
        <SubType Name="Information">0</SubType>
        <Level>8</Level>
        <TimeCreated SystemTime="2013-07-16T08:50:04.6176897Z" />
        <Source Name="System.ServiceModel.MessageLogging" />
        <Correlation ActivityID="{80101cc1-dfb5-4c8e-8d19-ec848ab69100}" />
        <Execution ProcessName="Playground.Client" ProcessID="4348" ThreadID="1" />
        <Channel />
        <Computer>ESP-DEV-9</Computer>
    </System>
    <ApplicationData>
        <TraceData>
            <DataItem>
                <MessageLogTraceRecord Time="2013-07-16T10:50:04.6176897+02:00" Source="TransportSend" Type="System.ServiceModel.Channels.BodyWriterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
                    <Addressing>
                        <Action>http://tempuri.org/IGreeterService/SayHello</Action>
                        <To>http://localhost:8080/greeter</To>
                    </Addressing>
                    <HttpRequest>
                        <Method>POST</Method>
                        <QueryString></QueryString>
                        <WebHeaders>
                            <VsDebuggerCausalityData>uIDPo4bOsuSXlSVEkmfof4AP2psAAAAAlEIoNto3KEWKgCnIGryjp9f3wbRlp+ROhY9Oy6bed/cACQAA</VsDebuggerCausalityData>
                        </WebHeaders>
                    </HttpRequest>
                    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
                        <s:Header>
                            <ActivityId CorrelationId="964a7c4f-3b18-4b5d-8085-e00ae03b58d1" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">80101cc1-dfb5-4c8e-8d19-ec848ab69100</ActivityId>
                        </s:Header>
                        <s:Body>
                            <SayHello xmlns="http://tempuri.org/">
                                <name>Albireo</name>
                            </SayHello>
                        </s:Body>
                    </s:Envelope>
                </MessageLogTraceRecord>
            </DataItem>
        </TraceData>
    </ApplicationData>
</E2ETraceEvent>
<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
    <System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
        <EventID>0</EventID>
        <Type>3</Type>
        <SubType Name="Information">0</SubType>
        <Level>8</Level>
        <TimeCreated SystemTime="2013-07-16T08:50:04.6957712Z" />
        <Source Name="System.ServiceModel.MessageLogging" />
        <Correlation ActivityID="{80101cc1-dfb5-4c8e-8d19-ec848ab69100}" />
        <Execution ProcessName="Playground.Client" ProcessID="4348" ThreadID="1" />
        <Channel />
        <Computer>ESP-DEV-9</Computer>
    </System>
    <ApplicationData>
        <TraceData>
            <DataItem>
                <MessageLogTraceRecord Time="2013-07-16T10:50:04.6801549+02:00" Source="TransportReceive" Type="System.ServiceModel.Channels.BufferedMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
                    <HttpResponse>
                        <StatusCode>OK</StatusCode>
                        <StatusDescription>OK</StatusDescription>
                        <WebHeaders>
                            <Content-Length>207</Content-Length>
                            <Content-Type>text/xml; charset=utf-8</Content-Type>
                            <Date>Tue, 16 Jul 2013 08:50:04 GMT</Date>
                            <Server>Microsoft-HTTPAPI/2.0</Server>
                        </WebHeaders>
                    </HttpResponse>
                    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
                        <s:Header></s:Header>
                        <s:Body>
                            <SayHelloResponse xmlns="http://tempuri.org/">
                                <SayHelloResult>Hello, Albireo.</SayHelloResult>
                            </SayHelloResponse>
                        </s:Body>
                    </s:Envelope>
                </MessageLogTraceRecord>
            </DataItem>
        </TraceData>
    </ApplicationData>
</E2ETraceEvent>

Здесь <Correlation ActivityID="{80101cc1-dfb5-4c8e-8d19-ec848ab69100}" /> тег устанавливает связь между каждым запросом и ответом, позволяя разработчику перестроить весь сеанс.

Есть ли способ выполнить то, что я пытаюсь сделать?

1 ответ

Решение

Если сервис зарегистрирован как веб-сервис WCF (не как веб-сервис старой школы ASMX), это можно сделать через IClientMessageInspector а также IEndpointBehavior,

Сначала вы должны создать класс, наследующий от IClientMessageInspector это будет обрабатывать регистрацию как запросов, так и ответов.

namespace Playground.Sandbox
{
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Dispatcher;

    public class MyClientMessageInspector : IClientMessageInspector
    {
        public object BeforeSendRequest(
            ref Message request,
            IClientChannel channel)
        {
            // TODO: log the request.

            // If you return something here, it will be available in the 
            // correlationState parameter when AfterReceiveReply is called.
            return null;
        }

        public void AfterReceiveReply(
            ref Message reply,
            object correlationState)
        {
            // TODO: log the reply.

            // If you returned something in BeforeSendRequest
            // it will be available in the correlationState parameter.
        }
    }
}

Затем вы должны создать класс, наследующий от IEndpointBehavior это зарегистрирует инспектора в клиенте.

namespace Playground.Sandbox
{
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;

    public class MyEndpointBehavior : IEndpointBehavior
    {
        public void Validate(
            ServiceEndpoint endpoint)
        {
        }

        public void AddBindingParameters(
            ServiceEndpoint endpoint,
            BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(
            ServiceEndpoint endpoint,
            EndpointDispatcher endpointDispatcher)
        {
        }

        public void ApplyClientBehavior(
            ServiceEndpoint endpoint,
            ClientRuntime clientRuntime)
        {
            var myClientMessageInspector = new MyClientMessageInspector();

            clientRuntime.ClientMessageInspectors.Add(myClientMessageInspector);
        }
    }
}

Затем, когда вы хотите использовать поведение, вы можете вручную зарегистрировать его перед использованием сервиса.

namespace Playground.Sandbox
{
    public static class Program
    {
        public static void Main()
        {
            using (var client = new MyWcfClient())
            {
                var myEndpointBehavior = new MyEndpointBehavior();

                client.Endpoint.Behaviors.Add(myEndpointBehavior);

                // TODO: your things with the client.
            }
        }
    }
}

Если вы не хотите регистрировать поведение вручную или хотите, чтобы оно было всегда активным, вы можете зарегистрировать его в файле конфигурации.

Сначала вам нужно создать класс, наследующий от BehaviorExtensionElement этот класс сообщит.NET Framework, какое поведение будет применено, и создаст экземпляр при необходимости.

namespace Playground.Sandbox
{
    using System;
    using System.ServiceModel.Configuration;

    public class MyBehaviorExtensionElement : BehaviorExtensionElement
    {
        protected override object CreateBehavior()
        {
            var myEndpointBehavior = new MyEndpointBehavior();

            return myEndpointBehavior;
        }

        public override Type BehaviorType
        {
            get
            {
                return typeof(MyEndpointBehavior);
            }
        }
    }
}

Тогда вам нужно зарегистрировать BehaviorExtensionElement в файле конфигурации (показана только соответствующая часть файла конфигурации).

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime sku=".NETFramework,Version=v4.5"
                          version="v4.0" />
    </startup>
    <system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name="withMyBehaviorExtensionElement">
                    <myBehaviorExtensionElement />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <client>
            <endpoint address="..."
                      behaviorConfiguration="withMyBehaviorExtensionElement"
                      binding="..."
                      bindingConfiguration="..."
                      contract="..."
                      name="..." />
        </client>
        <extensions>
            <behaviorExtensions>
                <add name="myBehaviorExtensionElement"
                     type="Playground.Sandbox.MyBehaviorExtensionElement, Playground.Sandbox" />
            </behaviorExtensions>
        </extensions>
    </system.serviceModel>
</configuration>

Теперь вы можете использовать сервис без ручной регистрации поведения каждый раз:

namespace Playground.Sandbox
{
    public static class Program
    {
        public static void Main()
        {
            using (var client = new MyWcfService())
            {
                // TODO: your things with the client.
            }
        }
    }
}

Вы можете найти руководство о том, как это сделать, в статье MSDN Message Inspectors.

Другие вопросы по тегам