今回は、エラーコントラクトについて。
WCFでは、サービス側にて処理中にエラーが発生した場合
クライアント側に知らせるために、エラーコントラクトと
仕組みが用意されています。
エラーコントラクトは以下の手順で設定します。
- エラー内容が保持されるユーザ定義クラスを作成
- 作成したクラスにDataContract, DataMember属性を付与
- サービスメソッドにて、FaultContract属性を付与
例)
[FaultContract(typeof(MyFaultData))]
これでそのサービスメソッド内にてエラーが発生した際に
サーバー側で
throw new FaultException<MyFaultData>(new MyFaultData { Message = "hogehoge" });
のようにして、例外をスローするとクライアント側に通知されます。
では、サービスメソッドにFaultContractを付与せずにメソッド内から
例外をスローするとどうなるのか?というと
クライアント側では、例外は通知されますが詳細なエラー内容は取得できません。
例外のエラーメッセージに
内部エラーのため、クライアントは要求を処理できませんでした。・・・・
と設定された状態で例外がキャッチできます。
これは、デフォルトの設定で例外発生時にエラーコントラクト以外の場合
詳細情報を通知しないように設定されているからです。
サービス側の設定ファイルにて
<behaviors> <serviceBehaviors> <behavior name="MyServiceBehavior"> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior> </serviceBehaviors> </behaviors>
とサービスビヘイビアを設定しておくと、クライアント側にも
ちゃんと例外情報が通知されます。
ただし、これは開発時のみに利用するべきオプションですのでご注意を。
以下、サンプルです。
サンプルでは、サービスメソッドを2つ定義し
一つはFaultContractを付与したクラス。もう一つはFaultContractを付与していないクラスを
利用しています。
まず、サービス定義。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.Runtime.Serialization; namespace Gsf.Samples.WCF { [ServiceContract] public interface IMyService { // 例外発生時にFaultException<MyFaultData>がスローされることを示す. [OperationContract] [FaultContract(typeof(MyFaultData))] bool ThrowFaultException(); // FaultContract属性を付与していないサービスメソッド. [OperationContract] bool ThrowFaultException2(); } }
エラーコントラクトにて利用するクラス定義.
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; namespace Gsf.Samples.WCF { // エラーコントラクト. [DataContract] public class MyFaultData { [DataMember] public string Message { get; set; } } }
using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; namespace Gsf.Samples.WCF { public class MyService : IMyService { public bool ThrowFaultException() { try { // 何かの処理を行っている途中で例外が発生したとする. throw new Exception("Dummy Exception."); } catch (Exception ex) { // // エラーコントラクトを構築し // FaultExceptionに包み直してre-throw. // throw new FaultException<MyFaultData>( new MyFaultData { Message = ex.Message } ,new FaultReason("例外発生確認のためのテスト") ); } } // サービスインターフェース側でFaultContract属性を付与していないが // 例外が発生するメソッド. public bool ThrowFaultException2() { throw new Exception("Dummy Exception 2."); } } }
次にホストアプリ。
using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; namespace Gsf.Samples.WCF { class Program { static void Main() { using (var host = new ServiceHost(typeof(MyService))) { host.Open(); Console.WriteLine("サービスが開始されました。"); Console.WriteLine("press <ENTER> to exit..."); Console.ReadLine(); host.Close(); } } } }
ホスト側のアプリケーション構成ファイル。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="Gsf.Samples.WCF.MyService"> <host> <baseAddresses> <add baseAddress="http://localhost:8089/WCFSample009/MyService"/> </baseAddresses> </host> <endpoint address="" binding="basicHttpBinding" contract="Gsf.Samples.WCF.IMyService" /> </service> </services> <!-- 以下のコメントを外してサービス側にbehaviorConfigurationで設定すると FaultContractを付与していないサービスメソッドから例外が発生しても クライアント側で詳細情報を取得することができる。 (開発時に利用すると便利) --> <!--<behaviors> <serviceBehaviors> <behavior name="MyServiceBehavior"> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior> </serviceBehaviors> </behaviors>--> </system.serviceModel> </configuration>
次にクライアントアプリ。
クライアント側では結果を表示するラベルが2つ配置してあり
それぞれの結果を表示してくれます。
(一つ目の実行結果が上のラベルに、2つ目の実行結果が下のラベルに表示されます。)
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using System.ServiceModel; namespace Gsf.Samples.WCF { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { lblResult.ResetText(); lblResult2.ResetText(); ActiveControl = btnCallServiceMethod; } private void btnCallServiceMethod_Click(object sender, EventArgs e) { using (var client = new MyServiceRef.MyServiceClient()) { try { // // サービス側でFaultContract属性が付与されているサービスメソッド // なので、FaultExceptionがスローされてくる。 // // FaultException.Detailにサービス側で設定されたエラーコントラクトが // 設定されているので、その情報を元にエラー処理を行う。 // client.ThrowFaultException(); } catch (FaultException<MyServiceRef.MyFaultData> ex) { lblResult.Text = ex.Detail.Message; } try { // // このメソッドではFaultContractの属性を付与していない. // なので、例外がサーバー側で発生した際にデフォルトでは情報が取得できない。 // (例外はキャッチできる。) // // 例外の詳細内容をFaultContractの指定なしで取得するには // サービス側にて // // <behaviors> // <serviceBehaviors> // <behavior name="MyServiceBehavior"> // <serviceDebug includeExceptionDetailInFaults="true" /> // </behavior> // </serviceBehaviors> // </behaviors> // // を設定する. // client.ThrowFaultException2(); } catch (Exception ex) { lblResult2.Text = ex.Message; } } } } }
クライアント側のアプリケーション構成ファイル。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> <endpoint address="http://localhost:8089/WCFSample009/MyService" binding="basicHttpBinding" contract="MyServiceRef.IMyService" /> </client> </system.serviceModel> </configuration>
サンプルは以下の場所にアップしてあります。
https://sites.google.com/site/gsfzero1/Home/WCFSample-009.zip?attredirects=0&d=1
================================
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ