【问题标题】:How do I get the XML SOAP request of an WCF Web service request?如何获取 WCF Web 服务请求的 XML SOAP 请求?
【发布时间】:2011-07-26 12:06:07
【问题描述】:

我在代码中调用此 Web 服务,我想查看 XML,但找不到公开它的属性。

【问题讨论】:

标签: c# .net xml wcf soap


【解决方案1】:

我认为您的意思是希望在客户端查看 XML,而不是在服务器上跟踪它。在这种情况下,您的答案在我上面链接的问题中,也在How to Inspect or Modify Messages on the Client 中。但是,由于那篇文章的 .NET 4 版本缺少其 C#,并且 .NET 3.5 示例中存在一些混淆(如果不是错误的话),因此这里为了您的目的对其进行了扩展。

您可以在消息发出之前使用IClientMessageInspector 拦截消息:

using System.ServiceModel.Dispatcher;
public class MyMessageInspector : IClientMessageInspector
{ }

该接口中的方法BeforeSendRequestAfterReceiveReply 允许您访问请求和回复。要使用检查器,您需要将其添加到IEndpointBehavior

using System.ServiceModel.Description;
public class InspectorBehavior : IEndpointBehavior
{
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new MyMessageInspector());
    }
}

您可以将该接口的其他方法保留为空实现,除非您也想使用它们的功能。阅读操作方法以了解更多详细信息。

实例化客户端后,将行为添加到端点。使用示例 WCF 项目中的默认名称:

ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
client.Endpoint.Behaviors.Add(new InspectorBehavior());
client.GetData(123);

MyMessageInspector.BeforeSendRequest()中设置断点; request.ToString() 被重载以显示 XML。

如果您要操作消息,则必须处理消息的副本。详情请见Using the Message Class

感谢Zach Bonham's answer 在另一个问题上找到这些链接。

【讨论】:

  • 我对你和 Zach Bonham 的回答都投了赞成票!感谢您提供简单的解决方案。我把它连接起来写一个基于配置的文件
  • public object BeforeSendRequest(ref Message request, IClientChannel channel) { if (ConfigurationManager.AppSettings["SaveWCFRequestXmlFiles"] != "true") return null; var file = Path.Combine(ConfigurationManager.AppSettings["XmlFilePath"], "Request-" + DateTime.Now.ToString("yyyyMMdd-Hmmssfff") + ".xml"); FileWriter.Write(request.ToString(), 文件);返回空值; }
  • 这行得通,但它并没有做我(和我假设的 OP)想要的,因为它不会让您访问到达 HTTP 级别的原始传入正文。 XML 在您获得之前已经过验证。当 Web 服务(有时会发生)回复 HTML 错误页面时,我需要获取该页面的 HTML。我得到并且绝对不需要的是被遗漏的 XML 的一部分(Message.ToString 中的“...Stream ...”,以及来自 XmlReader 的带有 ReadOuterXml 和 ReadInnerXml 的不完整 XML,您可以为 body )
  • 类似于@LucVdV 的评论,这并没有给你实际的要求。这产生的结果与 Fiddler 告诉您的实际结果之间存在细微差别。差异显着,以至于在发送请求时将结果复制到 SoapUI 或 Postman 会产生奇怪的错误。
【解决方案2】:

选项 1

使用消息跟踪/记录

看看herehere


选项 2

您始终可以使用Fiddler 查看 HTTP 请求和响应。


选项 3

使用System.Net tracing

【讨论】:

  • 在使用偏执级别的安全配置(例如,基于 SSL 的相互证书)时要小心 Fiddler,因为存在代理会导致此异常:System.ServiceModel.Security.SecurityNegotiationException
  • 不知道为什么选项 1 对我不起作用。尝试了几种不同的消息记录配置选项,但正文未显示在 SvcTraceViewer 中。
  • 选项 1 对我有用。我能够看到整个 SOAP 消息,包括传出和传入。我必须设置一个服务级别标志才能看到我想要的。 logMessagesAtServiceLevel="true"
  • @vbguyny 使用网络跟踪的完整示例?
【解决方案3】:
OperationContext.Current.RequestContext.RequestMessage 

此上下文在处理请求期间是可访问的服务器端。 这不适用于单向操作

【讨论】:

  • 服务器端,不客户端
【解决方案4】:

我们可以简单地跟踪请求消息。

OperationContext context = OperationContext.Current;

if (context != null && context.RequestContext != null)

{

Message msg = context.RequestContext.RequestMessage;

string reqXML = msg.ToString();

}

【讨论】:

  • 我猜这在服务端点(服务器)上可用。有没有办法在发送请求的客户端获得相同的结果?
【解决方案5】:

我只是想将此添加到 Kimberly 的答案中。也许它可以节省一些时间并避免因未实现 IEndpointBehaviour 接口所需的所有方法而导致的编译错误。

最好的问候

尼基

    /*
        // This is just to illustrate how it can be implemented on an imperative declarared binding, channel and client.

        string url = "SOME WCF URL";
        BasicHttpBinding wsBinding = new BasicHttpBinding();                
        EndpointAddress endpointAddress = new EndpointAddress(url);

        ChannelFactory<ISomeService> channelFactory = new ChannelFactory<ISomeService>(wsBinding, endpointAddress);
        channelFactory.Endpoint.Behaviors.Add(new InspectorBehavior());
        ISomeService client = channelFactory.CreateChannel();
    */    
        public class InspectorBehavior : IEndpointBehavior
        {
            public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
            {
                // No implementation necessary  
            }

            public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
            {
                clientRuntime.MessageInspectors.Add(new MyMessageInspector());
            }

            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
            {
                // No implementation necessary  
            }

            public void Validate(ServiceEndpoint endpoint)
            {
                // No implementation necessary  
            }  

        }

        public class MyMessageInspector : IClientMessageInspector
        {
            public object BeforeSendRequest(ref Message request, IClientChannel channel)
            {
                // Do something with the SOAP request
                string request = request.ToString();
                return null;
            }

            public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
            {
                // Do something with the SOAP reply
                string replySoap = reply.ToString();
            }
        }

【讨论】:

    【解决方案6】:

    我在 ASP.NET 兼容模式下使用以下 IIS 托管解决方案。感谢 Rodney Viana 的MSDN blog

    在 appSettings 下的 web.config 中添加以下内容:

    <add key="LogPath" value="C:\\logpath" />
    <add key="LogRequestResponse" value="true" />
    

    将您的 global.asax.cs 替换为以下内容(同时修复命名空间名称):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.SessionState;
    
    using System.Text;
    using System.IO;
    using System.Configuration;
    
    namespace Yournamespace
    {
        public class Global : System.Web.HttpApplication
        {
            protected static bool LogFlag;
            protected static string fileNameBase;
            protected static string ext = "log";
    
            // One file name per day
            protected string FileName
            {
                get
                {
                    return String.Format("{0}{1}.{2}", fileNameBase, DateTime.Now.ToString("yyyy-MM-dd"), ext);
                }
            }
    
            protected void Application_Start(object sender, EventArgs e)
            {
                LogFlag = bool.Parse(ConfigurationManager.AppSettings["LogRequestResponse"].ToString());
                fileNameBase = ConfigurationManager.AppSettings["LogPath"].ToString() + @"\C5API-";   
            }
    
            protected void Session_Start(object sender, EventArgs e)
            {
    
            }
    
            protected void Application_BeginRequest(object sender, EventArgs e)
            {
                if (LogFlag) 
                {                
                    // Creates a unique id to match Rquests with Responses
                    string id = String.Format("Id: {0} Uri: {1}", Guid.NewGuid(), Request.Url);
                    FilterSaveLog input = new FilterSaveLog(HttpContext.Current, Request.Filter, FileName, id);
                    Request.Filter = input;
                    input.SetFilter(false);
                    FilterSaveLog output = new FilterSaveLog(HttpContext.Current, Response.Filter, FileName, id);
                    output.SetFilter(true);
                    Response.Filter = output;
                }
            }
    
            protected void Application_AuthenticateRequest(object sender, EventArgs e)
            {
    
            }
    
            protected void Application_Error(object sender, EventArgs e)
            {
    
            }
    
            protected void Session_End(object sender, EventArgs e)
            {
    
            }
    
            protected void Application_End(object sender, EventArgs e)
            {
    
            }
        }
    
        class FilterSaveLog : Stream
        {
    
            protected static string fileNameGlobal = null;
            protected string fileName = null;
    
            protected static object writeLock = null;
            protected Stream sinkStream;
            protected bool inDisk;
            protected bool isClosed;
            protected string id;
            protected bool isResponse;
            protected HttpContext context;
    
            public FilterSaveLog(HttpContext Context, Stream Sink, string FileName, string Id)
            {
                // One lock per file name
                if (String.IsNullOrWhiteSpace(fileNameGlobal) || fileNameGlobal.ToUpper() != fileNameGlobal.ToUpper())
                {
                    fileNameGlobal = FileName;
                    writeLock = new object();
                }
                context = Context;
                fileName = FileName;
                id = Id;
                sinkStream = Sink;
                inDisk = false;
                isClosed = false;
            }
    
            public void SetFilter(bool IsResponse)
            {
    
    
                isResponse = IsResponse;
                id = (isResponse ? "Reponse " : "Request ") + id;
    
                //
                // For Request only read the incoming stream and log it as it will not be "filtered" for a WCF request
                //
                if (!IsResponse)
                {
                    AppendToFile(String.Format("at {0} --------------------------------------------", DateTime.Now));
                    AppendToFile(id);
    
                    if (context.Request.InputStream.Length > 0)
                    {
                        context.Request.InputStream.Position = 0;
                        byte[] rawBytes = new byte[context.Request.InputStream.Length];
                        context.Request.InputStream.Read(rawBytes, 0, rawBytes.Length);
                        context.Request.InputStream.Position = 0;
    
                        AppendToFile(rawBytes);
                    }
                    else
                    {
                        AppendToFile("(no body)");
                    }
                }
    
            }
    
            public void AppendToFile(string Text)
            {
                byte[] strArray = Encoding.UTF8.GetBytes(Text);
                AppendToFile(strArray);
    
            }
    
            public void AppendToFile(byte[] RawBytes)
            {
                bool myLock = System.Threading.Monitor.TryEnter(writeLock, 100);
    
    
                if (myLock)
                {
                    try
                    {
    
                        using (FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                        {
                            stream.Position = stream.Length;
                            stream.Write(RawBytes, 0, RawBytes.Length);
                            stream.WriteByte(13);
                            stream.WriteByte(10);
    
                        }
    
                    }
                    catch (Exception ex)
                    {
                        string str = string.Format("Unable to create log. Type: {0} Message: {1}\nStack:{2}", ex, ex.Message, ex.StackTrace);
                        System.Diagnostics.Debug.WriteLine(str);
                        System.Diagnostics.Debug.Flush();
    
    
                    }
                    finally
                    {
                        System.Threading.Monitor.Exit(writeLock);
    
    
                    }
                }
    
    
            }
    
    
            public override bool CanRead
            {
                get { return sinkStream.CanRead; }
            }
    
            public override bool CanSeek
            {
                get { return sinkStream.CanSeek; }
            }
    
            public override bool CanWrite
            {
                get { return sinkStream.CanWrite; }
            }
    
            public override long Length
            {
                get
                {
                    return sinkStream.Length;
                }
            }
    
            public override long Position
            {
                get { return sinkStream.Position; }
                set { sinkStream.Position = value; }
            }
    
            //
            // For WCF this code will never be reached
            //
            public override int Read(byte[] buffer, int offset, int count)
            {
                int c = sinkStream.Read(buffer, offset, count);
                return c;
            }
    
            public override long Seek(long offset, System.IO.SeekOrigin direction)
            {
                return sinkStream.Seek(offset, direction);
            }
    
            public override void SetLength(long length)
            {
                sinkStream.SetLength(length);
            }
    
            public override void Close()
            {
    
                sinkStream.Close();
                isClosed = true;
            }
    
            public override void Flush()
            {
    
                sinkStream.Flush();
            }
    
            // For streamed responses (i.e. not buffered) there will be more than one Response (but the id will match the Request)
            public override void Write(byte[] buffer, int offset, int count)
            {
                sinkStream.Write(buffer, offset, count);
                AppendToFile(String.Format("at {0} --------------------------------------------", DateTime.Now));
                AppendToFile(id);
                AppendToFile(buffer);
            }
    
        }
    }
    

    它应该在 LogPath 文件夹中创建带有请求和响应 XML 的日志文件。

    【讨论】:

    • 仅适用于服务器端 ?
    【解决方案7】:

    还有另一种查看 XML SOAP 的方法 - custom MessageEncoder。与 IClientMessageInspector 的主要区别在于它在较低级别上工作,因此它捕获原始字节内容,包括任何格式错误的 xml。

    为了使用这种方法实现跟踪,您需要将标准textMessageEncodingcustom message encoder 包装为新的binding element,并将该自定义绑定应用到config 中的端点。

    你也可以看看我在我的项目中是如何做到的—— wrapping textMessageEncoding,记录 encoder,自定义绑定 elementconfig

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多