いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

WCF入門-009 (基本的なサンプル, エラーコントラクト)


今回は、エラーコントラクトについて。


WCFでは、サービス側にて処理中にエラーが発生した場合
クライアント側に知らせるために、エラーコントラクトと
仕組みが用意されています。


エラーコントラクトは以下の手順で設定します。

  1. エラー内容が保持されるユーザ定義クラスを作成
  2. 作成したクラスにDataContract, DataMember属性を付与
  3. サービスメソッドにて、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


================================
過去の記事については、以下のページからご参照下さい。