動作確認環境は次のとおり
- OS: Vista Enterprise
- IDE:Visual Studio 2008 Professional
- .NET 3.5
Visual Studio のデバッグモードで確認しています。
1.WCFサービスの作成
Windows Form Application作成
ソリューション名がWCFSample004のソリューションを作成し、WCFサービス用にWindows From Applicationを作成します。WCFSampe002で作成したプログラムFrom1.cs,designer,resx,IProductService,Product,ProductService.cs,app.configをコピーし、プロジェクトに追加します。ネームスペースをWCFSample002からWCFSample004に変更します。変更方法はWCFSample002を参考にしてください。 System.ServiceModel.dll,System.Runtime.Serialization,System.Confuguration.dllのアセンブリを参照に追加します。 この時点で実行の確認できることを確認しておきます。
app.configのWCFSample002の部分はネームスペースをリネームダイアログで変換しても自動で変換されないので、WCFSample004に書き換えることを忘れないで下さい。
2.クライアントプログラムの作成
svcutilを使用したプロキシクラスの作成
今回はsvcutil.exeを使用してメタデータをサービスのエンドポイントから取得せずにプロキシクラスを作成します。Visual Studio2008コンソールを起動し、WCFサービスをビルドして実行ファイルが作成されたパスまで移動し、次のコマンドを入力しますしWCFサービスのメタ情報ファイルを作成します。
svcutil WCFSample004.exe

次に、メタ情報ファイルから、プロキシクラスを作成します。
svcutil /namespace:*,WCFSample004.Client tempuri.org.wsdl *.xsd

tempuri.orgはデフォルトのネームスペースです。作成されるネームスペース名は[ServiceContract]アトリビュートで変更することができます。output.configも作成されますが、使用しない情報が入っているので、クライアントプログラムのApp.configは手動で作成します。
クライアントプロジェクトの作成
WCFSample004.Clientというプロジェクト名でコンソールアプリケーションを作成します。プロジェクトに作成されたプロキシクラスschemas.microsoft.com.2003.10.Serialization.Arrays.csを追加します。参照にSystem.ServiceModel.dll,System.Runtime.Serialization.dllを追加します。
Program.csとApp.configの編集
App.configはWCFSample002で作成した内容と同じです。ファイルの中身を記載します。app.configは次のとおりです。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint name="TcpProductEndpoint" address="net.tcp://localhost:8080/ProductService"
binding="netTcpBinding" contract="WCFSample004.Client.IProductService" />
</client>
</system.serviceModel>
</configuration>
Program.cs次のとおり。WCFサービスの例外は何もしない場合、FaultExceptionとしてスローされます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace WCFSample004.Client
{
class Program
{
static void Main(string[] args)
{
try
{
System.Console.WriteLine("WCFサービスホストを起動したら、キーを入力して下さい。");
System.Console.Read();
ProductServiceClient proxy = new ProductServiceClient("TcpProductEndpoint");
int[] productIDs = proxy.GetProductIDs();
foreach (int productID in productIDs)
{
Console.WriteLine("Product ID: " + productID);
Product product = proxy.GetProductByID(productID);
Console.WriteLine("Name: " + product.Name);
Console.WriteLine("Color: " + product.Color);
Console.WriteLine("ListPrice: " + product.ListPrice);
}
proxy.Close();
}
catch (FaultException e)
{
Console.WriteLine("Error Code:" + e.Code.Name);
Console.WriteLine("Error Reason:" + e.Reason);
}
}
}
}
ソリューションをビルドして、動作することを確認します。下図のように、WCFサービスと、クライアントが同時に起動するようにソリューションのプロパティを編集しておくのを忘れないで下さい。

3.エラーを発生させる。
WCFサービスのapp.configを編集してわざとエラーを発生させます。次の例ではconnectionStringsを変更してサーバでエラーを出すようにします。add要素の属性nameをnamにしてサーバを実行。クライアントを実行します。
<connectionStrings>
<add nam="AdventureWorksConnection" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks;User ID=【ユーザID】;Password=【パスワード】"/>
</connectionStrings>
実行するとクライアントで次のようなエラーが表示されます。

エラー原因がわかりにくい、エラーメッセージが表示されてしまいます。
WCFサービスで発生したエラーをクライアントに伝播させる
WCFサービスの設定を変更することで、サービスで発生した例外をクライアントに伝播させることができるようになります。
[ServiceBehavior(IncludeExceptionDetailInFaults=true)]
public class ProductService : IProductService
{
...
プログラムを実行するとエラーメッセージが表示されます。FaultExceptionとして明示的に例外をスローしない場合、FaultException.Code.NameはReceiverになることを確認して下さい。

WCFサービスでFaultExceptionをサービスで明示的にスローして発生した例外をクライアントでキャッチする
WCFサービスのProductService.csを変更し、CreateConnection()メソッドを変更してFaultExceptionをスローするようにします。
private DbConnection CreateConnection()
{
try
{
return new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["AdventureWorksConnection"].ConnectionString);
}
catch (Exception)
{
throw new FaultException("Exception Occured:CreateConnection", new FaultCode("CreateConnectoinError"));
}
}
クライアントプログラムを実行するとエラーメッセージが表示されます。FaultException.Code.NameはCreateConnectionの例外時に指定したCreateConnectoinErrorとなっていることが確認できます。

型付FaultException<T>をスローする
最後に、型付のFaultExceptionを実装します。FaultExceptionだと、すべての例外をFaultExceptionをキャッチして処理しなければなりませんが、型付FaultExceptionを処理できるようにすることで、それぞれの例外に適した例外処理を行いやすくなります。WCFサービスのプロジェクトでエラーの詳細をクライアントに伝えるためのクラスErrorInfoを作成します。
namespace WCFSample004
{
[DataContract]
public class ErrorInfo
{
[DataMember]
public string Operation;
[DataMember]
public string ErrorReason;
}
}
IProductServiceの各サービスメソッドにFaultContractAttributeを追加します。
namespace WCFSample004.Production
{
// NOTE: If you change the interface name "IProductService" here, you must also update the reference to "IProductService" in App.config.
[ServiceContract]
public interface IProductService
{
[FaultContract(typeof(ErrorInfo))]
[OperationContract]
List<int> GetProductIDs();
[FaultContract(typeof(ErrorInfo))]
[OperationContract]
Product GetProductByID(int productID);
[FaultContract(typeof(ErrorInfo))]
[OperationContract]
bool ChangeListPrice(int productID, decimal newListPrice);
}
}
ProductService.csのCreateConnection()メソッドを修正して、FaultException<ErrorInfo>をスローするようにします。
private DbConnection CreateConnection()
{
try
{
return new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["AdventureWorksConnection"].ConnectionString);
}
catch (Exception)
{
ErrorInfo info = new ErrorInfo();
info.ErrorReason = "ConfigError";
info.Operation = "CreateConnection";
throw new FaultException<ErrorInfo>(info, "ErrorReason");
}
}
WCFサービスプログラムをビルドし、プロキシクラスを再作成してした後、クライアントプログラムを次のように修正します。
static void Main(string[] args)
{
try
{
System.Console.WriteLine("WCFサービスホストを起動したら、キーを入力して下さい。");
System.Console.Read();
ProductServiceClient proxy = new ProductServiceClient("TcpProductEndpoint");
int[] productIDs = proxy.GetProductIDs();
foreach (int productID in productIDs)
{
Console.WriteLine("Product ID: " + productID);
Product product = proxy.GetProductByID(productID);
Console.WriteLine("Name: " + product.Name);
Console.WriteLine("Color: " + product.Color);
Console.WriteLine("ListPrice: " + product.ListPrice);
}
proxy.Close();
}
catch (FaultException<ErrorInfo> ei)
{
Console.WriteLine("ErrorInfo-Operation:" + ei.Detail.Operation);
Console.WriteLine("ErrorInfo-Reason:" + ei.Detail.ErrorReason);
}
catch (FaultException e)
{
Console.WriteLine("Error Code:" + e.Code.Name);
Console.WriteLine("Error Reason:" + e.Reason);
}
}
実行結果は次のとおりです。
