🕌

WCFの集約例外と操作記録

2022/12/05に公開約8,200字

元記事(最新はこちらを参照):
https://qiita.com/tfukumori/items/5dac4b1c37489161c5c0


概要

  • WCFサービスを利用した際に集約例外と操作記録を行う方法
  • 実務ではDebug.Printの代わりにログツール(NLog, log4net)を利用する

環境

  • .NETFx 4.8
  • Visual Studio 202

処理概要

  • (1) 集約例外、操作記録の属性LogErrorOperationAttributeを作成する

    • System.Attributeを継承する
    • System.Web.Services.Description.IServiceBehaviorインタフェース実装を作成する
  • (2) 集約例外

    • System.Web.Services.Description.IServiceBehaviorインタフェースのApplyDispatchBehaviorメソッドで、serviceHostBase.ChannelDispatchersのエラーハンドルを利用する
      • 独自に作成したSystem.ServiceModel.Dispatcher.IErrorHandlerを継承したLogErrorHandlerクラスを追加し、この中のHandleErrorイベントで記録する
  • (3) 操作記録

    • System.Web.Services.Description.IServiceBehaviorインタフェースのApplyDispatchBehaviorメソッドで、serviceDescription.Endpointsの操作Contract.OperationsLoggingOperationBehaviorを設定する
      • System.Web.Services.Description.IOperationBehavior実装のLoggingOperationBehaviorでは、ApplyDispatchBehaviorメソッドで、パラメーターを受け取るためのユーザー定義メソッドとしてLoggingOperationInvokerを設定する
        • System.ServiceModel.Dispatcher.IOperationBehavior実装のLoggingOperationInvokerでは、Invokeメソッドで操作を記録する
  • (4) WCFサービスの実装にLogErrorOperationAttribute属性を設定する

呼び出し側

Dim sv = New Service1Service.Service1Client
Debug.Print(sv.DoWork("test"))

Web側

WCFメソッド

  • WCFの実装クラスに作成した属性LogErrorOperationAttributeを設定する
' メモ: コンテキスト メニューの [名前の変更] コマンドを使用すると、コード、svc、および config ファイルで同時にクラス名 "Service1" を変更できます。
' 注意: このサービスをテストするために WCF テスト クライアントを起動するには、ソリューション エクスプローラーで Service1.svc または Service1.svc.vb を選択し、デバッグを開始してください。
<LogErrorOperationAttribute>
Public Class Service1
    Implements IService1

    Public Function DoWork(value1 As String) As String Implements IService1.DoWork
        Return $"{value1}:{Now}"
    End Function
End Class

ログ

開始 アクション名:IService1/DoWork,引数:"p1":"test",
終了 アクション名:IService1/DoWork

属性

  • 操作記録は簡易的な実装
LogErrorOperationAttribute.vb
Imports System.Collections.ObjectModel
Imports System.Net
Imports System.ServiceModel
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Description
Imports System.ServiceModel.Dispatcher
Imports System.Web.Services.Description

''' <summary>
''' 集約例外、操作記録のための拡張
''' WCFサービスの属性として設定
''' </summary>
Public Class LogErrorOperationAttribute
    Inherits Attribute
    Implements IServiceBehavior

    Private _errorHandler As LogErrorHandler

    Public Sub New()
        _errorHandler = New LogErrorHandler
    End Sub

    Public Sub Validate(serviceDescription As Description.ServiceDescription, serviceHostBase As ServiceHostBase) Implements IServiceBehavior.Validate
    End Sub

    Public Sub AddBindingParameters(serviceDescription As Description.ServiceDescription, serviceHostBase As ServiceHostBase, endpoints As Collection(Of ServiceEndpoint), bindingParameters As BindingParameterCollection) Implements IServiceBehavior.AddBindingParameters
    End Sub

    Public Sub ApplyDispatchBehavior(serviceDescription As Description.ServiceDescription, serviceHostBase As ServiceHostBase) Implements IServiceBehavior.ApplyDispatchBehavior
        '集約例外
        For Each channelDispatcherBase In serviceHostBase.ChannelDispatchers
            Dim channelDispatcher = CType(channelDispatcherBase, ChannelDispatcher)
            If channelDispatcher IsNot Nothing Then
                channelDispatcher.ErrorHandlers.Add(_errorHandler)
            End If
        Next

        '操作記録
        For Each endpoint In serviceDescription.Endpoints
            For Each operation In endpoint.Contract.Operations
                Dim behavior = New LoggingOperationBehavior()
                operation.Behaviors.Add(behavior)
            Next
        Next
    End Sub
End Class

''' <summary>
''' 集約例外
''' </summary>
Public Class LogErrorHandler
    Implements IErrorHandler

    Public Sub ProvideFault([error] As Exception, version As MessageVersion, ByRef fault As Channels.Message) Implements IErrorHandler.ProvideFault
    End Sub

    Public Function HandleError([error] As Exception) As Boolean Implements IErrorHandler.HandleError
        Debug.Print([error].ToString)
        Return True
    End Function
End Class

''' <summary>
''' 操作記録
''' </summary>
Public Class LoggingOperationBehavior
    Implements IOperationBehavior

    Public Sub Validate(operationDescription As OperationDescription) Implements IOperationBehavior.Validate
    End Sub

    Public Sub ApplyDispatchBehavior(operationDescription As OperationDescription, dispatchOperation As DispatchOperation) Implements IOperationBehavior.ApplyDispatchBehavior
        dispatchOperation.Invoker = New LoggingOperationInvoker(dispatchOperation.Invoker, dispatchOperation)
    End Sub

    Public Sub ApplyClientBehavior(operationDescription As OperationDescription, clientOperation As ClientOperation) Implements IOperationBehavior.ApplyClientBehavior
    End Sub

    Public Sub AddBindingParameters(operationDescription As OperationDescription, bindingParameters As BindingParameterCollection) Implements IOperationBehavior.AddBindingParameters
    End Sub
End Class

''' <summary>
''' 操作記録
''' </summary>
Public Class LoggingOperationInvoker
    Implements IOperationInvoker

    Private _baseInvoker As IOperationInvoker

    ''' <summary>
    ''' 操作のアクション。例:http://tempuri.org/IService1/DoWork
    ''' </summary>
    Private _action As String
    Public Sub New(baseInvoker As IOperationInvoker, operation As DispatchOperation)
        _baseInvoker = baseInvoker
        _action = operation.Action?.Replace("http://tempuri.org/", "")
    End Sub

    Public ReadOnly Property IsSynchronous As Boolean Implements IOperationInvoker.IsSynchronous
        Get
            Return _baseInvoker.IsSynchronous
        End Get
    End Property

    Public Function AllocateInputs() As Object() Implements IOperationInvoker.AllocateInputs
        Return _baseInvoker.AllocateInputs()
    End Function

    Public Function Invoke(instance As Object, inputs() As Object, ByRef outputs() As Object) As Object Implements IOperationInvoker.Invoke
        Try
            Dim argString = GetArgString(inputs)
            Debug.Print($"開始 アクション名:{_action},引数:{argString}")
        Catch ex As Exception
            Debug.Print(ex.ToString())
            Debug.Print($"開始 LOGGING_PARAMETER_EXCEPTION")
        End Try

        Dim result = _baseInvoker.Invoke(instance, inputs, outputs)

        Debug.Print($"終了 アクション名:{_action}")

        Return result
    End Function

    Private Function GetArgString(inputs() As Object) As String
        Dim i = 0

        Dim sb = New StringBuilder()
        For Each item As Object In inputs
            i += 1
            sb.Append($"""p{i}"":")
            If (TypeOf (item) Is String) OrElse (TypeOf (item) Is DateTime) Then
                sb.Append($"""{item}""")
            ElseIf item Is Nothing Then
                sb.Append($"Nothing")
            Else
                Dim aryPublicProperties = item.GetType()?.GetProperties()

                If aryPublicProperties Is Nothing Then
                    sb.Append($"""{item}""")
                Else
                    sb.Append($"""{item}"":{{")
                    For Each p In aryPublicProperties
                        sb.Append($"""{p.Name}"":""{p.GetValue(item, Nothing)}"",")
                    Next
                End If
            End If
            sb.Append(",")
        Next

        Return sb.ToString()
    End Function

    Public Function InvokeBegin(instance As Object, inputs() As Object, callback As AsyncCallback, state As Object) As IAsyncResult Implements IOperationInvoker.InvokeBegin
        Return _baseInvoker.InvokeBegin(instance, inputs, callback, state)
    End Function

    Public Function InvokeEnd(instance As Object, ByRef outputs() As Object, result As IAsyncResult) As Object Implements IOperationInvoker.InvokeEnd
        Return _baseInvoker.InvokeEnd(instance, outputs, result)
    End Function
End Class

参考

https://learn.microsoft.com/ja-jp/dotnet/api/system.servicemodel.description.iservicebehavior?view=netframework-4.8

https://tnakamura.hatenablog.com/entry/20090904/error_handler

https://stackoverflow.com/questions/36419810/iservicebehavior-prevent-applying-message-inspectors-for-some-messages

Discussion

ログインするとコメントできます