2009-07-31 95 views
2

我在最後一天遇到了這個問題。我已經在MSDN文章和一些博客帖子之後創建了一個SOAP擴展,但我無法讓它工作。好了一些代碼:序列化後SOAP擴展流爲空

public class EncryptionExtension : SoapExtension 
{ 

    Stream _stream; 
    public override object GetInitializer(Type serviceType) 
    { 
     return typeof(EncryptionExtension); 
    } 

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute) 
    { 
     return attribute; 
    } 

    public override void Initialize(object initializer) 
    { 
    } 

    public override void ProcessMessage(SoapMessage message) 
    { 

     switch (message.Stage) 
     { 

      case SoapMessageStage.BeforeSerialize: 
       break; 

      case SoapMessageStage.AfterSerialize: 
       break; 

      case SoapMessageStage.BeforeDeserialize: 
       break; 

      case SoapMessageStage.AfterDeserialize: 
       break; 
      default: 
       throw new Exception("invalid stage"); 
     } 

    } 
    public override Stream ChainStream(Stream stream) 
    { 
     _stream = stream; 
     return stream; 
    } 
} 

還有一個屬性類:

[AttributeUsage(AttributeTargets.Method)] 
public class EncryptionExtensionAttribute : SoapExtensionAttribute 
{ 

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

    public override int Priority 
    { 
     get; 
     set; 
    } 
} 

所以當消息進來,我可以看到入站SOAP請求時,我在BeforeDeserialization和AfterDeserialization,這是偉大的調試。然後調用我的Web服務方法。這是簡單的:

[WebMethod()] 
[EncryptionExtension] 
public string HelloWorld() 
{ 
    return "Hello world"; 
} 

該過程然後跳回到我的SoapExtension。在BeforeSerialization和AfterSerialization放置斷點時,我看到出站流不包含任何內容。我並不驚訝它在BeforeSerialization上是空的,但我很驚訝它在AfterSerialization中是空的。這會產生一個問題,因爲我需要獲取出站流,以便對其進行加密。

有人能告訴我爲什麼出站流是空的嗎?我遵循這個MSDN文章,這表明它不應該是http://msdn.microsoft.com/en-us/library/ms972410.aspx。 我是否缺少一些配置或其他內容?

回答

3

我發現這個問題在「SoapExtension MSDN」(它也可以找到帶有示例代碼的文檔作爲頂部命中)的谷歌搜索的頂部點擊之中,所以這裏有一些有用的建議給任何其他人試圖使在Soap擴展編碼方面有時會出現混淆或矛盾的文檔。

如果您正在修改序列化消息(作爲流),您需要從ChainStream覆蓋創建並返回一個不同的流。否則,你說你的擴展不會修改流並讓它通過。這個例子使用了一個MemoryStream,這可能是因爲奇怪的設計而必須使用的:當ChainStream被調用時,你不知道你是發送還是接收,所以你必須準備好處理它。我認爲,即使你只處理一個方向,你仍然需要處理另一個方向,並且將數據從一個流複製到另一個流,因爲你無需知道它是哪一種方式將自己插入鏈中。

private Stream _transportStream; // The stream closer to the network transport. 
private MemoryStream _accessStream; // The stream closer to the message access. 

public override Stream ChainStream(Stream stream) 
{ 
    // You have to save these streams for later. 
    _transportStream = stream; 
    _accessStream = new MemoryStream(); 
    return _accessStream; 
} 

然後你必須處理ProcessMessage中的AfterSerialize和BeforeDeserialize情況。我讓他們分別調用ProcessTransmitStream(message)和ProcessReceivedStream(message)來幫助保持流程清晰。

ProcessTransmitStream從_accessStream獲取輸入(在首次將此MemoryStream的位置重置爲0之後)並將其輸出寫入_transportStream - 這可能允許非常有限的訪問(無搜索等),所以我建議先處理本地MemoryStream緩衝區,然後將其複製到_transportStream中(將其位置重置爲0之後)。 (或者如果你將它處理成字節數組或字符串,你可以直接寫入_transportStream,我的用例是壓縮/解壓縮,因此我傾向於將它全部作爲流處理。)

ProcessReceivedStream需要從_transportStream輸入並將其輸出寫入_accessStream。在這種情況下,您應該首先將_transportStream複製到本地MemoryStream緩衝區(然後將緩衝區的位置重置爲0),以便您可以更方便地訪問。 (或者,您可以直接將整個_transportStream讀入字節數組或其他形式,如果這是您需要的話)。確保在返回之前重置_accessStream.Position = 0,以便它可以爲鏈中的下一個鏈接做好準備從它讀取。

這是爲了改變序列化的流。如果你不改變流,那麼你不應該覆蓋ChainStream(因此把你的擴展放在流處理鏈中)。相反,你會在BeforeSerialize和/或AfterDeserialize階段進行處理。在這些階段中,您不會修改或訪問流,而是處理消息對象本身,例如將自定義SoapHeader添加到BeforeSerialize階段中的message.Headers集合中。

SoapMessage類本身是抽象的,所以你真正得到的是SoapClientMessage或SoapServerMessage。文檔說你在客戶端獲得SoapClientMessage,在服務器端獲得SoapServerMessage(在調試器中進行試驗應該能夠確認或糾正)。它們在你所能訪問的內容上看起來非常相似,但你必須向正確的方向施放才能正確訪問它;使用錯誤的將會失敗,並且爲ProcessMessage參數聲明的基本SoapMessage類型不會讓您訪問所有內容。

我還沒有看過屬性的東西(它不會是我編碼的一部分),所以我不能幫助如何使用該部分。

0

我得到一個SOAP擴展工作的唯一方法是從MSDN例子開始,得到的例子工作。只有當它工作之後,我纔會逐漸改變它,逐步測試每一步,直到它達到我想要的水平。

這甚至可以告訴我我做錯了什麼,但對我而言,下次記憶永遠不夠。通常與Streams有關,但是。

+0

是啊,我注意到,在的SoapExtension類概述文檔的例子似乎是正確的(AFAICT),但描述了整個過程的更完整的文檔違背傳遞和返回ChainStream流的使用。我的測試發現,這個例子有他們的權利,而不是一般的文件。 – 2010-10-14 20:31:10

+0

@Rob,如果您發現文檔錯誤,我希望您將其報告給Microsoft。 – 2010-10-18 18:45:06

1

爲了能夠處理輸出,您需要在ChainStream方法中做更多的事情,而不僅僅是返回相同的流。

您還必須在ProcessMessage方法中實際執行某些操作。您提供的代碼中沒有任何事情發生。

這是關於SOAP擴展的一個很好的閱讀:http://hyperthink.net/blog/inside-of-chainstream/。一定還要閱讀有關更好的命名比oldStream和NewStream的評論。個人而言,將它們稱爲wireStream和appStream,讓我對事情變得更加清楚。

2

我在嘗試編寫SoapExtension時遇到此帖子,該SoapExtension會將我的web服務活動記錄在soap級別。該腳本經過測試,可用於在服務器端使用時將活動記錄到文本文件。客戶端不支持。

要使用您想要用於日誌文件寫入的實際目錄替換'C:\ Your Destination Directory'。

這項工作花了我整整一天,所以我張貼它希望其他人不必這樣做。

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Web.Services; 
using System.Web.Services.Protocols; 
using System.IO; 
using System.Net; 
using System.Reflection; 

    public class WebServiceActivityLogger : SoapExtension 
    { 
     string fileName = null; 

     public override object GetInitializer(Type serviceType) 
     { 
      return Path.Combine(@"C:\Your Destination Directory", serviceType.Name + " - " + DateTime.Now.ToString("yyyy-MM-dd HH.mm") + ".txt"); 
     } 

     public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute) 
     { 
      return Path.Combine(@"C:\Your Destination Directory", methodInfo.DeclaringType.Name + " - " + DateTime.Now.ToString("yyyy-MM-dd HH.mm") + ".txt"); 
     } 

     public override void Initialize(object initializer) 
     { 
      fileName = initializer as string; 
     } 

     Dictionary<int, ActivityLogData> logDataDictionary = new Dictionary<int, ActivityLogData>(); 
     private ActivityLogData LogData 
     { 
      get 
      { 
       ActivityLogData rtn; 
       if (!logDataDictionary.TryGetValue(System.Threading.Thread.CurrentThread.ManagedThreadId, out rtn)) 
        return null; 
       else 
        return rtn; 
      } 
      set 
      { 
       int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId; 
       if(logDataDictionary.ContainsKey(threadId)) 
       { 
        if (value != null) 
         logDataDictionary[threadId] = value; 
        else 
         logDataDictionary.Remove(threadId); 
       } 
       else if(value != null) 
        logDataDictionary.Add(threadId, value); 
      } 
     } 

     private class ActivityLogData 
     { 
      public string methodName; 
      public DateTime startTime; 
      public DateTime endTime; 
      public Stream transportStream; 
      public Stream accessStream; 
      public string inputSoap; 
      public string outputSoap; 
      public bool endedInError; 
     } 

     public override Stream ChainStream(Stream stream) 
     { 
      if (LogData == null) 
       LogData = new ActivityLogData(); 
      var logData = LogData; 

      logData.transportStream = stream; 
      logData.accessStream = new MemoryStream(); 
      return logData.accessStream; 
     } 

     public override void ProcessMessage(SoapMessage message) 
     { 
      if (LogData == null) 
       LogData = new ActivityLogData(); 
      var logData = LogData; 

      if (message is SoapServerMessage) 
      { 
       switch (message.Stage) 
       { 
        case SoapMessageStage.BeforeDeserialize: 
         //Take the data from the transport stream coming in from the client 
         //and copy it into inputSoap log. Then reset the transport to the beginning 
         //copy it to the access stream that the server will use to read the incoming message. 
         logData.startTime = DateTime.Now; 
         logData.inputSoap = GetSoapMessage(logData.transportStream); 
         Copy(logData.transportStream, logData.accessStream); 
         logData.accessStream.Position = 0; 
         break; 
        case SoapMessageStage.AfterDeserialize: 
         //Capture the method name after deserialization and it is now known. (was buried in the incoming soap) 
         logData.methodName = GetMethodName(message); 
         break; 
        case SoapMessageStage.BeforeSerialize: 
         //Do nothing here because we are not modifying the soap 
         break; 
        case SoapMessageStage.AfterSerialize: 
         //Take the serialized soap data captured by the access stream and 
         //write it into the log file. But if an error has occurred write the exception details. 
         logData.endTime = DateTime.Now; 
         logData.accessStream.Position = 0; 
         if (message.Exception != null) 
         { 
          logData.endedInError = true; 
          if (message.Exception.InnerException != null && message.Exception is System.Web.Services.Protocols.SoapException) 
           logData.outputSoap = GetFullExceptionMessage(message.Exception.InnerException); 
          else 
           logData.outputSoap = GetFullExceptionMessage(message.Exception); 
         } 
         else 
          logData.outputSoap = GetSoapMessage(logData.accessStream); 

         //Transfer the soap data as it was created by the service 
         //to the transport stream so it is received the client unmodified. 
         Copy(logData.accessStream, logData.transportStream); 
         LogRequest(logData); 
         break; 
       } 
      } 
      else if (message is SoapClientMessage) 
      { 
       throw new NotSupportedException("This extension must be ran on the server side"); 
      } 

     } 

     private void LogRequest(ActivityLogData logData) 
     { 
      try 
      { 
       //Create the directory if it doesn't exist 
       var directoryName = Path.GetDirectoryName(fileName); 
       if (!Directory.Exists(directoryName)) 
        Directory.CreateDirectory(directoryName); 

       using (var fs = new FileStream(fileName, FileMode.Append, FileAccess.Write)) 
       { 
        var sw = new StreamWriter(fs); 

        sw.WriteLine("--------------------------------------------------------------"); 
        sw.WriteLine("- " + logData.methodName + " executed in " + (logData.endTime - logData.startTime).TotalMilliseconds.ToString("#,###,##0") + " ms"); 
        sw.WriteLine("--------------------------------------------------------------"); 
        sw.WriteLine("* Input received at " + logData.startTime.ToString("HH:mm:ss.fff")); 
        sw.WriteLine(); 
        sw.WriteLine("\t" + logData.inputSoap.Replace("\r\n", "\r\n\t")); 
        sw.WriteLine(); 
        if (!logData.endedInError) 
         sw.WriteLine("* Output sent at " + logData.endTime.ToString("HH:mm:ss.fff")); 
        else 
         sw.WriteLine("* Output ended in Error at " + logData.endTime.ToString("HH:mm:ss.fff")); 
        sw.WriteLine(); 
        sw.WriteLine("\t" + logData.outputSoap.Replace("\r\n", "\r\n\t")); 
        sw.WriteLine(); 
        sw.Flush(); 
        sw.Close(); 
       } 
      } 
      finally 
      { 
       LogData = null; 
      } 
     } 

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

     private string GetMethodName(SoapMessage message) 
     { 
      try 
      { 
       return message.MethodInfo.Name; 
      } 
      catch 
      { 
       return "[Method Name Unavilable]"; 
      } 
     } 

     private string GetSoapMessage(Stream message) 
     { 
      if(message == null || message.CanRead == false) 
       return "[Message Soap was Unreadable]"; 
      var rtn = new StreamReader(message).ReadToEnd(); 
      message.Position = 0; 
      return rtn; 
     } 

     private string GetFullExceptionMessage(System.Exception ex) 
     { 
      Assembly entryAssembly = System.Reflection.Assembly.GetEntryAssembly(); 
      string Rtn = ex.Message.Trim() + "\r\n\r\n" + 
       "Exception Type: " + ex.GetType().ToString().Trim() + "\r\n\r\n" + 
       ex.StackTrace.TrimEnd() + "\r\n\r\n"; 
      if (ex.InnerException != null) 
       Rtn += "Inner Exception\r\n\r\n" + GetFullExceptionMessage(ex.InnerException); 
      return Rtn.Trim(); 
     } 
    } 

將此添加到您的服務器的web.config。

<system.web> 
     <webServices> 
     <soapExtensionTypes> 
      <add type="[Your Namespace].WebServiceActivityLogger, [Assembly Namespace], Version=1.0.0.0, Culture=neutral" priority="1" group="0" /> 
     </soapExtensionTypes> 
     </webServices> 
    </system.web>