从控制台应用程序中记录SOAP消息

8
我正在尝试记录由我开发的控制台应用程序和特定第三方远程SOAP Web服务之间的请求和响应(原始XML 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>

这种方法的问题在于它似乎只适用于Web应用程序,在控制台应用程序中实现它没有任何结果。
跟踪方法
跟踪方法基于 配置消息日志记录 指南,使用此方法可以为应用程序中的每个SOAP/WCF通信启用.NET跟踪,并将日志转储到某个地方(有关配置的更多信息,请参见 推荐的跟踪和消息日志记录设置)。
以下是跟踪配置示例:
<?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的内容可以在GitHub的Gist中找到。

这种方法的问题是它记录了应用程序中每个 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>

有了这些信息,无法重建请求/响应流,因为所有消息都混在一起。

将它们与本地XmlWriterTraceListener生成的*.svclog进行比较:

<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}" />标签建立了每个请求和响应之间的关系,允许开发人员重建整个会话。是否有办法实现我想做的事情?

请不要仅仅向我们提出问题并让我们为您解决。请展示给我们您自己尝试解决问题的方法,然后准确地展示结果,并告诉我们为什么您认为它没有起作用。请参阅“你都尝试了什么?”这篇优秀的文章,您真的需要阅读一下。 - John Saunders
@JohnSaunders 我详细说明了我之前的尝试,现在你感觉好些了吗? - Albireo
1个回答

20
如果该服务被注册为WCF Web服务(而不是老式的ASMX Web服务),则可以通过IClientMessageInspectorIEndpointBehavior完成操作。
首先,您需要创建一个从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的消息检查器文章中找到如何执行此操作的指南。


1
非常有帮助。谢谢! - Andrew Kelly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接