SOAP機能拡張を利用するための準備
SOAP機能拡張を利用するためには、次の2つのクラスを作成しなくてはなりません。
- System.Web.Services.Protocols.SoapExtensionを継承したクラス
- System.Web.Services.Protocols.SoapExtensionAttributeを継承したクラス
SOAP機能拡張で利用するロジックの実装は「SoapExtension」クラスを継承したクラスに行います。「SoapExtensionAttribute」クラスはその名の通り属性です。「SoapExtension」クラスをどのような属性名でWebMethodに適用するか、またSOAP機能拡張で使用できるパラメータを定義することができます。
SoapExtensionクラスの継承
まず、「SoapExtension」クラスを継承した「SampleExtention」クラスを作成します。
Imports System.IO
Imports System.Web.Services.Protocols
Imports System.Xml
Public Class SampleExtention
Inherits SoapExtension
' BeforeDeserializeでシリアル化されたSOAPメッセージを
' 保持するメンバ変数
Private _oldStream As Stream
' AfterSerializeでシリアル化されたSOAPメッセージを
' 保持するメンバ変数
Private _newStream As Stream
' カスタム属性を適用するように拡張機能を指定した場合実行されます
' 戻り値にはSOAP拡張機能でキャッシングしたい値を返します
Public Overloads Overrides Function GetInitializer _
(ByVal methodInfo As _
System.Web.Services.Protocols.LogicalMethodInfo, _
ByVal attribute As _
System.Web.Services.Protocols.SoapExtensionAttribute) _
As Object
Return Nothing
End Function
' web.config、またはapp.configに参照を追加した場合実行されます
' 戻り値にはSOAP拡張機能でキャッシングしたい値を返します
Public Overloads Overrides Function GetInitializer _
(ByVal serviceType As System.Type) As Object
Return Nothing
End Function
' SOAP機能拡張の初期化時に一度のみ実行されます
'GetInitializerメソッドでの戻り値がパラメータとして渡されます
Public Overloads Overrides Sub Initialize _
(ByVal initializer As Object)
End Sub
' 引数で渡されたSOAPメッセージへの参照をメンバ変数に設定します
' 戻り値はSOAP機能拡張で利用される戻り値への参照となります
Public Overloads Overrides Function ChainStream _
(ByVal stream As Stream) As Stream
_oldStream = stream
_newStream = New MemoryStream
Return _newStream
End Function
' SOAP拡張機能のすべてのSoapMessageStage段階で実行されます
Public Overloads Overrides Sub ProcessMessage _
(ByVal message As System.Web.Services.Protocols.SoapMessage)
Select Case message.Stage
Case SoapMessageStage.BeforeDeserialize
CopyStream(_oldStream, _newStream)
_newStream.Position = 0
Case SoapMessageStage.AfterSerialize
_newStream.Position = 0
If Not (message.Exception Is Nothing) Then
InsertDetailIntoOldStream(message.Exception)
Else
CopyStream(_newStream, _oldStream)
End If
End Select
End Sub
' 引数で渡されたStream変数間で値をコピーします
Private Sub CopyStream(ByVal from As Stream, ByVal [To] As Stream)
Dim reader As TextReader = New StreamReader(from)
Dim writer As TextWriter = New StreamWriter([To])
writer.WriteLine(reader.ReadToEnd)
writer.Flush()
End Sub
' WebMethod内で発生した例外情報をdetail要素に設定します
Private Sub InsertDetailIntoOldStream(ByVal exp As Exception)
Dim xmlDoc As XmlDocument = New XmlDocument
xmlDoc.Load(_newStream)
' detail要素を選択します
Dim detailNode As XmlNode = xmlDoc.SelectSingleNode("//detail")
' message要素を作成し、例外よりMessage情報を設定してから
' detail要素に追加します
Dim elem As XmlElement = xmlDoc.CreateElement("message")
elem.AppendChild(xmlDoc.CreateTextNode(_
exp.GetBaseException.Message))
detailNode.AppendChild(elem)
' stacktrace要素を作成し、例外よりStacktrace情報を設定してから
' detail要素に追加します
Dim elem2 As XmlElement = xmlDoc.CreateElement("stacktrace")
elem2.AppendChild(xmlDoc.CreateTextNode(_
exp.GetBaseException.StackTrace))
detailNode.AppendChild(elem2)
' date要素を作成し、現在日時を設定してから
' detail要素に追加します
Dim elem3 As XmlElement = xmlDoc.CreateElement("date")
elem3.AppendChild(xmlDoc.CreateTextNode(Date.Now))
detailNode.AppendChild(elem3)
'_oldStreamに書き込みを行います
Dim writer As XmlWriter = New XmlTextWriter(_oldStream, _
Encoding.UTF8)
xmlDoc.WriteTo(writer)
writer.Flush()
End Sub
End Class
実装されるメソッドは次の通りです。
| メソッド | 説明 |
| GetInitializer | Webサービスメソッド固有のデータを初期化 |
| Initialize | GetInitializerメソッドで戻された値を引数として受け取り、このパラメータを利用した初期化処理を実装できる |
| ChainStream | SOAP要求/応答を格納しているメモリバッファに、SOAP拡張機能からアクセス |
| ProcessMessage | SoapMessageオブジェクトの状態を識別することで、Webサービスメソッド実行の前後に処理を実装 |
| CopyStream | Stream型変数間でデータをコピー |
| InsertDetailIntoOldStream | Webサービスメソッド内で発生した例外情報をdetail要素に設定 |
上記メソッドは、それぞれ規定されたタイミングで実行されますが、ProcessMessageメソッドのみがSoapMessageStageに定義された段階でそれぞれ複数回実行されます。
SoapMessageStageには次の値が定義されています。
| 値 | 説明 |
| BeforeDeserialize | クライアントからSOAPメッセージを受信し、逆シリアライズする前に発生 |
| AfterDeserialize | クライアントからSOAPメッセージを受信し、逆シリアライズした後に発生 |
| BeforeSerialize | クライアントに返すSOAPメッセージをシリアライズする前に発生 |
| AfterSerialize | クライアントに返すSOAPメッセージをシリアライズした後に発生 |
これらをふまえ、SOAPメッセージを受信してからクライアントに返すまでの処理順をまとめると次のようになります。
- WebサーバがSOAPメッセージを受信
- GetInitializerメソッドの実行
- Initializeメソッドの実行
- ChainStreamメソッドの実行
- ProcessMessage(SoapMessageStage.BeforeDeserialize)の実行
ChainStreamで設定された送信メッセージ(_oldStream)の内容を_newStreamにコピーしています。- ProcessMessage(SoapMessageStage.AfterDeserialize)メソッドの実行
- Webサービスメソッドの実行
- ChainStreamメソッドの実行
- ProcessMessage(SoapMessageStage.BeforeSerialize)メソッドの実行
- ProcessMessage(SoapMessageStage.AfterSerialize)メソッドの実行
- WebサーバがクライアントにSOAPメッセージを返信
ここでポイントになるのは、ChainStream/ProcessMessageメソッドです。ChainStreamメソッドは、SOAPメッセージを取得するために使用できるストリームへの参照を受信できる唯一のメソッドになります。そこでChainStreamメソッドでは、この参照をメンバ変数に格納し、後からProcessMessageメソッドでSOAPメッセージを調べたり変更したりする際にアクセスできるようにしておきます。また、戻り値はSOAP機能拡張で利用される戻り値への参照となるので、新たにStreamオブジェクトを生成して返しています。
SOAPメッセージを変更するとき、SOAP拡張機能はChainStreamに渡したStreamから読み取りを行い、ChainStreamのStreamに戻り値を書き込みます。2つのStream参照をChainStreamに格納することがポイントです。
ProcessMessageメソッドではSoapMessageStageの状態に応じて、処理を分けています。SoapMessageStage.BeforeDeserializeの段階では_newStreamへ_oldStreamの内容をコピーしています。また、SoapMessageStage.AfterSerializeの段階では逆に_oldStreamに_newStremの内容をコピーしています。例外時には_newStreamの内容からdetail要素を選択し、message、stacktrace、date要素を追加してから_oldStreamに内容を書き出しています。
SoapExtensionAttributeの継承
次に、SoapExtensionAttributeクラスを継承したSampleExtentionAttributeクラスを作成します。
Imports System.Web.Services.Protocols
Public Class SampleExtentionAttribute
Inherits SoapExtensionAttribute
'SoapExtensionクラスを継承したクラスの型を返します
Public Overrides ReadOnly Property ExtensionType() As System.Type
Get
Return GetType(SampleExtention)
End Get
End Property
Private _priority As Integer = 0
' 指定したSoapExtension継承クラスの優先順位を指定します
Public Overrides Property Priority() As Integer
Get
Return _priority
End Get
Set(ByVal value As Integer)
_priority = value
End Set
End Property
End Class
| プロパティ | 説明 |
| ExtensionType | SOAP拡張機能のTypeを取得 |
| Priority | SOAP拡張機能の優先順位を取得/設定 |
ExtentionTypeプロパティでは、このAttributeクラスを属性とした場合に適用されるSoapExtensionクラスを継承したクラスをTypeオブジェクトとして返しています。また、PriorityプロパティではこのSoapExtensionクラスの実行順位を設定できます。ここでは優先順位を設定しない場合、初期値であるゼロを返すようにしています。
SOAP機能拡張の適用と実行例
それでは、作成した「SampleExtention」をWebサービスクラスに適用させましょう。
Imports System.Web Imports System.Web.Services Imports System.Web.Services.Protocols Public Class MessageService Inherits System.Web.Services.WebService <WebMethod()> _ <SampleExtention()> _ Public Function GenerateMessage(ByVal str As String) As String If String.IsNullOrEmpty(str) Then Throw New ArgumentException("空の文字は無効です。") End If Return "Hello !" + str End Function End Class
メソッドに<SampleExtention>属性を付与することで、作成したSOAP機能拡張「SampleExtention」を適用しています。ここでは、単一のSOAP機能拡張を利用していますが、複数のSOAP機能拡張を利用することも可能です。このとき、記述順とは関係なく、引数で指定した数値が小さいSOAP機能拡張から順に適用されるため、SOAP機能拡張の適用順を指定することができます。以下の例の場合、OtherExtentionが先に適用されます。
<WebMethod()> _ <SampleExtention(priority:=1)> _ <OtherExtention(priority:=0)> _ Public Function GenerateMessage(ByVal str As String) As String
そして、クライアント側でも例外情報を取得するように、例外処理を書き換えます。
Imports Microsoft.VisualBasic Imports System.Web.Services.Protocols Imports System.Text Public Class SampleForm Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button1.Click Dim service As MessageProxy.MessageService = _ New MessageProxy.MessageService Try TextBox2.Text = service.GenerateMessage(TextBox1.Text) Catch ex As SoapException If ex.Detail.HasChildNodes Then Dim builder As StringBuilder = New StringBuilder builder.Append("メッセージ : ") builder.Append(ex.Detail.ChildNodes(0).InnerText) builder.Append(vbNewLine) builder.Append("スタックトレース : ") builder.Append(ex.Detail.ChildNodes(1).InnerText) builder.Append(vbNewLine) builder.Append("発生日時 : ") builder.Append(ex.Detail.ChildNodes(2).InnerText) MessageBox.Show(builder.ToString, "エラー情報") End If End Try End Sub End Class
ここではSoapExtensionをキャッチし、detail要素から情報を取得しダイアログに表示させています。
実行例
エラーを発生させてみると、detail要素にメッセージ、スタックトレース、発生日時が追加されていることが確認できるはずです。
SoapExtensionで付加されたdetail要素からメッセージ、スタックトレース、発生日時を取得し、ダイアログに表示しています。

このようにSoapExtensionを適用するだけで、簡単に例外情報を付加することができました。
SoapToolKitの参考情報
最後にSoapToolKitの利用方法について簡単に説明します。
入手先とインストール方法
SOAP Toolkit 3.0のダウンロードページ中段の[Download]ボタンを押下してソフトウェアを入手してください。ダウンロード後はsoapsdk.exeを起動し、ウィザードに従ってインストールを実行します。
使い方
プログラムメニューより[Microsoft SOAP Toolkit Version 3]-[Trace Utility]から起動します。表示された画面のツールメニューの[File]-[New]を選択し、[Formatted Trace]を選びます。表示されたダイアログに下記の情報を入力すれば、Soapメッセージの内容を記録することができます。

| 項目 | 内容 |
| Local port | SoapToolKitが受信するポート番号 |
| Destination host | SoapToolKitにてSoapメッセージをトレース後、フォワードするホスト名 |
| Destination port | SoapToolKitにてSoapメッセージをトレース後、フォワードするホストのポート番号 |
まとめ
本稿では、SoapExtensionの機能を活用し、例外処理をビジネスロジックから切り離して簡潔に実装する方法について紹介しました。SoapExtensionは、これ以外にも通信内容のロギング、暗号化の実装などの用途にも活用できそうです。みなさんもアイデアを活用してシステム開発に役立ててください。
参考資料
- Web Services Developer Center Home
- XML Web サービスの作成とアクセスに関するチュートリアル(MSDNライブラリ)
- SOAP 拡張機能を使用した SOAP メッセージの変更(MSDNライブラリ)
- SoapExtensionクラス(MSDNライブラリ)
- SoapExtensionAttributeクラス(MSDNライブラリ)
- SoapMessageStage列挙体(MSDNライブラリ)
- SOAP Toolkit 3.0

