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}" />标签建立了每个请求和响应之间的关系,允许开发人员重建整个会话。

有没有办法完成我想做的事情?

4

1 回答 1

19

如果该服务注册为 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 的Message Inspectors文章中找到有关如何执行此操作的指南。

于 2013-09-09T13:15:08.700 回答