拦截客户端与Web服务之间的SOAP消息

29

我有一个客户端与Web服务进行通信。我所通信的类是通过wsdl.exe生成的C#类。现在我想记录所有进出消息。

到目前为止,我已经编写了一个继承自自动生成的C#类并重写了GetReaderForMessage方法的类。这样,我就可以访问接收到的消息,类似于以下方式:

protected override XmlReader GetReaderForMessage(SoapClientMessage message, int bufferSize)
{
    System.Xml.XmlReader aReader = base.GetReaderForMessage(message, bufferSize);
    System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
    doc.Load(aReader);
    string content = doc.InnerXml.ToString();
    System.Xml.XmlReader aReader2 = System.Xml.XmlReader.Create(new System.IO.StringReader(content));

    return aReader2;
}
显然,我对这个解决方案不太满意,因为基本上我创建了两个xml阅读器。一个用于读取SOAP消息的内容,另一个用于返回给方法调用者。另外,我不能真正地使用GetWriterForMessage方法做同样的事情。但也许我开始时做的事情太困难了。例如,直接读取SoapClientMessage对象的内容是否可能?我已经阅读了一些文章建议在此处使用SoapExtensions,但从我所理解的来看,只有当我创建的“客户端”本身就是Web服务时才有效,而在这种情况下它不是。有什么建议吗?

你为什么不使用WCF呢? - John Saunders
7
我可以回答你的问题@JohnSaunders。我正在处理一大批旧代码,这些代码“能用”,业务部门不希望出现任何“不能用”的情况,因此对于“能用”的代码进行任何更改都不被看好。 - Chris Hayes
@ChrisHayes:你是@trabart吗?如果不是,那么你怎么回答我问他的问题。顺便说一句,“如果它没坏,就不要修理它”的观点我同意。但在我看来,需要修改现有代码(例如添加日志记录)意味着代码已经“坏了”。特别是,使用WCF比使用ASMX更容易实现OP想要做的事情。如果该代码将继续需要更改,则值得考虑升级。同样,需要频繁更改的VB6代码应升级为.NET,因为这将使更改变得更加容易。 - John Saunders
Trabart,我有类似的项目边界 - 服务是使用basicHttpBinding的WCF服务,但客户端仅限于使用.NET Framework 2.0。我找到了很多WCF客户端/服务器解决方案,但没有针对这种混合情况的。最终,我使用了@Ceottaki在I am consuming a WCF service that requires headers from a .NET 2 website. How can I programmatically add the headers to the messages?文章中描述的解决方案。希望能有所帮助。 - Rudolf Dvoracek
2个回答

61

若要使用此解决方案,您需要使用 "Add Service Reference" 而不是 "Add Web Reference" 功能,它可以用于 ASMX 或 WCF 服务。(您需要使用 .NET Framework 3.X 来使用此功能)

本文将帮助您将服务引用添加到 C# 项目中。

为拦截请求和响应的 XML,实现以下两个类:

public class InspectorBehavior : IEndpointBehavior
{
    public string LastRequestXML { 
        get
        {
            return myMessageInspector.LastRequestXML;
        }
    }

    public string LastResponseXML { 
        get
        {
            return myMessageInspector.LastResponseXML;
        }
    }


    private MyMessageInspector myMessageInspector = new MyMessageInspector();
    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {

    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {

    }

    public void Validate(ServiceEndpoint endpoint)
    {

    }


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





public class MyMessageInspector : IClientMessageInspector
{
    public string LastRequestXML { get; private set; }
    public string LastResponseXML { get; private set; }
    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
        LastResponseXML = reply.ToString();
    }

    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
    {
        LastRequestXML = request.ToString();
        return request;
    }
}

然后,将调用代码更改为:

MyTestServiceSoapClient client = new MyTestServiceSoapClient();
var requestInterceptor = new InspectorBehavior();
client.Endpoint.Behaviors.Add(requestInterceptor );
client.DoSomething("param1", "param2", "param3");
string requestXML = requestInterceptor.LastRequestXML;
string responseXML = requestInterceptor.LastResponseXML;

****编辑**** 这与服务器端技术无关,您可以将其用于WCF,ASMX,PHP等Web服务,我已在以下网站上进行了测试: http://www.w3schools.com/webservices/tempconvert.asmx

并得到以下XML:

requestXML=

<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/CelsiusToFahrenheit</Action>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <CelsiusToFahrenheit xmlns="http://tempuri.org/">
      <Celsius>50</Celsius>
    </CelsiusToFahrenheit>
  </s:Body>
</s:Envelope>

响应XML =

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <s:Header xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" />
  <soap:Body>
    <CelsiusToFahrenheitResponse xmlns="http://tempuri.org/">
      <CelsiusToFahrenheitResult>122</CelsiusToFahrenheitResult>
    </CelsiusToFahrenheitResponse>
  </soap:Body>
</soap:Envelope>

****编辑 2****

"Add Web Reference" 不是专为 ASMX 设计的,也不是 ASMX 客户端技术,而 "Add Service Reference" 也不是 WCF 客户端技术。你可以使用这两个工具来添加对于 ASMX、WCF、JSP 或 PHP 开发的 Web 服务的引用。如果你需要使用 "Add Service Reference" 来添加引用,你需要应用程序使用 .Net Framework 3.5。

这篇文章 提到:

在 Visual Studio 中使用 “Add Web Reference” 对话框时,将生成一个客户端代理,并将其添加到 Visual Studio 项目中。通常用于 ASMX 服务,但是您也可以使用 “Add Web Reference” 对话框为 WCF 服务创建客户端代理。但是,您需要手动输入服务 URL,并且生成的代理使用 XML 序列化,这是唯一支持的序列化类型。要为支持数据契约序列化器的 WCF 服务创建客户端代理,可以使用 Svcutil.exe 工具或使用 Visual Studio 开发工具的 .NET Framework 3.x 的 "Add Service Reference" 功能。


2
他似乎正在使用“Add Web Reference”—— ASMX 客户端技术,而不是 WCF 客户端技术的“Add Service Reference”。 - John Saunders
1
你误解了。WCF 包括客户端代码和服务器端代码。"添加服务引用" 为任何类型的服务创建 WCF 客户端。 - John Saunders
但是他可以改变生成代理类的方式,使用svcutil.exe而不是wsdl.exe,并像你说的那样生成WCF客户端!! 即使他的服务不是WCF服务,他也可以轻松地做到这一点,我已经在我的解决方案中提到了这一点。 - Saw
太好了!谢谢!从C#客户端获取Java Web服务SoapFault异常的详细信息一直让我很困扰,使用这种技术,我终于解决了问题。干杯!;-) - Ualter Jr.
拦截器工作得很好,但最好将其放在Try catch中: try { var response = await myclient.doactionAsync(); return response; } catch { _logger.LogInformation(myclient.requestInterceptor.LastRequestXML); _logger.LogInformation(myclient.requestInterceptor.LastResponseXML); } - Claudio Ferraro

6

好的...又出现了一个问题。我现在有一个继承自由wsdl生成的类的类。这个类有一个Login方法,它调用'wsdl'-class的Login方法。当我将扩展应用于这个wsdl-class时,一切似乎都正常,但我不想这样做,因为当Web服务更改时,这个类可能会被重新生成。所以现在我想将这个扩展应用于我的类,但是扩展类中的方法没有被调用...有什么建议吗? - trabart

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