サンプル2~WCFとの連携
サンプル1はスタンドアロンでワークフローを実行していましたが、やはりワークフローを実際に使う場合は、アプリケーションサーバ上でワークフローをホスティングし、クライアントからワークフローをサービスとして呼び出す、という形態を取る場合が多いでしょう。
では、続いてWFとWCFを連携させるサンプルを実装してみます。
.NET Framework 3.5では、ワークフローをWCFサービスとして公開することができます。クライアントは通常のWCFサービスを呼び出す場合と同じコードでワークフローを呼び出すことができ、実際のWCFサービスがワークフローとして実装されていることを意識する必要はありません。
このサンプルはプロジェクトの数が多いので、最初にまとめておきます。
プロジェクト名 | 概要 |
WcfServiceLibrary | WCFサービスの定義を行う。サービス コントラクトであるIService1.csを含む。 |
WorkflowLibrary | ワークフロー定義を行う。WcfServiceLibraryを参照。 |
HostingApplication | WCFサービスとしてワークフローを公開するホスティングアプリケション。WcfServiceLibrary/WorkflowLibraryを参照。 |
WorkflowClient | WCFサービスを呼び出すクライアントアプリケーション。WcfServiceLibraryを参照。 |
以下の手順で実装を進めていきます。
- WCFサービスコントラクトを作成する(WcfServiceLibraryプロジェクト)
- サービス コントラクトをワークフローで実装する(WorkflowLibraryプロジェクト)
- ワークフローをWCFサービスとして公開する(HostingApplicationプロジェクト)
- WCFサービスを呼び出す(WorkflowClientプロジェクト)
WCFサービスコントラクトを作成する(WcfServiceLibraryプロジェクト)
最初にWCFのサービス コントラクトを作成します。プロジェクトの新規作成で[WCF]-[WCFサービス ライブラリ]を選択してプロジェクトを作成します。今回はプロジェクト名をWcfServiceLibrary、ソリューション名をWFWCFTestとしました。
サービス コントラクトとして「IService1.cs」が、サービス実装として「Service1.cs」が自動生成されます。今回はサービス実装はワークフローとして別プロジェクトで行いますので、ここでは「Service1.cs」を削除し、「IService1.cs」を変更します。
以下の内容でサービスコントラクトを作成します。お気に入りURLを登録/取得できるブックマークサービスのイメージです。
namespace WcfServiceLibrary { [ServiceContract] public interface IService1 { [OperationContract] void AddFavorite(string url); [OperationContract] string[] GetFavorites(); } }
URLを登録するAddFavorite
メソッドと、登録したURL一覧を取得するGetFavorites
メソッドをコントラクトとして登録します。
サービス コントラクトをワークフローで実装する(WorkflowLibraryプロジェクト)
次に、コントラクトを実装するためのステートマシン ワークフローを作成します。プロジェクトの新規作成で[Visual C#]-[Workflow]-[ステートマシンのワークフロー ライブラリ]を選択してWorkflowLibraryプロジェクトを作成します。
ステートマシン ワークフローはイベント駆動によってステートが変化していくワークフローです。
WFとWCFの統合により、Receiveアクティビティを使うことで、WCFの呼び出しをWFのイベントとして受け付けることができるようになりました。それで今回は
- 初期状態:AddFavorite呼び出しイベント → URL登録・初期状態へ遷移
- 初期状態:GetFavorite呼び出しイベント → URLリスト出力・終了状態へ遷移
という2つの状態と2つのイベントを持つワークフローを作成しましょう。ここでのイベントは、後で実装するクライアントからのWCFサービスの呼び出しのタイミングで発生することになります。
最初にWCFサービスコントラクトがあるWcfServiceLibraryへの参照を追加しておきましょう。[プロジェクト]-[参照の追加]から[プロジェクト]-[WcfServiceLibrary]を選択します。
ここでいったんビルドし、WCFサービスコントラクトをワークフローから参照できるようにします。
さて、プロジェクト作成直後、ワークフロー上には状態を表すStateアクティビティが1つだけ配置されていますので、もう一つStateアクティビティを配置し、右クリックして[完了済の状態として設定]を選び、終了状態とします。名前はendStateActivityとします。
まずはAddFavorite
メソッドの呼び出しイベントをハンドリングしてみましょう。
左上のWorkflow1InitialStateアクティビティを右クリックし、[EventDrivenの追加]を選ぶと、イベントを受け付けるためのEventDrivenアクティビティが配置されます。EventDrivenアクティビティは子階層を持つコンポジット アクティビティですので、自動的に子階層の設定画面に切り替わります。
WCFサービス呼び出しを受信するためのReceiveアクティビティを配置し、さらに子供として実装を行うためのCodeアクティビティを配置します。
なお、赤い!マークが表示されているのはプロパティが未設定でアクティビティの状態がエラーとなっているためです。
まず、ここで受け付けるWCFサービスを選択しましょう。
ReceiveアクティビティのプロパティペインでServiceOperationInfo
プロパティ右端の[...]ボタンをクリックします。[操作の選択]画面で[インポート]をクリックし、[.NET型の参照と選択]画面で[参照アセンブリ]-[WcfServiceLibrary]-[IService1]を選択して[OK]ボタンをクリックします。
[操作の選択]画面に[使用可能な操作]としてIServiceで定義したメソッドが表示されているので、AddFavorite
メソッドを選択し、[OK]ボタンをクリックします。
これでAddFavorite
メソッド呼び出し時に、イベントをハンドリングできるようになりました。次にメソッドの引数を取り出し、実装を行いましょう。
Receiveアクティビティのプロパティペインに、urlという名前の欄が新しく追加されています。これは、AddFavorite
メソッドのurl引数を表しています。右端の[...]ボタンをクリックし、この引数をどのように扱うかを選択します。
[新しいメンバへのバインド]タブの[プロパティの作成]を選択して[OK]ボタンをクリックします。
これで、AddFavorite
メソッドの引数urlの値は、receiveActivity1_url1
というプロパティに設定されるようになります。
もう一つ、CanCreateInstance
プロパティをTrueとしておきましょう。これは、AddFavorite
メソッドが呼ばれたときに、このワークフローをインスタンス化するためのプロパティです。
次に、Codeアクティビティをダブルクリックし、次のようにコードを実装します。
using System.Collections.Generic; //ファイル先頭のusing文に追加 List<string> favorites = new List<string>(); //URLを管理するためのリスト private void codeActivity1_ExecuteCode(object sender, EventArgs e) { favorites.Add(receiveActivity1_url1); //リストにURLを追加 }
先ほど作成したreceiveActivity1_url1
プロパティから値を取得していることに注目してください。保存先はList
クラスとしました。実サービスではDBなどに格納することになるでしょう。
以上の手順でAddFavorite
メソッドのハンドリングは完了です。
同様にGetFavorites
メソッドもハンドリングしてみましょう。ワークフローデザイン画面の左上の[Workflow1]をクリックし、ワークフロー全体画面に戻ります。左上のWorkflow1InitialStateアクティビティを右クリックし、もう一回[EventDrivenの追加]を選択します。
先ほどと同様にReceiveアクティビティ、Codeアクティビティを配置し、最後に状態遷移を行うためのSetStateアクティビティを配置しましょう。
ReceiveアクティビティのServiceOperationInfo
プロパティの設定を開きます。先ほどインポートしたコントラクト(WcfServiceLibrary.IService1)が選択されていますので、今度はGetFavorites
メソッドを選択します。
GetFavorites
メソッドは引数を持ちませんが、string型の戻り値を返す必要があります。戻り値も引数と同様に、プロパティにバインドしましょう。今度は(ReturnValue)という名前の欄が作成されていますので、(ReturnValue)の右のオレンジの部分をダブルクリックしましょう。
先ほどと同様に[新しいメンバへのバインド]タブの[プロパティの作成]を選択して[OK]ボタンをクリックします。これで、GetFavorites
メソッドの戻り値は、receiveActivity2__ReturnValue_1
というプロパティから取得されるようになります。
後は、Codeアクティビティを実装するだけです。ダブルクリックして次のようにコードを実装します。
private void codeActivity2_ExecuteCode(object sender, EventArgs e) { receiveActivity2__ReturnValue_1 = favorites.ToArray(); }
List型に格納していたURL一覧をstring配列に変換し、先ほどのプロパティに設定しています。
最後に、終了状態への遷移を行います。SetStateアクティビティのプロパティペインからTargetStateName
プロパティを選択し、状態一覧から終了状態であるendStateActivityを選択します。
左上の[Workflow1]をクリックして全体図に戻ると、状態の間に遷移の矢印が追加されています。これは、SetStateアクティビティによる状態遷移が検出されているためです。
以上でワークフローによるWCFサービスの実装は完了です。
ワークフローはWCFのコントラクトを直接継承するのではなく、Receiveアクティビティを介してイベントとして受け取る、という点に注意してください。ワークフロー内ではWCFのサービスコントラクトで宣言したメソッドを直接実装するのではなく、Receiveアクティビティでメソッドをハンドリングし、引数や戻り値に対応するプロパティを通して、適切な実装を行います。
ワークフローをWCFサービスとして公開する(HostingApplicationプロジェクト)
次に、ワークフローをホスティングするアプリケーションを作成しましょう。
プロジェクトの新規作成で[コンソール アプリケーション]を選択してHostingApplicationプロジェクトを作成します。
作成後、以下のプロジェクトへの参照を追加します。
- WcfServiceLibrary
- WorkflowLibrary
続けて、以下の.NETアセンブリへの参照を追加します。
- System.WorkflowServices
- System.ServiceModel
- System.Workflow.Activities
- System.Workflow.ComponentModel
「Program.cs」のMain
メソッドを次のように書き換えます。
using System.ServiceModel; namespace HostingApplication { class Program { static void Main(string[] args) { WorkflowServiceHost serviceHost = new WorkflowServiceHost(typeof(WorkflowLibrary.Workflow1)); WSHttpContextBinding binding = new WSHttpContextBinding(); serviceHost.AddServiceEndpoint( typeof(WcfServiceLibrary.IService1), binding, "http://localhost:8080/FavoriteService"); serviceHost.Open(); Console.ReadLine(); serviceHost.Close(); } } }
WCFのサービス公開の基本的な流れと同じなのですが、WCFで使ったServiceHost
クラスではなく、WorkflowServiceHost
クラスが使われています。WorkflowServiceHost
クラスはWFのワークフローをWCFのサービスとして公開するためのクラスです。
また、WCFのバインディングとしてWSHttpContextBinding
というクラスが指定されています。こちらは後述しますが、コンテキスト交換を行うための特別なバインディングです。
AddServiceEndpoint
メソッドでWCFのエンドポイントを作成します。引数にはコントラクト、バインディング、アドレス、つまりWCFのABCが指定されていることに注目してください。
を、WCFのホスティングの基本については
をご覧ください。IIS以外のホスティングについては後者の記事で扱っています。
HostingApplicationプロジェクトを右クリックして[デバッグ]-[新しいインスタンスを開始]を選択すると、ワークフローをWCFサービスとしてホスティングしたコンソールアプリケーションが立ち上がります。[Enter]キーを押すとアプリケーションは終了します。
WCFサービスを呼び出す(WorkflowClientプロジェクト)
最後に、WCFサービスを呼び出すクライアントを作成しましょう。プロジェクトの新規作成で[Windows フォーム アプリケーション]を選択してWorkflowClientプロジェクトを作成します。
作成後、これまでと同様に、以下のプロジェクト・.NETアセンブリへの参照を追加します。
- WcfServiceLibrary
- System.WorkflowServices
- System.ServiceModel
今回は次のような画面構成にしてみました。上のテキストボックスにURLを入れて[追加]ボタンを押すとWCFサービスのAddFavorite
メソッドを呼び、いくつか追加した後に[一覧取得]ボタンを押すとWCFサービスのGetFavorites
メソッドを呼んで、登録したURL一覧をリストに表示します。
コードの実装ですが、まずコンストラクタにWCFサービスを利用するためのコードを記述します。
using WcfServiceLibrary; using System.ServiceModel; using System.ServiceModel.Channels; //以上3行はファイル先頭のusing行に追加 ... //WCFを呼び出すためのチャンネルファクトリクラス ChannelFactory<IService1> channelFactory; IService1 serviceProxy; //WCFサービスコントラクトのプロキシ public Form1() { InitializeComponent(); //コンテキスト接続のためのバインディング WSHttpContextBinding binding = new WSHttpContextBinding(); //コントラクト、バインディング、アドレスを指定して //WCFサービスへのチャンネルファクトリを生成 channelFactory = new ChannelFactory<IService1>( binding, "http://localhost:8080/FavoriteService"); serviceProxy = channelFactory.CreateChannel(); //プロキシを生成 button2.Enabled = false; }
こちらは完全にWCFクライアントとしての実装ですので、詳細は省略しますが、HostingApplicationと同じABCで接続していることに注目してください。
[追加]ボタンのイベントハンドラは次のとおりです。
private void button1_Click(object sender, EventArgs e) { serviceProxy.AddFavorite(textBox1.Text); button2.Enabled = true; textBox1.Clear(); }
[追加]ボタン押下時にワークフローに対してAddFavorite
メソッドの呼び出しを行っています。といっても、クライアント上ではこれがワークフローで実行されることを意識しておらず、通常のWCF呼び出しと同じ記述であることに注目してください。
[一覧取得]ボタンのイベントハンドラは次のとおりです。
private void button2_Click(object sender, EventArgs e) { listBox1.Items.Clear(); string[] favorites = serviceProxy.GetFavorites(); listBox1.Items.AddRange(favorites); button2.Enabled = false; serviceProxy = channelFactory.CreateChannel(); }
こちらも同様にワークフローに対してGetFavorites
メソッドの呼び出しを行い、結果をリストボックスに表示しています。
なお、今回はGetFavorites
メソッドを呼ぶと、ステートマシン ワークフローが終了状態に遷移する設計にしましたので、一覧取得後はCreateChannel
メソッドで新しいワークフローを呼び出すようにしています。
(HostingApplicationが実行されていることを確認したうえで)WorkflowClientプロジェクトを右クリックして[デバッグ]-[新しいインスタンスを開始]を選択すると、次のようにクライアントが立ち上がります。
URL登録を何回か行い、一覧取得が行えることを確認してください。
さて、AddFavorite
メソッドで登録したURLが、GetFavorites
メソッドできちんと取得できたこと、これは実は当たり前のことではありません。なぜならば、よく知られているようにHTTP通信はステートの概念を持たないステートレスな仕組みだからです。異なるHTTP通信同士が関連しているかどうかを判断するには、HTTPクッキーや他の方法を用いてセッションなどのステートフルな仕組みを準備する必要があります。しかし、今回はそうした設定をまったく行うことなく、別個のHTTP通信が同じワークフローインスタンスに適切に割り当てられていました(AddFavorite
で追加した文字列をGetFavorites
で取得できる=同じワークフローインスタンスで実行されている)。
これは、.NET Framework 3.5からWF/WCFに導入されたコンテキスト交換のフレームワークによって実現されています。コンテキスト交換に対応したバインディングを使うことで、SOAPヘッダないしはHTTPクッキー(設定によって切り替え可能)を使って呼び出しのコンテキストを保持することができます。HTTPベース通信の場合はWSHttpContextBinding
を、TCPベース通信の場合はNetTcpContextBinding
をバインディングに使うことで、自前でコンテキストの保持を考える必要なく、メソッドの呼び出しが適切なワークフローに割り振られるようになります。
まとめ
長い記事になってしまいましたが、Visual Studio 2008を使ったWFアプリケーションの構築について考えてくることができました。永続化サービスをはじめとするWFの機能はまだたくさんあり、この記事で扱ったのはWFの基本的な概念に過ぎません。
しかし、日頃縁遠いワークフローという概念を、簡単にアプリケーションに組み込むことができるWFのポテンシャルについては、垣間見ることができたのではないでしょうか。
この記事で扱えなかったいくつかの点については、以前のWF記事
- WF(Windows Workflow Foundation)チュートリアル 前編
- WF(Windows Workflow Foundation)チュートリアル 中編
- WF(Windows Workflow Foundation)チュートリアル 後編
をご覧ください。本記事と重複する部分もありますが、IISでのホスティングや、SQL Serverへのワークフローの永続化など、参考になる点も多いかと思います。
さて、Visual Studio 2005 Extensions for WFからVisual Studio 2008に進化するに際し、.NET Framework 3.5の新機能以外には、サポート範囲や変化した部分は特に見あたりませんでした。これはVisual Studio 2005 Extensions for WFがベータ版ではなく正式リリース版であり、十分な完成度を持っていたことに起因する点かと思います。
では、Visual Studio 2008を使う際、WFについて特に見るべき点はないのか? と言うと、そうではありません。.NET Framework 3.0から3.5にかけてWFに追加された機能、つまりWCFとの統合は強力なソリューションとなり得ます。この記事のサンプルでも見ることができましたが、WCFサービスとして公開したワークフローを利用するクライアントの実装は、ワークフローとまったく無関係に行うことができました。この点はクライアントからもワークフローをいろいろ意識する必要があった、.NET Framework 3.0におけるクライアント実装から大いに進歩した点と言えます。
WCFとの統合により、これまでワークフローを使うことを考えなかった分野で、WCFのバックエンドとしてWFのワークフローを活用していく場面が増えていくことでしょう。
今回はReceiveアクティビティでWCFサービスの呼び出しを受け付ける実装のみを取り扱いましたが、Sendアクティビティを使うことで、他のサービスの呼び出しを行うこともできます。また、以前より存在する外部のWebサービス呼び出し機能などを用いることで、さまざまなデータソース・サービスを連携させていくこともできるでしょう。
参考資料
- MSDN 『WFとWCFの連携(スクリーンキャスト)』