SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

SOAP拡張機能でSoapExceptionに例外情報を付加する

例外の一元的なハンドリングと例外情報の付加


SOAP機能拡張を利用するための準備

 SOAP機能拡張を利用するためには、次の2つのクラスを作成しなくてはなりません。

  1. System.Web.Services.Protocols.SoapExtensionを継承したクラス
  2. System.Web.Services.Protocols.SoapExtensionAttributeを継承したクラス

 SOAP機能拡張で利用するロジックの実装は「SoapExtension」クラスを継承したクラスに行います。「SoapExtensionAttribute」クラスはその名の通り属性です。「SoapExtension」クラスをどのような属性名でWebMethodに適用するか、またSOAP機能拡張で使用できるパラメータを定義することができます。

SoapExtensionクラスの継承

 まず、「SoapExtension」クラスを継承した「SampleExtention」クラスを作成します。

SampleExtention.vbの抜粋
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

 実装されるメソッドは次の通りです。

SampleExtentionのメソッド
メソッド 説明
GetInitializer Webサービスメソッド固有のデータを初期化
Initialize GetInitializerメソッドで戻された値を引数として受け取り、このパラメータを利用した初期化処理を実装できる
ChainStream SOAP要求/応答を格納しているメモリバッファに、SOAP拡張機能からアクセス
ProcessMessage SoapMessageオブジェクトの状態を識別することで、Webサービスメソッド実行の前後に処理を実装
CopyStream Stream型変数間でデータをコピー
InsertDetailIntoOldStream Webサービスメソッド内で発生した例外情報をdetail要素に設定

 上記メソッドは、それぞれ規定されたタイミングで実行されますが、ProcessMessageメソッドのみがSoapMessageStageに定義された段階でそれぞれ複数回実行されます。

 SoapMessageStageには次の値が定義されています。

SoapMessageStage列挙体
説明
BeforeDeserialize クライアントからSOAPメッセージを受信し、逆シリアライズする前に発生
AfterDeserialize クライアントからSOAPメッセージを受信し、逆シリアライズした後に発生
BeforeSerialize クライアントに返すSOAPメッセージをシリアライズする前に発生
AfterSerialize クライアントに返すSOAPメッセージをシリアライズした後に発生

 これらをふまえ、SOAPメッセージを受信してからクライアントに返すまでの処理順をまとめると次のようになります。

  1. WebサーバがSOAPメッセージを受信
  1. GetInitializerメソッドの実行
  2. 初回のみ実行。
  1. Initializeメソッドの実行
  2. 初回のみ実行。
  1. ChainStreamメソッドの実行
  2. SOAPメッセージへの参照を保持し、新たなSOAPメッセージへの参照を設定。
  1. ProcessMessage(SoapMessageStage.BeforeDeserialize)の実行
  2. SOAPメッセージを受信した直後に実行。メッセージはまだ逆シリアライズされていません。ChainStreamで設定された送信メッセージ(_oldStream)の内容を_newStreamにコピーしています。
  1. ProcessMessage(SoapMessageStage.AfterDeserialize)メソッドの実行
  2. SOAPメッセージを逆シリアライズした直後に実行(本稿では使用しません)。
  1. Webサービスメソッドの実行
  1. ChainStreamメソッドの実行
  2. SOAPメッセージへの参照を保持し、新たなSOAPメッセージへの参照を設定。
  1. ProcessMessage(SoapMessageStage.BeforeSerialize)メソッドの実行
  2. SOAPメッセージをクライアント返すためにシリアライズする前に実行(本稿では使用しません)。
  1. ProcessMessage(SoapMessageStage.AfterSerialize)メソッドの実行
  2. SOAPメッセージをクライアントに返すためにシリアライズした後に実行。
  1. WebサーバがクライアントにSOAPメッセージを返信

 ここでポイントになるのは、ChainStreamProcessMessageメソッドです。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要素を選択し、messagestacktracedate要素を追加してから_oldStreamに内容を書き出しています。

SoapExtensionAttributeの継承

 次に、SoapExtensionAttributeクラスを継承したSampleExtentionAttributeクラスを作成します。

SampleExtentionAttribute.vbの抜粋
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
SoapExtensionAttributeのパブリックプロパティ
プロパティ 説明
ExtensionType SOAP拡張機能のTypeを取得
Priority SOAP拡張機能の優先順位を取得/設定

 ExtentionTypeプロパティでは、このAttributeクラスを属性とした場合に適用されるSoapExtensionクラスを継承したクラスをTypeオブジェクトとして返しています。また、PriorityプロパティではこのSoapExtensionクラスの実行順位を設定できます。ここでは優先順位を設定しない場合、初期値であるゼロを返すようにしています。

SOAP機能拡張の適用と実行例

 それでは、作成した「SampleExtention」をWebサービスクラスに適用させましょう。

SampleExtention適用後のMessageService.vbの抜粋
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が先に適用されます。

優先順位を指定したSOAP機能拡張の適用例
<WebMethod()> _
<SampleExtention(priority:=1)> _
<OtherExtention(priority:=0)> _
Public Function GenerateMessage(ByVal str As String) As String

 そして、クライアント側でも例外情報を取得するように、例外処理を書き換えます。

SoapExtension適用後のSampleForm.vbの抜粋
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要素にメッセージ、スタックトレース、発生日時が追加されていることが確認できるはずです。

エラー発生時のSOAP通信内容(SoapExtension実装時)
エラー発生時のSOAP通信内容(SoapExtension実装時)

 SoapExtensionで付加されたdetail要素からメッセージ、スタックトレース、発生日時を取得し、ダイアログに表示しています。

エラー内容のダイアログ表示例
エラー内容のダイアログ表示例

 このようにSoapExtensionを適用するだけで、簡単に例外情報を付加することができました。

SoapToolKitの参考情報

 最後にSoapToolKitの利用方法について簡単に説明します。

入手先とインストール方法

 SOAP Toolkit 3.0のダウンロードページ中段の[Download]ボタンを押下してソフトウェアを入手してください。ダウンロード後はsoapsdk.exeを起動し、ウィザードに従ってインストールを実行します。

使い方

 プログラムメニューより[Microsoft SOAP Toolkit Version 3]-[Trace Utility]から起動します。表示された画面のツールメニューの[File]-[New]を選択し、[Formatted Trace]を選びます。表示されたダイアログに下記の情報を入力すれば、Soapメッセージの内容を記録することができます。

SoapToolKitのダイアログ入力例
SoapToolKitのダイアログ入力例
SoapToolKitでのトレース実行時に必要な入力項目
項目 内容
Local port SoapToolKitが受信するポート番号
Destination host SoapToolKitにてSoapメッセージをトレース後、フォワードするホスト名
Destination port SoapToolKitにてSoapメッセージをトレース後、フォワードするホストのポート番号

まとめ

 本稿では、SoapExtensionの機能を活用し、例外処理をビジネスロジックから切り離して簡潔に実装する方法について紹介しました。SoapExtensionは、これ以外にも通信内容のロギング、暗号化の実装などの用途にも活用できそうです。みなさんもアイデアを活用してシステム開発に役立ててください。

参考資料

  1. Web Services Developer Center Home
  2. XML Web サービスの作成とアクセスに関するチュートリアル(MSDNライブラリ)
  3. SOAP 拡張機能を使用した SOAP メッセージの変更(MSDNライブラリ)
  4. SoapExtensionクラス(MSDNライブラリ)
  5. SoapExtensionAttributeクラス(MSDNライブラリ)
  6. SoapMessageStage列挙体(MSDNライブラリ)
  7. SOAP Toolkit 3.0
修正履歴

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

WINGSプロジェクト 奥山 学(オクヤマ マナブ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/834 2010/01/05 11:07

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー