WCF では、例外を一元的にハンドリングするための機構が別の方法で提供されています。
Global.asax に比べて、手順がやや面倒となりますが、クライアントにフォールトメッセージを通知することを考慮した仕組みにはなっています。
【 IErrorHandler インターフェイス 】
例外を一元ハンドリングするのは、 IErrorHandler インターフェイス (System.ServiceModel.Dispatcher) を実装した独自のクラスとなります。
このインターフェイスが提供するメソッドは以下の2つです。
[ ProvideFault メソッド ]
ProvideFault メソッド は、サービス内部からスローされた例外をハンドリングします。このメソッド内でクライアントに渡すフォールトメッセージを生成することができます。
ロギングなどの処理はこのメソッドではなく、後述の HandleError メソッドで行います。
このメソッドのパラメータは以下の3つです。
error
型は Exception クラス (System) です。
このパラメータにより、サービス内部からスローされた例外を取得できます。
version
型は MessageVersion クラス (System.ServiceModel.Channels) です。
このパラメータにより、現在の SOAP 通信 における SOAP のバージョンを取得できます。
fault
型は Message クラス (System.ServiceModel.Channels) です。また、このパラメータは参照渡しとなっています。
サービス内部からスローされた例外が、 FaultException クラス (System.ServiceModel) であった場合、それを表わすフォールト メッセージがこのパラメータにより取得できます。サービス内部からスローされた例外が、 FaultException クラスでない場合、このパラメータは null となります。
この ProvideFault メソッド内で Message クラスを生成して、このパラメータに設定することができます。これにより、クライアントに渡すフォールトメッセージを指定することができるわけです。
・ProvideFault メソッドの実装例
FaultException 以外の、ハンドルされなかった例外が発生した場合に、予期せぬエラーが発生したという通知をクライアントに対して行うための実装例を以下に示します。
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (fault != null)
{
return;
}
FaultReasonText[] reasonTexts =
new FaultReasonText[]
{
new FaultReasonText("予期せぬエラーが発生しました。", "ja-JP"),
new FaultReasonText("An unexpected error occurred.", "en-US")
};
FaultReason reason = new FaultReason(reasonTexts);
FaultCode code = FaultCode.CreateReceiverFaultCode("unexpected", "http://schemas.yokoken.net/Hoge");
FaultException faultException = new FaultException(reason, code);
MessageFault messageFault = faultException.CreateMessageFault();
string action = OperationContext.Current.IncomingMessageHeaders.Action;
fault = Message.CreateMessage(version, messageFault, action);
}
[ HandleError メソッド ]
HandleError メソッド は、サービス内部からスローされた例外と、通信時に発生した例外をハンドリングします。
サービス内部からスローされた例外は、ProvideFault メソッドが先にハンドリングします。その後、このメソッドがハンドリングします。ProvideFault メソッドでフォールト メッセージを生成した場合でも、HandleError メソッドでハンドリングされる例外は変わりません。つまり、サービス内部でスローされた例外がハンドリングされます。
ロギングや、MSMQ の有害メッセージ ( Poison Message ) の処理などはこのメソッドで行います。
このメソッドのパラメータは以下の1つです。
error
型は Exception クラス (System) です。
このパラメータにより、サービス内部からスローされた例外や、通信時に発生した例外を取得できます。
・戻り値について
このメソッドの戻り値は bool 型の値です。true を返した場合、例外はハンドルされたものとして扱われ、false を返した場合、例外はハンドルされなかったものとして扱われます。
MSMQ で有害メッセージを処理した等の場合は、ここで true を返します。何も処理しなかったり、ロギングのみを行う場合は false を返します。
( ただ、正直な話、これらの具体的な違いは不明です。色々試してみましたが、 true を返しても false を返しても、挙動の違いは見受けられませんでした。Reflector でコードを覗いてみると、一応特定の条件下において false が返された場合に、チャネルの中止処理を行うようになっているようです。 )
・HandleError メソッドの実装例
FaultException 以外の、ハンドルされなかった例外 ( 通信時の例外含む ) が発生した場合に、例外の情報をコンソールへ出力するための実装例を以下に示します。
public bool HandleError(Exception error)
{
if (error is FaultException)
{
return false;
}
string errorType = error.GetType().FullName;
string errorMessage = error.Message;
string errorStackTrace = error.StackTrace;
Console.WriteLine("[ Error ]\r\nType: {0}\r\nMessage: {1}\r\nStack Trace: {2}", errorType, errorMessage, errorStackTrace);
return false;
}
【 エラー ハンドラの登録 】
[ ChannelDispatcher へ登録 ]
IErrorHandler 実装クラスを定義しただけでは、エラー ハンドラとして動作してくれません。IErrorHandler 実装クラスを登録する処理を実装する必要があります。
登録は、ChannelDispatcher クラス (System.ServiceModel.Dispatcher) のインスタンスの、ErrorHandlers プロパティ に対して行います。このプロパティに格納されているコレクションオブジェクトに対してIErrorHandler 実装クラスのインスタンスを追加してやります。
ServiceHostBase クラス (System.ServiceModel) の ChannelDispatchers プロパティ には、ChannelDispatcherCollection クラス (System.ServiceModel.Dispatcher) のインスタンスが格納されています。このコレクションから、ChannelDispatcher オブジェクトを取得することができます。
このコレクション自体は、ChannelDispatcherBase クラス (System.ServiceModel.Channels) のコレクションとして定義されています。ChannelDispatcherBase クラスは、ChannelDispacher クラスの基本クラスですが、ErrorHandlers プロパティは定義されていません。つまり、ChannelDispacher クラスへのキャストが行えるか検証し、キャストを行う必要があります。 ( ただ、ChannelDispatcherBase クラスは抽象クラスであり、このクラスの派生クラスは ChannelDispacher クラスしかありません。つまり、事実上ChannelDispatcherCollection クラスは ChannelDispacher のコレクションです。しかし、それでもキャストができるかどうかの検証はしっかり行うことを推奨します。 )
[ 登録はサービス ビヘイビアで行う ]
登録処理は独自のビヘイビアで行います。ビヘイビアからではなく、サービス ホストを直接操作して登録処理を行っても、エラー ハンドラは動作してくれません。
ビヘイビアはサービス ビヘイビアまたはエンドポイント ビヘイビアとなります。
独自のビヘイビアを定義する方法については以下の記事を参照してください。
独自のビヘイビアを定義する
独自のビヘイビアを定義する 2
【 実装例 】
エラー ハンドラの実装例と、それを適用するためのサービス ビヘイビアの実装例を以下に示します。
エラー ハンドラ
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
internal sealed class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
if (error is FaultException)
{
return false;
}
string errorType = error.GetType().FullName;
string errorMessage = error.Message;
string errorStackTrace = error.StackTrace;
Console.WriteLine("[ Error ]\r\nType: {0}\r\nMessage: {1}\r\nStack Trace: {2}", errorType, errorMessage, errorStackTrace);
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (fault != null)
{
return;
}
FaultReasonText[] reasonTexts =
new FaultReasonText[]
{
new FaultReasonText("予期せぬエラーが発生しました。", "ja-JP"),
new FaultReasonText("An unexpected error occurred.", "en-US")
};
FaultReason reason = new FaultReason(reasonTexts);
FaultCode code = FaultCode.CreateReceiverFaultCode("unexpected", "http://schemas.yokoken.net/Hoge");
FaultException faultException = new FaultException(reason, code);
MessageFault messageFault = faultException.CreateMessageFault();
string action = OperationContext.Current.IncomingMessageHeaders.Action;
fault = Message.CreateMessage(version, messageFault, action);
}
}
サービス ビヘイビア
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
[AttributeUsage(AttributeTargets.Class)]
public class ErrorHandlerAttribute : Attribute, IServiceBehavior
{
private IErrorHandler errorHandler;
public ErrorHandlerAttribute()
{
this.errorHandler = new ErrorHandler();
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher dispatcher = dispatcherBase as ChannelDispatcher;
if (dispatcher == null)
{
continue;
}
dispatcher.ErrorHandlers.Add(this.errorHandler);
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
これで、サービス クラスに ErrorHandlerAttribute クラスを属性として付加すれば、サービスにエラー ハンドラが適用されます。