2009-07-31 10 views
2

私はこの問題を最後の日に受けました。私はSOAP拡張機能をMSDNの記事とブログ投稿の後に作成しましたが、動作させることができません。 [OK]をいくつかのコード: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; 
    } 
} 

属性クラスもあります:私はBeforeDeserializationとAfterDeserializationでデバッグするとき、私は偉大である、インバウンドSOAPリクエストを見ることができるで

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

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

    public override int Priority 
    { 
     get; 
     set; 
    } 
} 

ので、メッセージが来るとき。私のWebサービスメソッドが呼び出されます。単純には次のとおりです。

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

次に、プロセスがSoapExtensionにホップバックします。 BeforeSerializationとAfterSerializationにブレークポイントを設定すると、アウトバウンドストリームには何も含まれていないことがわかります。私はそれがBeforeSerializationで空であることに驚いていませんが、私はそれがAfterSerializationで空であることに驚いています。これは、私がそれを暗号化できるように、アウトバウンドストリームを保持する必要があるため、問題を引き起こします。

送信ストリームが空である理由を教えてもらえますか?私はそれがhttp://msdn.microsoft.com/en-us/library/ms972410.aspxであってはならないことを示すこのMSDN記事に従っています。 設定などがありませんか?

答えて

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(メッセージ)とProcessReceivedStream(メッセージ)を呼び出しています。

ProcessTransmitStreamは、このMemoryStreamのPostionを最初に0にリセットした後に_accessStreamから入力を受け取り、その出力を_transportStreamに書き込みます(非常に制限されたアクセス(シークなしなど)が可能な場合があります。ローカルのMemoryStreamバッファーにコピーし、それを(_ PostStreamを0にリセットした後で)_transportStreamにコピーします。 (または、それをバイト配列または文字列に処理すると、_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によって返されたことに気付きました。私のテストでは、この例は一般的なドキュメントではなく、正しいものであることがわかりました。 –

+0

@Rob、ドキュメントのエラーが見つかった場合は、マイクロソフトに報告していただきたいと思います。 –

1

出力を操作できるようにするには、同じストリームを返すだけでなく、ChainStreamメソッドでさらに処理する必要があります。

また、実際にはProcessMessageメソッドで何かを実行する必要があります。提供されたコードには何も起こりません。

これは、SOAP拡張機能でよく読まれます:http://hyperthink.net/blog/inside-of-chainstream/です。 oldStreamやNewStreamよりも良い名前付けについてのコメントも必ず読んでください。個人的には、それらをwireStreamとappStreamと呼ぶと、物事は私にとってもっとはっきりと分かります。

+0

投稿する前にこの例を試しました。 SOAPExtensionクラスのChainStreamメソッドのデフォルトの動作は、ストリームを返すことです。私が行ったことですが、私が変数に入れた唯一の理由は、デバッグするときにストリームの内容を見ることができるからです。 –

2

このポストには、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> 
関連する問題