はじめに
第5回、第7回に続き、.NET Framework 3.0から導入されたコンポーネントとVisual Studio 2008でのサポートについて解説していきます。
今回扱うのはワークフロー フレームワークであるWF(Windows Workflow Foundation)です。.NET Framework 3.5から、WFとWCFの統合が図られ、ワークフローをWCFの通信モデルを使って提供することが容易になりました。
対象読者
- Visual Studio 2008に興味がある方
- WFに興味がある方
必要な環境
シリーズ第1回を参考に、Visual Studio 2008のインストールを行ってください。
WFについて
.NET Framework 3.0から導入されたコンポーネント群について、Visual Studio 2008からどのように利用できるかを解説していますが、最後に残ったのがWF(Windows Workflow Foundation)です。
ワークフローとは何か
ワークフローとは、主に業務アプリケーションなどで使われることの多い言葉で、特定の業務を小さな処理(ワーク)の集合体でなる一つの流れ(フロー)とみなす概念のことです。
ワークフローの重要性について考える際に、ワークフロー自体をビジネスモデルと呼べる場合もある、というのは興味深いことです。例えば多くのオンラインショップで利用されている「買い物かご」あるいは「カート」モデルは、1つのワークフローと呼ぶことができます。
「商品を1つ注文したら精算する(都度精算モデル)」というワークフローから「商品を任意の数だけ注文し、最後に一括で精算する(買い物かごモデル)」というワークフローに移行することで、ユーザーエクスペリエンスの向上・さらなる購入機会の増大(「ついで買い」の可能性は都度精算モデルよりも買い物かごモデルの方が高いでしょう)など、多くのメリットが生み出されてきました。ワークフローが業務の単なる手順書にとどまらず、ビジネス全体に影響を及ぼす一例ではないでしょうか。
このように、どのような処理の組み合わせで流れていくかという観点で業務をモデル化したものがワークフローです。ワークフローの中の処理のことをアクティビティと呼びます。
ワークフローを専門に扱うフレームワークが必要なのはなぜか
「ワークフローとは、細かな処理であるアクティビティの組み合わせである」という概念が分かったならば、それをコンピュータ上に実装することは簡単でしょう。
しかし、シンプルなモデルであれば、プログラム実装およびワークフロー変更に伴う書き換えもそれほど煩雑ではないかもしれませんが、複雑な条件や多くの処理を含むワークフローの場合、モデルをプログラムに落とし込む中でバグが入り込むかもしれません。
ここでワークフロー フレームワークの登場です。ワークフロー フレームワークはワークフローをプログラムに落とし込まず、そのままのモデルで扱います。このことには多くのメリットがあります。
- 可視化:
- ドメイン固有言語(Domain-Specific Language:DSL)である:
- 再利用性:
今回扱うWFは、ワークフローを.NET Framework上で扱うために設計された、汎用的に使用可能なフレームワークです。
WFの扱うワークフロー
WFでは、大きく分けて2種類のワークフローを扱うことができます。
シーケンシャル ワークフロー
シーケンシャル ワークフローは、ワークフロー開始からアクティビティを実行していき、最後のアクティビティが完了するまで順に処理が流れていくワークフローです。
ただし、シーケンシャル ワークフローは厳密な逐次処理ではなく、複数のアクティビティを同時に実行することもできますので、処理の順番が厳密に定められているわけではありません。
ステートマシン ワークフロー
ステートマシン ワークフローは、さまざまな状態(ステート)を含んでおり、イベントに応じてステート間を遷移しながら処理を行い、最終状態に遷移していくワークフローです。
ステートマシン ワークフローはイベントをトリガにしてアクションを実行し、ステートを遷移しながら処理を行います。
こうした状態遷移を持つモデルを実装する場合、ステート管理が煩雑になりがちです。しかしWFにおいては、ステート管理やワークフローに伴うさまざまな処理をフレームワークに任せることができます。
2種類のワークフローの特徴
シーケンシャル ワークフローは、処理シーケンスをモデル化したもので、基本的に止まることなく処理が続いて流れていきます(もちろん、アクション内でブロックが発生して処理が止まることはあります)。
それに対し、ステートマシン ワークフローはステートを中心に、ステート間を繋ぐイベントとアクションをモデル化したもので、イベントがない限り処理は止まっており、イベントが発生した時点でアクションが実行されてステートが変化していきます。
実は、シーケンシャル ワークフローはステートマシン ワークフローの特殊なパターン(初期状態と終了状態以外のステートを持たないステートマシン ワークフロー)と言えます。従って、すべてのシーケンシャル ワークフローはステートマシン ワークフローとして表現することができます。
実装においては、扱う対象が特にステートを持たず、シーケンシャル ワークフローで表現できるものか、ステート・イベント・アクションの組み合わせで構成されるステートマシン ワークフローで表現する必要があるものか、を検討したうえで、よりモデル化しやすい方を選んでいきましょう。
WF(&Visual Studio 2008)の提供する機能
WF(とVisual Studio 2008)が提供する代表的な機能は次のとおりです。
- GUIによるワークフローデザイン
- ワークフローのホスティング機能
- ワークフローの永続化
- ワークフロー内のデバッグ機能
GUIによるワークフローデザイン
Visual Studio 2008を使うことで、以下のようワークフローを直感的なUIでデザインすることができます。
ワークフロー内にさまざまなアクティビティを並べてつなぎ合わせることで、複雑な処理を実現することができます。アクティビティの種類については後述します。
なお、WFはソースコードベースのワークフローだけではなく、XMLベースのXOML(Extensible Object Markup Language)形式のワークフローにも対応しています。
ワークフローのホスティング機能
WFには特別なサーバアプリケーションは存在せず、任意のアプリケーション上でワークフローを動作させることができます。従って、ネットワークを使わないスタンドアロンアプリケーションの中でもワークフローを利用できます。
しかし、ワークフローが業務アプリケーションで利用されることが多いことを考えれば、一般的にはワークフローをアプリケーションサーバ上でホスティングし、クライアントからワークフローを呼び出す、という形態が用いられることでしょう。
WFを使えば、ワークフローをASP.NET WebサービスやWindows Forms、コンソールアプリケーションといった、さまざまな種類のアプリケーションでホスティングすることができます。
また、.NET Framework 3.5からの新機能として、WFとWCFの統合が図られ、ワークフローをWCFサービスとして公開することもできるようになりました。
ワークフローの永続化
処理時間、という観点で言えば、コンピュータ内で完結する、あるいは他のコンピュータと通信するなどの比較的短時間(~数十秒)で終了するワークフローもあれば、上長が決裁ボタンを押すなど、人間がかかわるために終了までにかなりの時間(~数日)がかかるワークフローもあるでしょう
ワークフローでこうした処理を行う場合、この間ずっとワークフローをホスティングするアプリケーションを実行しておく必要があります。また、多数のワークフローがメモリ上に溜まってしまい、性能劣化を引き起こす可能性もあります。
WFはワークフローの永続化というソリューションを提供することで、こうした問題を解決します。WFはワークフローをSQL Serverなどのストレージに永続化させますので、アプリケーションは任意のワークフローを中断/再開させることができます。
このワークフローの永続化は透過的に行われるため、いくつかの設定を行うだけで、特別なコーディングを必要としません。
ワークフローのデバッグ
ワークフローを使ったアプリケーション開発の際、ワークフロー上の特定のアクティビティにブレークポイントを仕掛け、デバッグを行うことができます。
WFで利用可能なアクティビティ
では、サンプルに入る前の最後の説明として、アクティビティについて考えましょう。
Visual Studio 2008でワークフローをデザインする際には、次のようなツールボックスからアクティビティを配置していきます。
.NET Framework 3.0から存在するアクティビティについては前回連載記事をご覧ください。ここでは.NET Framework 3.5で追加されたアクティビティを紹介します。
アクティビティ | 概要 |
Receive | WCFサービスの呼び出しを受け付ける |
Send | WCFサービスを呼び出す |
コンポジット アクティビティ
いくつかのアクティビティは、他のアクティビティを子供に持って階層化することができます。階層化可能なアクティビティをコンポジット アクティビティと呼びます。
例えばIfElseアクティビティは、条件が真の場合とそれ以外の場合に、それぞれ実行すべきアクティビティを子供として持つことができます。
サンプル1~シーケンシャル ワークフロー
では、早速ワークフローを使ったサンプルを実装してみましょう。最初はシンプルなシーケンシャル ワークフローをデザインし、スタンドアロンアプリケーションでホスティングします。
今回は購入申請アプリケーションで、入力された申請内容の条件を見て、申請を許可するか却下するかを判断する、というワークフローを作成します。
申請内容は次のようなクラスで、金額・申請者名・申請品目のみを保持するものとしましょう。
public class ApplicationForm { int price; public int Price //金額 { get { return price; } set { price = value; } } string applicantName; public string ApplicantName //申請者名 { get { return applicantName; } set { applicantName = value; } } string productName; public string ProductName //購入品目 { get { return productName; } set { productName = value; } } }
このクラスのPrice
プロパティが一定額未満であれば購入許可、一定額以上であれば拒否、というシンプルなワークフローです。
[ファイル]-[新規作成]-[プロジェクト]を選択し、[Visual C#]-[Workflow]-[シーケンシャル ワークフロー コンソール アプリケーション]を選択してプロジェクトを作成します。
ここではSeqWFTestというプロジェクトを作成しました。
プロジェクト作成後、ワークフローのひな形としてWorkflow1.csが作成され、デザイン画面が表示されます。
シーケンシャル ワークフローは前述の通り、ワークフロー開始から終了までアクティビティが順に実行されて流れていくワークフローです。この画面のがワークフロー開始を、がワークフロー終了を表しています。
では、ワークフローをデザインしてみましょう。
アクティビティの配置
まずツールボックスからIfElseアクティビティをドラッグ&ドロップでワークフロー開始と終了の間に貼り付けます。
IfElseアクティビティは、前述の通りコンポジット アクティビティで、条件分岐した後に実行すべきアクティビティを持つことができます。今回はプログラムを直接実行するCodeアクティビティをそれぞれに配置してみましょう。
以上でアクティビティの配置は完了です。次に条件とコードの実装に進んでいきましょう。
ワークフローのパラメータ指定
条件分岐の設定をする前に、[プロジェクト]-[新しい項目の追加]から[クラス]を選択し、先ほどのApplicationForm
クラスをプロジェクトに追加しましょう。
次にワークフローに、パラメータである購入申請を受け取るためのプロパティを追加します。
ソリューション エクスプローラの「Workflow1.cs」を右クリックし、[コードの表示]を選択して、以下のコードを追加します。
private ApplicationForm form; public ApplicationForm Form { get { return form; } set { form = value; } }
IfElseアクティビティの条件設定
次に条件設定です。貼り付けたIfElseアクティビティの左下のifElseBranchActivity1をクリックし、右下のプロパティペインでCondition
プロパティを設定します。
ソースコードによる条件指定も可能ですが、今回は[宣言型のルール条件]を選択しましょう。Condition
プロパティの配下にConditionName
プロパティが作成されますので、右端の[...]ボタンをクリックします。
[条件の選択]画面で[新規作成(N)]ボタンをクリックします。
[ルール条件エディタ]画面で実際の条件を指定します。
入力された購入申請の価格が5000未満かどうかという条件分岐にしますので、
form.Price < 5000
という条件を入力します。入力の際に、IntelliSenseが働くことに注目してください。
[OK]ボタンを押し、[条件の選択]画面で条件を確認してください。
Codeアクティビティへのコード実装
以上で条件分岐が設定できたので、Codeアクティビティで条件分岐後のコードを書きましょう。Codeアクティビティをダブルクリックすると、実行するコードを記述するためのハンドラが自動生成されます。
ここでは次のようにハンドラを記述します。
private void codeActivity1_ExecuteCode(object sender, EventArgs e) { //条件真の場合 Console.WriteLine( string.Format("{0}さんの{1}購入申請は許可されました。", form.ApplicantName, form.ProductName)); } private void codeActivity2_ExecuteCode(object sender, EventArgs e) { //条件偽の場合 Console.WriteLine(string.Format( "{0}さんの{1}購入申請は却下されました。", form.ApplicantName, form.ProductName)); }
条件を満たす場合、満たさない場合それぞれにメッセージを出力します。
以上でワークフロー側の実装は完了です。
Mainメソッドからのワークフロー呼び出し
次に「Program.cs」を開き、Main
メソッドの内容を確認しましょう。
static void Main(string[] args) { using(WorkflowRuntime workflowRuntime = new WorkflowRuntime()) { AutoResetEvent waitHandle = new AutoResetEvent(false); workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {waitHandle.Set();}; workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e) { Console.WriteLine(e.Exception.Message); waitHandle.Set(); }; WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(SeqWFTest.Workflow1)); instance.Start(); waitHandle.WaitOne(); } }
ここで重要なのはワークフローの生成を司るWorkflowRuntime
クラスと、ワークフローの実体を表すWorkflowInstance
クラスです。
WorkflowRuntime
クラスはワークフローを実行するためのランタイムで、1つのプロセスに付き1つだけインスタンスを生成し、その上でワークフローを実行します。
続いていくつかのイベントハンドラ登録を行った後、WorkflowRuntime#createWorkflow
メソッドからWorkflowInstance
インスタンスを生成しています。引数にSeqWFTest.Workflow1
クラスの型を指定することで、実際に実行するワークフローを特定します。その後Start
メソッドで実行を開始します。
以上はワークフロー実行時にパラメータを渡さない場合のコードです。今回は購入申請をパラメータとして渡しますので、コードの書き換えを行いましょう。
CreateWorkflow
メソッドでインスタンス生成を行っている部分を、次のように書き換えます。
//購入申請インスタンス生成 ApplicationForm form = new ApplicationForm(); form.ApplicantName = "土井"; form.Price = 3200; form.ProductName = "参考文献"; //パラメータとなるDictionaryクラスを生成 Dictionary<string, object> properties = new Dictionary<string, object>(); properties.Add("Form", form); //ワークフロー生成時にパラメータも渡す WorkflowInstance instance = workflowRuntime.CreateWorkflow( typeof(SeqWFTest.Workflow1),properties); instance.Start(); waitHandle.WaitOne(); Console.ReadKey(); //キー入力待ち
ApplicationForm
クラスに申請情報を入力し、それをDictionary
型のproperties
変数にいったん格納します。その際にキーとして指定した文字列"Form"
は、ワークフローに作成したプロパティ名と合わせる必要があります。パラメータを詰めたproperties
変数をCreateWorkflow
メソッドの2番目の引数として指定します。ここで、内部的にはDictionary
型のキーと値が、ワークフローのプロパティ名とプロパティ値とみなされ、プロパティが設定されます。
この場合は作成した申請情報がForm
プロパティに設定された上でワークフローが生成されることになります。
ワークフローの実行
では、[F5]キーを押して実行してみましょう。
今回はPrice
プロパティの値が3200ですので、条件(Priceが5000未満)を満たし、購入申請は許可されます。
ワークフローのデザイン/実行が非常にシンプルなことがご理解いただけたかと思います。
サンプル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の連携(スクリーンキャスト)』