65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Web 服务反序列化失败时,提取来自外部 Web 服务的原始 SOAP 消息

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (11投票s)

2005 年 8 月 11 日

3分钟阅读

viewsIcon

103897

本地 Web 服务调用第三方 Web 服务,该服务返回 XML 数据,但序列化失败。 本文展示了如何获取实际的 SOAP 消息。

捕获序列化无法处理的 SOAP 消息

在我的工作中,我必须编写代码来使用第三方的 Web 服务。 在与不同公司的 Web 服务合作的过程中,我发现其中一些服务会返回无效的 XML 数据。 这些数据由于过时的模式或一些微妙的问题而无效,这些问题会导致 Microsoft 的反序列化器抛出异常。 由于我需要查看发送给我的数据,我不得不绕过反序列化操作并访问原始 SOAP 消息,而 Microsoft 的代码拒绝向我的 Web 服务透露。 本文解释了我是如何通过创建一个与方法调用相关联的特定属性来绕过 Microsoft 的,该属性允许我在序列化之前查看 SOAP 消息,并将消息记录下来以便在需要时访问。

注释

  • 这些步骤不是我编写代码的方式,而是您应该如何进行实现的方式。
  • 本文不涵盖 C# Web 服务的“为什么”或“如何”。 它只详细介绍了在特殊情况下获取 SOAP 数据的方法。
  • 此问题发生在访问第三方 Web 服务的 C# Web 服务中,因此采用了以下设计。

步骤 1:将自定义属性插入到代理代码中

由于我们正在使用外部 Web 服务,我们要么必须处理 Microsoft 生成的代理代码,要么必须手动创建它; 无论哪种方式,我们都需要找到调用返回数据的位置。 这就是我们将放置我们的专用属性的位置。

如果是生成的代理,那么您需要查找位于 Web 服务文件夹中的隐藏文件 *Reference.cs*。 找到后,找到返回数据的主要方法。 这是您需要添加属性的位置,该属性将处理绕过反序列化情况的工作。

// Inside proxy class before data extraction call:

// Here is our attribute to capture 

// soap and log it for us.

[ClientSoapLogger()] 
public zzzResponse ProcessData(...
{
  object[] results = this.Invoke(...
  return((zzzResponse)(results[0]));
}

注释

  • 如果这是生成的代理代码,则每次重新生成代码时都需要添加该属性。
  • 您需要添加对属性代码所在命名空间的引用,如后续步骤所示。 如果您忘记了,构建过程将提醒您。

步骤 2:定义自定义属性 SoapExtensionAttribute

该属性是我们数据记录过程的钩子。 这是带有适当重写的属性的类。

/// <summary>The attribute [ClientSoapLogger()] 

/// will appear in the data return method found in the

/// proxy code.</summary>

[AttributeUsage(AttributeTargets.Method)]
public class ClientSoapLoggerAttribute : 
                                SoapExtensionAttribute
{
   private int m_Priority = 0;

   public override Type ExtensionType
   {
      get { return typeof(ClientSoapLogger); }
   }

   public override int Priority
   {
      get { return m_Priority;  }
      set { m_Priority = value; }
   }
} // End of class ClientSoapLoggerAttribute

步骤 3:创建 SoapExtension

SOAP 扩展将以自主的方式与 SOAP 消息的处理流程配合使用。 我们的目标不是破坏数据流,而只是查看它以提取数据。

/// <summary>This class handles the data 

/// during the differing states and 

/// does so autonomously; ie in the background 

/// without affecting standard processing..</summary>

public class ClientSoapLogger : SoapExtension
{
   public Stream oldStream;
   public Stream newStream;

   public override object GetInitializer(
                             Type serviceType)
   {
      return null;
   }
   public override object GetInitializer(
                     LogicalMethodInfo methodInfo, 
                     SoapExtensionAttribute attribute)
   {
      return null;
   }
   public override void Initialize(object initializer)
   {
      return;
   }

   /// <summary>Here is where we take a 

   /// dip into the stream. Due to the

   /// nature of streams if we dipped 

   /// without copying, we would damage the

   /// stream.</summary>

   public override Stream ChainStream(Stream stream)
   {
      oldStream = stream;
      newStream = new MemoryStream();
      return newStream;
   }

   /// <summary>With this override we can 

   /// access the data during different stages

   /// of processing. Logging occurs during the 

   /// before and after serialization.</summary>

   public override void ProcessMessage(SoapMessage message)
   {
      switch(message.Stage)
      {
         case SoapMessageStage.BeforeDeserialize: 
                                LogResponse(message); break;
         case SoapMessageStage.AfterSerialize:    
                                LogRequest(message);  break;

         // Do nothing on other states

         case SoapMessageStage.AfterDeserialize:
         case SoapMessageStage.BeforeSerialize:                    
         default:
            break;
      }
   }
// Class continued in step 4

步骤 4:记录数据

这是真正打动我的步骤。 如果您的代码位于 Web 服务中(我的代码是调用 Web 服务的 Web 服务),那么如何在 SOAP 进程和访问代理的代码之间传递数据? 在某些情况下,例如 ASP.NET,可以将消息放在 HttpContext 上,但这对于我在 Web 服务中的代码来说不是一个选项。 下面的代码通过将数据放在 Remoting 调用上下文中来处理空 HttpContext 的情况。

/// <summary>Publish out the message received 

/// either in the HttpContext for ASP pages

/// or publish via remoting for webservices 

/// and winform applications.</summary>

public void LogResponse(SoapMessage message)
{
   Copy(oldStream,newStream);

   newStream.Position = 0;

   string strSOAPresponse = 
              ExtractFromStream(newStream);

   HttpContext hc = HttpContext.Current;

   if (hc != null)
      hc.Items.Add("SOAPResponse", 
                       strSOAPresponse);
   else
      System.Runtime.Remoting.Messaging.CallContext.SetData(
                              "SOAPResponse",strSOAPresponse);
      
   newStream.Position = 0;

}

/// <summary>Transfer the text from the target 

/// stream from the current position.</summary>

private String ExtractFromStream(Stream target)
{
   if (target != null)
      return (new StreamReader(target)).ReadToEnd();

   return "";
}

/// <summary>Facilitate the copying process.</summary>

private void Copy(Stream from, Stream to)
{
   TextReader reader = new StreamReader(from);
   TextWriter writer = new StreamWriter(to);
   writer.WriteLine(reader.ReadToEnd());
   writer.Flush();
}
} // End ClientSoapLogger class

步骤 5:使用记录的数据

对于我的 Web 服务,如果传入的数据无效,我的指示是反序列化器抛出的异常,或者数据访问方法简单地返回一个 null 值。 此时,我的代码尝试对 SOAP 原始数据进行后提取,并处理该消息。

[编辑者注:使用换行符以避免滚动。]

// New class outside ClientSoapLogger...


/// <summary>Examine the soap return for possible missing 

/// data since the return was null.</summary>

protected virtual void DiagnoseResponseProblem()
{
   HttpContext hc = HttpContext.Current;
   string SoapResponse = null;
   string SoapRequest  = null;

   // Post processing debugging pull out 

   // the actual soap message and debug it.

   if (hc != null)
   {
      SoapRequest  = hc.Items["SOAPRequest"].ToString();
      SoapResponse = hc.Items["SOAPResponse"].ToString();
   }
   else
   {
      try
      {
         SoapResponse = System.Runtime.Remoting.Messaging.
               CallContext.GetData("SOAPResponse").ToString();
         SoapRequest  = System.Runtime.Remoting.Messaging.
                CallContext.GetData("SOAPRequest").ToString();

         System.Runtime.Remoting.Messaging.CallContext.
                            FreeNamedDataSlot("SOAPResponse");
         System.Runtime.Remoting.Messaging.CallContext.
                             FreeNamedDataSlot("SOAPRequest");
      }
      // Ignore...most likely this system does not 

      // have the remote message context setup.

      catch (System.Exception){} 
   }
   ...
}

注释

  • 如果您有更好的方法或可以改进的地方,请告诉我。
© . All rights reserved.