none
あるサービスインスタンスから、別のインスタンスのコールバックを実行したい RRS feed

  • 質問

  • いつもありがとうございます。

    双方向通信のコールバックを、任意に動かしたいと思っています。

    しかし、実装方法がわからなくて困っています。可能なのかもわかりません。

    クライアントはすべてコールバックコントラクトを実装している、次のようなシナリオです。

    ■その1
     クライアントAがサービスのメソッド「呼出」を呼び出す
             ↓
     サーバーとセッションのある特定のクライアントのコールバックが呼び出される。

    ■その2
     クライアントAがサービス
    のメソッド「一斉呼出」を呼び出す
             ↓
     サーバーとセッションのあるすべてのクライアントのコールバックが呼び出される。


    IISのWASでは無理でしょうか。
    では、サービスホストアプリケーションならできるのでしょうか。
    いずれにせよ、一クライアントの挙動をトリガに、別のインスタンスのコールバックを起動したいのです。

    よろしくお願いします。
    2008年12月12日 6:36

回答

  •  尾画茶 さんからの引用

    EndPointBehaviorへの登録の仕方がわからず、行き詰ってしまいました。

     

    そこらへんの理屈の説明がほとんど無いので苦労しますよね。

     

    例です。

    改行されまくりだと思うのでVSに貼り付けたほうが見やすいかと思います。

     

    Code Snippet

    ' このインスタンスをEndPointへ割り当てる
    Public Class MyServiceContractBehavior
        Implements Description.IContractBehavior

        Private _InstanceProvider As Dispatcher.IInstanceProvider
        Public ReadOnly Property InstanceProvider() As Dispatcher.IInstanceProvider
            Get
                Return _InstanceProvider
            End Get
        End Property

        Public Sub New(ByVal instanceProvider As Dispatcher.IInstanceProvider)

            _InstanceProvider = instanceProvider

        End Sub

    #Region "IContractBehavior"

        Public Sub AddBindingParameters(ByVal contractDescription As System.ServiceModel.Description.ContractDescription, ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal bindingParameters As System.ServiceModel.Channels.BindingParameterCollection) Implements System.ServiceModel.Description.IContractBehavior.AddBindingParameters
            ' 処理無し
        End Sub
        Public Sub ApplyClientBehavior(ByVal contractDescription As System.ServiceModel.Description.ContractDescription, ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal clientRuntime As System.ServiceModel.Dispatcher.ClientRuntime) Implements System.ServiceModel.Description.IContractBehavior.ApplyClientBehavior
            ' 処理無し
        End Sub

        Public Sub ApplyDispatchBehavior(ByVal contractDescription As System.ServiceModel.Description.ContractDescription, ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal dispatchRuntime As System.ServiceModel.Dispatcher.DispatchRuntime) Implements System.ServiceModel.Description.IContractBehavior.ApplyDispatchBehavior

            dispatchRuntime.InstanceProvider = _InstanceProvider

        End Sub

        Public Sub Validate(ByVal contractDescription As System.ServiceModel.Description.ContractDescription, ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint) Implements System.ServiceModel.Description.IContractBehavior.Validate
            ' 処理無し
        End Sub

    #End Region

    End Class


    ' サービスインスタンスを管理するクラス
    Public Class ServiceManager

        Implements Dispatcher.IInstanceProvider

    #Region "IInstanceProvider Interface"

        Friend Overloads Function GetServiceInstance(ByVal instanceContext As System.ServiceModel.InstanceContext) As Object Implements System.ServiceModel.Dispatcher.IInstanceProvider.GetInstance

        End Function

        Friend Overloads Function GetInstanceByMessage(ByVal instanceContext As System.ServiceModel.InstanceContext, ByVal message As System.ServiceModel.Channels.Message) As Object Implements System.ServiceModel.Dispatcher.IInstanceProvider.GetInstance

            Try

                ' サービスインスタンスをここで一括自前で作成
                Dim newInstance As New MyService(Me)

                ' ここでサービスインスタンスへ情報をいろいろ渡せます

                Return newInstance

            Catch ex As Exception

                Return Nothing

            End Try

        End Function

        Friend Sub ReleaseInstance(ByVal instanceContext As System.ServiceModel.InstanceContext, ByVal instance As Object) Implements System.ServiceModel.Dispatcher.IInstanceProvider.ReleaseInstance

        End Sub

    #End Region

    #Region "拡張動作の登録"

        ' ホストをOpenする前に呼び出す。
        Public Sub InitializeBehaviour()

            ' 拡張コントラクト
            Dim newContractBehavior As New MyServiceContractBehavior(Me)

            ' 拡張コントラクトの登録
            For Each se As Description.ServiceEndpoint In myServiceHost.Description.Endpoints
                If Not se.Name.ToLower.EndsWith("mex") Then

                    ' 割り当て:これにより↑のIInstanceProvider実装が呼ばれるようになる
                    se.Contract.Behaviors.Add(newContractBehavior)

                    ' おまけ:あるメソッドのメッセージングがXMLの要素が多くパンクする場合、大きくしてみる
                    For Each od As Description.OperationDescription In se.Contract.Operations
                        If od.Name.EndsWith("MethodWithManyXmlItems") Then
                            Dim sb As Description.DataContractSerializerOperationBehavior = _
                                    od.Behaviors.Find(Of Description.DataContractSerializerOperationBehavior)()
                            If sb Is Nothing Then
                                sb = New Description.DataContractSerializerOperationBehavior(od)
                                od.Behaviors.Add(sb)
                            End If
                            With sb
                                .MaxItemsInObjectGraph = Integer.MaxValue
                            End With
                        End If
                    Next
                End If
            Next

        End Sub

    #End Region

    End Class

     

     

     尾画茶 さんからの引用

    セッションの開始終了ということは、サービスインスタンスのコンストラクタとDisposeで実装ということでしょうか。

     

    私の場合、コレクションへ登録・解除しているのはOpenSession(Initiating=True),CloseSession(IsTerminating=True)です。

    当然、いろんなきっかけの異常終了時にもコレクションから解除しています。

    あと、サービスインスタンスへServiceState プロパティを実装して「ログイン中」「異常終了」などの状態を持たせて

    コレクションに対しての操作対象の判断等に利用しています。

     

    余談ですが、OpenSession、Login、CloseSession、Logout とわざわざ四段階の業務インターフェースにしてます。

    OpenSessionとLoginの間の接続状態を利用して、サーバーから初期化情報などを取得できる期間を作るためです。

    と言うより、Loginという概念+Initiating=Trueだと身動き取れなくなる落ち。。。

    #有効なインスタンスかどうかの瞬間的な判断もできませんし。

     

    2008年12月15日 10:42

すべての返信

  • こんにちは

     

    求める回答になっているかわかりませんが。

     

     尾画茶 さんからの引用

    クライアントはすべてコールバックコントラクトを実装している、次のようなシナリオです。

    ■その1
     クライアントAがサービスのメソッド「呼出」を呼び出す
             ↓
     サーバーとセッションのある特定のクライアントのコールバックが呼び出される。

     

    クライアントAとは別のクライアントに対してコールバックを呼び出したいということですか?
    尾画茶さんが特定といっているクライアントの特定方法がわからいないのでなんともいえませんが、サービス層からみた場合、あくまでWCFが行うのはメッセージ通信の抽象化だと考えています。特定する仕込みは作りこんでおく必要があると思います。


     尾画茶 さんからの引用

    ■その2
     クライアントAがサービスのメソッド「一斉呼出」を呼び出す
             ↓
     サーバーとセッションのあるすべてのクライアントのコールバックが呼び出される。


    IISのWASでは無理でしょうか。
    では、サービスホストアプリケーションならできるのでしょうか。
    いずれにせよ、一クライアントの挙動をトリガに、別のインスタンスのコールバックを起動したいのです。

    単純な方法でよいなら、サービスクラスにstaticなコレクション型を用意し、クライアントのコールバックコンテキストの参照
    を保持しておいて特定のオペレーションが呼び出された時にコールバックを呼び出すようにすればできなくはないかなと思いました。

     

     尾画茶 さんからの引用

    IISのWASでは無理でしょうか。
    では、サービスホストアプリケーションならできるのでしょうか。
    いずれにせよ、一クライアントの挙動をトリガに、別のインスタンスのコールバックを起動したいのです。

     

    どの方法でホストするかは基本的に関係ないと思います。

    2008年12月13日 10:16
  • #そういうことばっかりやってるまどかです。

     

    いわゆる業務ログインをしたら全クライアントにログインイベント投げてたりします。

    こちらでのパターンを書きます。

     

    まず、サービスインスタンスを管理するクラス(ServiceManager)がありサービスインスタンスのコレクションを保持します。

    IInstanceProviderを実装しEndPointBehaiviorへ登録して、サービスインスタンスの作成を引き受けます。

    そしてサービスインスタンスの作成時にServiceManager自身の参照をサービスインスタンスへ渡します。

    コレクションへはセッションの開始終了で出し入れします。

    #個人的なポリシーだとサービスインスタンスからイベントを受け取りたいのですが

    #密接でやり取りがたくさんできるので一応インターフェース型で参照を渡しちゃってます。

     

    あとは、サービスインスタンスがServiceManager参照のコールバック依頼のメソッドを呼ぶだけです。

    ServiceManagerは受け取ったサービスインスタンスの情報とコレクションを照らし合わせて

    宛先のサービスインスタンスのコールバック送信メソッド(これも用意しておく)を呼び出します。

    サービスインスタンスはセッション確立時にGetCallbackChannelのリターンを保持しておき利用します。

     

    上記の仕掛けで他にやってることは

    ・ServiceManagerでセッションIDを採番してサービスインスタンスへ渡す。

    ・IsTerminating=Trueなコールバックを用意して、ServiceManagerから一斉切断する。(異常終了やWindowsシャットダウン時)

    ってとこです。

     

    2008年12月14日 18:58
  • handcraftさん、まどかさん、いつも助けていただいて、ありがとうございます。

     > サービスクラスにstaticなコレクション型を用意し、クライアントのコールバックコンテキストの参照を保持しておいて

     > 特定のオペレーションが呼び出された時にコールバックを呼び出すようにすればできなくはないかなと思いました。

     > どの方法でホストするかは基本的に関係ないと思います。

    というhandcraftさんのお答えに対して、
    public コンストラクタ()
    {
                  Server.Program.AddInstance(this);
    }
    のようにしてみました。目下、検証中です。
    ただ、WASの場合、ホストがIISであり、アプリケーションドメインも異なるとなると、それもWCFで実装する必要があるのでしょうか。

    まどかさんの示したくださった方法を試そうと頑張ったのですが、EndPointBehaviorへの登録の仕方がわからず、行き詰ってしまいました。
    セッションの開始終了ということは、サービスインスタンスのコンストラクタとDisposeで実装ということでしょうか。
    IInstanceProviderなど、もっとよくよく勉強しないと、実装できないように思いました。
    WCFは奥が深く、結構難儀しております……。


    2008年12月15日 8:35
  •  尾画茶 さんからの引用

    EndPointBehaviorへの登録の仕方がわからず、行き詰ってしまいました。

     

    そこらへんの理屈の説明がほとんど無いので苦労しますよね。

     

    例です。

    改行されまくりだと思うのでVSに貼り付けたほうが見やすいかと思います。

     

    Code Snippet

    ' このインスタンスをEndPointへ割り当てる
    Public Class MyServiceContractBehavior
        Implements Description.IContractBehavior

        Private _InstanceProvider As Dispatcher.IInstanceProvider
        Public ReadOnly Property InstanceProvider() As Dispatcher.IInstanceProvider
            Get
                Return _InstanceProvider
            End Get
        End Property

        Public Sub New(ByVal instanceProvider As Dispatcher.IInstanceProvider)

            _InstanceProvider = instanceProvider

        End Sub

    #Region "IContractBehavior"

        Public Sub AddBindingParameters(ByVal contractDescription As System.ServiceModel.Description.ContractDescription, ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal bindingParameters As System.ServiceModel.Channels.BindingParameterCollection) Implements System.ServiceModel.Description.IContractBehavior.AddBindingParameters
            ' 処理無し
        End Sub
        Public Sub ApplyClientBehavior(ByVal contractDescription As System.ServiceModel.Description.ContractDescription, ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal clientRuntime As System.ServiceModel.Dispatcher.ClientRuntime) Implements System.ServiceModel.Description.IContractBehavior.ApplyClientBehavior
            ' 処理無し
        End Sub

        Public Sub ApplyDispatchBehavior(ByVal contractDescription As System.ServiceModel.Description.ContractDescription, ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal dispatchRuntime As System.ServiceModel.Dispatcher.DispatchRuntime) Implements System.ServiceModel.Description.IContractBehavior.ApplyDispatchBehavior

            dispatchRuntime.InstanceProvider = _InstanceProvider

        End Sub

        Public Sub Validate(ByVal contractDescription As System.ServiceModel.Description.ContractDescription, ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint) Implements System.ServiceModel.Description.IContractBehavior.Validate
            ' 処理無し
        End Sub

    #End Region

    End Class


    ' サービスインスタンスを管理するクラス
    Public Class ServiceManager

        Implements Dispatcher.IInstanceProvider

    #Region "IInstanceProvider Interface"

        Friend Overloads Function GetServiceInstance(ByVal instanceContext As System.ServiceModel.InstanceContext) As Object Implements System.ServiceModel.Dispatcher.IInstanceProvider.GetInstance

        End Function

        Friend Overloads Function GetInstanceByMessage(ByVal instanceContext As System.ServiceModel.InstanceContext, ByVal message As System.ServiceModel.Channels.Message) As Object Implements System.ServiceModel.Dispatcher.IInstanceProvider.GetInstance

            Try

                ' サービスインスタンスをここで一括自前で作成
                Dim newInstance As New MyService(Me)

                ' ここでサービスインスタンスへ情報をいろいろ渡せます

                Return newInstance

            Catch ex As Exception

                Return Nothing

            End Try

        End Function

        Friend Sub ReleaseInstance(ByVal instanceContext As System.ServiceModel.InstanceContext, ByVal instance As Object) Implements System.ServiceModel.Dispatcher.IInstanceProvider.ReleaseInstance

        End Sub

    #End Region

    #Region "拡張動作の登録"

        ' ホストをOpenする前に呼び出す。
        Public Sub InitializeBehaviour()

            ' 拡張コントラクト
            Dim newContractBehavior As New MyServiceContractBehavior(Me)

            ' 拡張コントラクトの登録
            For Each se As Description.ServiceEndpoint In myServiceHost.Description.Endpoints
                If Not se.Name.ToLower.EndsWith("mex") Then

                    ' 割り当て:これにより↑のIInstanceProvider実装が呼ばれるようになる
                    se.Contract.Behaviors.Add(newContractBehavior)

                    ' おまけ:あるメソッドのメッセージングがXMLの要素が多くパンクする場合、大きくしてみる
                    For Each od As Description.OperationDescription In se.Contract.Operations
                        If od.Name.EndsWith("MethodWithManyXmlItems") Then
                            Dim sb As Description.DataContractSerializerOperationBehavior = _
                                    od.Behaviors.Find(Of Description.DataContractSerializerOperationBehavior)()
                            If sb Is Nothing Then
                                sb = New Description.DataContractSerializerOperationBehavior(od)
                                od.Behaviors.Add(sb)
                            End If
                            With sb
                                .MaxItemsInObjectGraph = Integer.MaxValue
                            End With
                        End If
                    Next
                End If
            Next

        End Sub

    #End Region

    End Class

     

     

     尾画茶 さんからの引用

    セッションの開始終了ということは、サービスインスタンスのコンストラクタとDisposeで実装ということでしょうか。

     

    私の場合、コレクションへ登録・解除しているのはOpenSession(Initiating=True),CloseSession(IsTerminating=True)です。

    当然、いろんなきっかけの異常終了時にもコレクションから解除しています。

    あと、サービスインスタンスへServiceState プロパティを実装して「ログイン中」「異常終了」などの状態を持たせて

    コレクションに対しての操作対象の判断等に利用しています。

     

    余談ですが、OpenSession、Login、CloseSession、Logout とわざわざ四段階の業務インターフェースにしてます。

    OpenSessionとLoginの間の接続状態を利用して、サーバーから初期化情報などを取得できる期間を作るためです。

    と言うより、Loginという概念+Initiating=Trueだと身動き取れなくなる落ち。。。

    #有効なインスタンスかどうかの瞬間的な判断もできませんし。

     

    2008年12月15日 10:42
  • まどかさん、ありがとうございました。

    VB.NETの記法に慣れておらず、かなり戸惑ってしまいましたが、無事、示されたコードと等価なもので実行できました。
    しかし、まだまだWCFは、奥深すぎて、ちょっとうまく行ったと思うとはつまづきます。

    あんまり資料もないし、本当に助かりました。
    2008年12月22日 16:01