C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2007/02/04 22:41

[ WCFにおける例外 ]

WCFにおけるアプリケーション固有のエラーは、全てFaultException クラスまたはFaultException ジェネリック クラスとしてクライアントに渡されます。
サービスコントラクトを開発する際は、これらの例外を明示的にスローすることで、クライアントに例外を通知します。
それ以外の例外がスローされた場合、その例外の情報はクライアントには隠蔽され、代わりにFaultExceptionが渡されます。 ( 詳しくは後述 )


[ FaultException クラス ]
FaultException クラス (System.ServiceModel) を明示的にスローすることで、例外メッセージや例外コード ( 例外の原因を識別するためのコード ) をクライアントに通知することができます。

例外メッセージは、コンストラクタの引数 FaultReason にて指定します。このFaultReasonには、直接文字列を指定することも、FaultReason クラスを指定することもできます。FaultReason クラスを使用すると、クライアントのカルチャに応じたメッセージを渡すことができます。 ( 詳しくは後述 )
引数 FaultReason に指定された例外メッセージは、通常の例外と同じく、Message プロパティから取得することができます。また、Reason プロパティからFaultReason型のオブジェクトとして取得することもできます。

例外コードは、コンストラクタの引数 FaultCode にて指定します。このFaultCodeの型は、FaultCode クラス です。 ( 詳しくは後述 )
引数 FaultCode に指定された例外コードは、Code プロパティからFaultCode型のオブジェクトとして取得することができます。


[ FaultReason クラス ]
FaultReason クラス (System.ServiceModel) には、各カルチャ用のメッセージを用意して格納することが可能です。以下の例のように、複数のカルチャ用にメッセージを用意してやることで、例外を受け取るクライアントでは、クライアントのカルチャに応じたメッセージをMessageプロパティから取得することができます。

サービスコントラクト// using System;
// using System.Collections.Generic;
// using System.Globarization;
// using System.ServiceModel;

public void MyOperation1()
{
    FaultReasonText reasonTextJA = new FaultReasonText("ほげ ふが ぴよ", new CultureInfo("ja-JP"));
    FaultReasonText reasonTextEN = new FaultReasonText("hoge fuga piyo", new CultureInfo("en-US"));
    FaultReasonText[] translations = new FaultReasonText[] { reasonTextJA, reasonTextEN };
    FaultReason reason = new FaultReason(translations);
    FaultException ex = new FaultException(reason);
    throw ex;
}

こうやってスローされたFaultExceptionは、クライアントでは、カレントカルチャに従って適切なメッセージを提供します。以下の例のようにカレントカルチャを明示的に指定してみると簡単に確認できます。

クライアント// using System;
// using System.Globarization;
// using System.ServiceModel;
// using System.Threading.Thread;

using (MyServiceClient myServiceClient = new MyServiceClient())
{
    try
    {
        myServiceClient.MyOperation1();
    }
    catch (FaultException ex)
    {
        Thread.CurrentThread.CurrentCulture = new CultureInfo("ja-JP")
        Console.WriteLine(ex.Message); // ほげ ふが ぴよ
        Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US")
        Console.WriteLine(ex.Message); // hoge fuga piyo
    }
}


[ FaultCode クラス ]
FaultCode クラス (System.ServiceModel) は、例外の原因を識別するためのコードを表します。
このクラスのインスタンスは、FaultCode クラスの静的メソッドを使用して取得するか、コンストラクタを使用して取得することができます。

・静的メソッド
例外の原因が要求側 ( クライアント ) または受信側 ( サーバー ) にあることを示す例外コードを生成する場合、FaultCode クラスの静的メソッドを使用します。
FaultCode クラスの静的メソッドには、次の2種類があります。

FaultCode.CreateSenderFaultCode メソッド
例外の原因が要求側 ( クライアント ) にあることを示すFaultCodeオブジェクトを生成します。このメソッドによって生成されたFaultCodeオブジェクトのIsSenderFault プロパティは、trueを返します。

FaultCode.CreateReceiverFaultCode メソッド
例外の原因が受信側 ( サーバー ) にあることを示すFaultCodeオブジェクトを生成します。このメソッドによって生成されたFaultCodeオブジェクトのIsReceiverFault プロパティ は、trueを返します。

これらの静的メソッドは、引数にサブコードを ( FaultCode型、または文字列で ) 指定することができます。特に必要ない場合は、nullを指定します。

・コンストラクタ
独自のコードを作成する場合、FaultCode クラスのコンストラクタを使用します。
コンストラクタを使用する場合、引数 name に独自のコードを文字列で指定します。

コンストラクタによって生成されたFaultCodeオブジェクトのIsSenderFault プロパティとIsReceiverFault プロパティは、falseを返します。

コンストラクタの場合も、サブコードを指定することが可能です。特に必要ない場合は、サブコードを引数として取らないコンストラクタを使用するか、引数 subCode にnullを指定します。


[ FaultException ジェネリック クラス ]
FaultException ジェネリック クラス (System.ServiceModel)FaultException クラスの派生クラスです。このクラスを明示的にスローすることで、例外メッセージや例外コードに加え、例外の詳細をクライアントに通知することができます。

FaultException ジェネリック クラスのジェネリック型パラメータ TDetail に指定された型は、Detail プロパティの型になります。クライアントは、このDetail プロパティを通じて例外の詳細を知ることができます。ただしそのためには、以下の例のようにFaultException ジェネリック クラスをスローする可能性のあるオペレーションコントラクトに、FaultContractAttribute クラス (System.ServiceModel) を属性として付加し、引数 detailType を指定する必要があります。
[ServiceContract]
public interface IMyService
{
    [OperationContract]
    [FaultContract(typeof(MyDetail))]
    void MyOperation1();
}

ジェネリック型パラメータ TDetail には、ユーザー定義の型を指定することもできます。その場合、ユーザー定義の型は以下の例のようにデータコントラクトとして宣言する必要があります。
// using System;
// using System.ServiceModel;

[DataContract]
public class MyDetail
{
    private string message;

    [DataMember]
    public string Message
    {
        get
        {
            return this.message;
        }
        set
        {
            this.message = value;
        }
    }

    public MyDetail(string message)
    {
        this.message = message;
    }
}

これらを適切に行うことで、クライアント側でもFaultException ジェネリック クラスを適切に利用することが可能になります。

なお、ジェネリック型パラメータ TDetail にExceptionDetail クラス (System.ServiceModel) を指定したり、Exception クラスまたはその派生クラスを指定することはしてはいけません。WCFサービス内で発生した例外情報を直接クライアントに公開することはセキュリティ的によろしくないですし、スタックトレースなどの例外情報はクライアント側には不要のはずです。FaultException クラスのコンストラクタにInnerExceptionを指定することができないのも、同様の理由からでしょう。


[ WCFでは例外情報を無闇に公開しない ]
WCFでは通常、セキュリティの観点から、FaultException クラスまたはFaultException ジェネリック クラス以外のハンドルされなかった例外の情報は、クライアントに一切渡さず、代わりに、ハンドルされなかった例外が発生したことを示すFaultException 例外オブジェクトを渡します。

ただし、これではデバッグ時に不便である場合があるため、例外の情報をクライアントに渡すように設定することもできます。
プロジェクトを、クラスライブラリではなく Webサービスとして作成した場合の初期設定では、例外の情報をクライアントに渡すように設定されているので注意します。

[ system.serviceModel ] - [ behaviors ] - [ serviceBehaviors ] - [ behavior ] - [ serviceDebug ] セクションのincludeExceptionDetailInFaults属性が、ハンドルされなかった例外の情報をクライアントに渡すように設定するための属性です。
この属性にtrueが設定されている場合、FaultException クラスまたはFaultException ジェネリック クラス以外のハンドルされなかった例外はFaultException ジェネリック クラスとしてスローされます。この時、ジェネリック型パラメータ TDetail はExceptionDetail クラス (System.ServiceModel) となります。クライアントでは、FaultException ジェネリック クラスのDetailプロパティにて取得したExceptionDetailオブジェクトから、ハンドルされなかった例外の情報を詳しく調べることができます。

以下に、設定例を示します。
<system.serviceModel>
    <services>
        <service name="MyService" behaviorConfiguration="MyService_ServiceBehaviors">
            <endpoint contract="IMyService" binding="wsHttpBinding"/>
        </service>
    </services>
    <behaviors>
        <serviceBehaviors>
            <behavior name="MyService_ServiceBehaviors" >
                <serviceDebug includeExceptionDetailInFaults="true" />
                <serviceMetadata httpGetEnabled="true"/>
            </behavior>
        </serviceBehaviors>
    </behaviors>
</system.serviceModel>


[ 例外の通信 ]
WCFサービス内でハンドルされなかった例外は、SOAPメッセージに変換され、クライアントへと渡されます。
この時、ハンドルされなかった例外がFaultException クラス、またはFaultException ジェネリック クラスなら、以下の情報がSOAPメッセージの中に記述されます。
  • Reason プロパティ
  • Code プロパティ
  • Action プロパティ
  • Detail プロパティ ( FaultException ジェネリック クラスの場合 )

ハンドルされなかった例外が、FaultException クラス、またはFaultException ジェネリック クラスでない場合、serviceDebug セクションのincludeExceptionDetailInFaults 属性の値に応じて次のような処理が行われます。

・falseの場合
ハンドルされなかった例外情報はSOAPメッセージの中には記述されません。代わりに、ハンドルされなかった例外が発生したことを示すFaultException例外オブジェクトが生成・スローされ、その例外情報がSOAPメッセージの中に記述されます。

・trueの場合
ハンドルされなかった例外情報を元にFaultException<ExceptionDetail>例外オブジェクトが生成・スローされ、その例外情報がSOAPメッセージの中に記述されます。

こうやってできあがったSOAPメッセージは、クライアントへと渡されます。
このSOAPメッセージを受け取ったクライアントは、SOAPメッセージを解析し、新たにFaultException クラスまたはFaultException ジェネリック クラスを生成し、スローします。

つまり、クライアントでハンドルしたFaultException クラスの例外オブジェクトまたはFaultException ジェネリック クラスの例外オブジェクトは、クライアントで生成・スローされたことになります。よって、StackTraceプロパティなどに格納されている情報はクライアントでスローされた時の情報であり、サーバーの例外情報ではありません。先にも述べたように、サーバーから渡される例外情報は、Reason プロパティ, Code プロパティ, Action プロパティ, Detail プロパティのみです。


[ まとめ ]
WCFの例外処理において、以下の3点には特に注意する必要があります。
  • FaultException ジェネリック クラスをスローする可能性のあるオペレーションコントラクトでは、FaultContractAttribute属性を付加します。
  • FaultException ジェネリック クラスの型引数にExceptionDetail クラスやException クラスを指定して、WCFサービス内で発生した例外の詳細をそのまま公開してはいけません。
  • 運用時にはserviceDebug セクションのincludeExceptionDetailInFaults 属性の値はfalseにします。
タグ: .NET C# WCF 例外処理











トラックバックURL↓
http://csharper.blog57.fc2.com/tb.php/73-6d4d1e2f

WCF サーバーアプリケーションで発生した例外の通知
WCF ではサーバー側で発生した例外の通知に FaultException とい...

2008.05.07 23:35 | track4 labo.