从控制台应用程序记录SOAP消息 [英] Log SOAP messages from a console application

查看:59
本文介绍了从控制台应用程序记录SOAP消息的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图将我开发的控制台应用程序和特定的第三方远程SOAP Web服务之间的请求和响应(原始XML SOAP信封)记录到数据库中以进行审核,但我找不到方法去做.

I'm trying to log the requests and responses (the raw XML SOAP envelope) between a console application developed by me and a specific third party remote SOAP web service to database for audit purposes, and I can't find a way to do it.

理想情况下,我想做的就是获取请求

Ideally what I'd like to do is getting the request

<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>

并将它们保存在数据库中.

and save them in the database.

到目前为止,我在网上找到的每个教程都可以归结为两种方法,即SoapExtension方法和跟踪方法.

So far every tutorial on the net I found boils down to two approaches, the SoapExtension method and the tracing method.

SoapExtension方法基于使用SOAP扩展对SOAP消息进行修改指南,在此方法中,您将创建一个从SoapExtension继承的类并将其挂接到应用程序的配置中,该类的ProcessMessage方法将允许您截取SOAP消息.

The SoapExtension method is based on the SOAP Message Modification Using SOAP Extensions guide, in this method you create a class inheriting from SoapExtension and hook it in the application's configuration, the class' ProcessMessage method will allow you to intercept the SOAP messages.

这是从SoapExtension继承的类的示例:

This is an example of the class inherited from 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();
        }
    }
}

这是它在配置中的接线方式:

And this is how it is wired in the configuration:

<?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应用程序,尝试在控制台应用程序中实现它不会产生任何结果.

The problem with this method is it seems to work only for web applications, trying to implement it in a console application yield no result.

跟踪方法基于配置消息记录指南,通过这种方法,您可以为应用程序中的每个SOAP/WCF通信启用.NET跟踪,并将日志转储到某处(有关配置的更多信息,请参见

The tracing method is based upon the Configuring Message Logging guide, in this method you enable .NET's tracing for every SOAP/WCF communication in the application and dump the log somewhere (more information on the configuration can be found in Recommended Settings for Tracing and Message Logging).

这是跟踪配置的示例:

<?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>

在GitHub的Gist中 中可以找到ServiceModel.svclog和MessageLogging.svclog的内容.它太大了,无法容纳在这里.

The content of ServiceModel.svclog and MessageLogging.svclog can be found in a GitHub's Gist as it's too big to fit here.

此方法的问题是它在应用程序中记录了个SOAP/WCF消息,并且似乎生成的日志并不是真正有用的,它们包含大量信息,我无法理解是否以及如何仅过滤我感兴趣的内容,读取它们的唯一实用方法似乎是Microsoft的

The problem with this method is it logs every SOAP/WCF message in the application and it seems the generated logs are not really useful, they contains loads of informations and I can't understand if and how filter only what I'm interested in, the only practical way to read them seems to be Microsoft's Service Trace Viewer.

我也尝试添加自定义TraceListener:

I've tried to add a custom TraceListener too:

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);
            }
        }
    }
}

但是,即使它允许我拦截消息,也不会保存任何元数据:

But even though it allows me to intercept the messages, it doesn't save any metadata:

<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>

有了这些信息,就不可能重建请求/响应流,因为所有消息都混合在一起了.

With this information it's impossible to rebuild the request/response flow as all the messages are mixed together.

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

Compare them to the *.svclog generated by the native 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}" />标签在这里建立了每个请求和响应之间的关系,允许开发人员重建整个会话.

Here the <Correlation ActivityID="{80101cc1-dfb5-4c8e-8d19-ec848ab69100}" /> tag establishes a relation between each request and response, allowing a developer to rebuild the whole session.

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

Is there a way to accomplish what I'm trying to do?

推荐答案

如果该服务已注册为WCF网络服务(而不是旧式ASMX网络服务),则可以通过IClientMessageInspector进行操作,并且IEndpointBehavior.

If the service is registered as a WCF web-service (not as an old-school ASMX web-service) it's possible to do it through IClientMessageInspector and IEndpointBehavior.

首先,您必须创建一个继承自 IClientMessageInspector 将同时处理请求和答复的日志记录.

First you have to create a class inheriting from IClientMessageInspector that will handle the logging of both the requests and the replies.

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 将在客户中注册检查员.

Then you have to create a class inheriting from IEndpointBehavior that will register the inspector in the client.

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);
        }
    }
}

然后,当您要使用该行为时,可以在使用服务之前手动注册它.

Then when you want to use the behavior you can manually register it before using the service.

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.
            }
        }
    }
}

如果您不想手动注册行为或需要使其始终处于活动状态,则可以在配置文件中注册它.

If you don't want to register the behavior manually or you need it to be always active, you can register it in the configuration file.

首先,您需要创建一个继承自 BehaviorExtensionElement ,该类将告诉.NET Framework将应用哪种行为,并在需要时创建实例.

First you need to create a class inheriting from BehaviorExtensionElement, this class will tell the .NET Framework which behavior will be applied and will create the instance when needed.

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(仅显示配置文件的相关部分).

Then you need to register the BehaviorExtensionElement in the configuration file (only the relevant part of the configuration file are shown).

<?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>

现在,您可以使用服务而无需每次都手动注册行为:

Now you can use the service without manually registering the behavior each time:

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

您可以在MSDN的消息检查器文章中找到有关如何执行此操作的指南.

You can find a guide on how to do this in the MSDN's Message Inspectors article.

这篇关于从控制台应用程序记录SOAP消息的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆