WCF で集約例外ハンドラを実装する

WCF サービス内部で発生した例外は、1ヶ所でまとめてハンドルして、ログ出力などのエラー処理を行いたいですよね。

WCF では catch されなかった例外をハンドルするために、IErrorHandler インタフェースが提供されています。このインタフェースを実装したクラスを、ChannelDispatcher クラスの ErrorHandler プロパティに設定してやる事で、例外をまとめてハンドル可能になります。

文章で説明するよりも、コードを見た方が分かり易いかな。

まずはカスタム ErrorHandler を作成します。

public class LogErrorHandler : IErrorHandler
{
    public bool HandleError(Exception error)
    {
        // TODO: ここにログを出力するコードを記述する
        Console.WriteLine(error.Message);
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        // 今回は何もしない。
        // 本当は、ここで fault にフォールトメッセージを設定します。
    }
}

次に、IErrorHandler 実装クラスをエラーハンドラとして登録するために、ServiceBehavior を作成します。

public sealed class LogErrorHandlerBehaviorAttribute : Attribute, IServiceBehavior
{
    // エラーハンドラ
    private LogErrorHandler _errorHandler;

    public LogErrorHandlerBehaviorAttribute()
    {
        _errorHandler = new LogErrorHandler();
    }

    public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
    {
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        // 作成したカスタム ErrorHandler を設定
        foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
        {
            ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
            if (channelDispatcher != null)
            {
                channelDispatcher.ErrorHandlers.Add(_errorHandler);
            }
        }
    }
}

あとは、作成したカスタム ServiceBehavior を、サービスクラスに指定すれば OK。

[ServiceContract]
public interface IFooService
{
    [OperationContract]
    void Foo();
}

[LogErrorHandlerBehaviorAttribute]
public class FooService : IFooService
{
    public void Foo()
    {
        // わざと例外を投げる
        throw new Exception("Test");
    }

}