CodeZine(コードジン)

特集ページ一覧

Visual Studio 2008で標準搭載されたWindows Workflow Foundation

Visual Studio 2008 徹底入門 (8)

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2008/05/09 00:00

Visual Studio 2008では、ついに.NET Framework 3.5がサポートされ、WPF/WCF/WFについても、統合開発環境を活かしたサポートが行われるようになりました。今回は、ワークフロー フレームワークであるWF(Windows Workflow Foundation)を紹介します。.NET Framework 3.5から、WFとWCFの統合が図られ、ワークフローをWCFの通信モデルを使って提供することが容易になりました。

はじめに

 第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でデザインすることができます。

 ワークフロー内にさまざまなアクティビティを並べてつなぎ合わせることで、複雑な処理を実現することができます。アクティビティの種類については後述します。

GUI操作に対するワークフローの内部実装
 GUIを使ってワークフローをデザインすると、内部的にはアクティビティに対応するオブジェクトを使ったソースコードが生成されます。
なお、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~シーケンシャル ワークフロー

 では、早速ワークフローを使ったサンプルを実装してみましょう。最初はシンプルなシーケンシャル ワークフローをデザインし、スタンドアロンアプリケーションでホスティングします。

 今回は購入申請アプリケーションで、入力された申請内容の条件を見て、申請を許可するか却下するかを判断する、というワークフローを作成します。

 申請内容は次のようなクラスで、金額・申請者名・申請品目のみを保持するものとしましょう。

申請クラス ApplicationForm.cs
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アクティビティ配置
IfElseアクティビティ配置

 IfElseアクティビティは、前述の通りコンポジット アクティビティで、条件分岐した後に実行すべきアクティビティを持つことができます。今回はプログラムを直接実行するCodeアクティビティをそれぞれに配置してみましょう。

Codeアクティビティ配置
Codeアクティビティ配置

 以上でアクティビティの配置は完了です。次に条件とコードの実装に進んでいきましょう。

ワークフローのパラメータ指定

 条件分岐の設定をする前に、[プロジェクト]-[新しい項目の追加]から[クラス]を選択し、先ほどのApplicationFormクラスをプロジェクトに追加しましょう。

 次にワークフローに、パラメータである購入申請を受け取るためのプロパティを追加します。

 ソリューション エクスプローラの「Workflow1.cs」を右クリックし、[コードの表示]を選択して、以下のコードを追加します。

プロパティ追加
private ApplicationForm form;

public ApplicationForm Form
{
  get { return form; }
  set { form = value; }
}

IfElseアクティビティの条件設定

 次に条件設定です。貼り付けたIfElseアクティビティの左下のifElseBranchActivity1をクリックし、右下のプロパティペインでConditionプロパティを設定します。

Conditionプロパティの選択
Conditionプロパティの選択

 ソースコードによる条件指定も可能ですが、今回は[宣言型のルール条件]を選択しましょう。Conditionプロパティの配下にConditionNameプロパティが作成されますので、右端の[...]ボタンをクリックします。

 [条件の選択]画面で[新規作成(N)]ボタンをクリックします。

[条件の選択]画面
[条件の選択]画面

 [ルール条件エディタ]画面で実際の条件を指定します。

 入力された購入申請の価格が5000未満かどうかという条件分岐にしますので、

form.Price < 5000

 という条件を入力します。入力の際に、IntelliSenseが働くことに注目してください。

 [OK]ボタンを押し、[条件の選択]画面で条件を確認してください。

追加された分岐条件
追加された分岐条件

Codeアクティビティへのコード実装

 以上で条件分岐が設定できたので、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メソッドの内容を確認しましょう。

Program.cs
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を参照。

 以下の手順で実装を進めていきます。

  1. WCFサービスコントラクトを作成する(WcfServiceLibraryプロジェクト)
  2. サービス コントラクトをワークフローで実装する(WorkflowLibraryプロジェクト)
  3. ワークフローをWCFサービスとして公開する(HostingApplicationプロジェクト)
  4. WCFサービスを呼び出す(WorkflowClientプロジェクト)

WCFサービスコントラクトを作成する(WcfServiceLibraryプロジェクト)

 最初にWCFのサービス コントラクトを作成します。プロジェクトの新規作成で[WCF]-[WCFサービス ライブラリ]を選択してプロジェクトを作成します。今回はプロジェクト名をWcfServiceLibrary、ソリューション名をWFWCFTestとしました。

 サービス コントラクトとして「IService1.cs」が、サービス実装として「Service1.cs」が自動生成されます。今回はサービス実装はワークフローとして別プロジェクトで行いますので、ここでは「Service1.cs」を削除し、「IService1.cs」を変更します。

 以下の内容でサービスコントラクトを作成します。お気に入りURLを登録/取得できるブックマークサービスのイメージです。

IService1.cs
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アクティビティを配置します。

Receive/Codeアクティビティの追加後
Receive/Codeアクティビティの追加後

 なお、赤い!マークが表示されているのはプロパティが未設定でアクティビティの状態がエラーとなっているためです。

 まず、ここで受け付けるWCFサービスを選択しましょう。

 ReceiveアクティビティのプロパティペインでServiceOperationInfoプロパティ右端の[...]ボタンをクリックします。[操作の選択]画面で[インポート]をクリックし、[.NET型の参照と選択]画面で[参照アセンブリ]-[WcfServiceLibrary]-[IService1]を選択して[OK]ボタンをクリックします。

[.NET型の参照と選択]画面
[.NET型の参照と選択]画面
コントラクトが表示されない場合は
 まずビルドしましょう。参照したプロジェクトを一回もビルドしていない状態では、コントラクトが表示されません。

 [操作の選択]画面に[使用可能な操作]としてIServiceで定義したメソッドが表示されているので、AddFavoriteメソッドを選択し、[OK]ボタンをクリックします。

[操作の選択]画面 AddFavoriteメソッドを選択
[操作の選択]画面 AddFavoriteメソッドを選択

 これでAddFavoriteメソッド呼び出し時に、イベントをハンドリングできるようになりました。次にメソッドの引数を取り出し、実装を行いましょう。

 Receiveアクティビティのプロパティペインに、urlという名前の欄が新しく追加されています。これは、AddFavoriteメソッドのurl引数を表しています。右端の[...]ボタンをクリックし、この引数をどのように扱うかを選択します。

 [新しいメンバへのバインド]タブの[プロパティの作成]を選択して[OK]ボタンをクリックします。

プロパティのバインド方法の選択
プロパティのバインド方法の選択

 これで、AddFavoriteメソッドの引数urlの値は、receiveActivity1_url1というプロパティに設定されるようになります。

 もう一つ、CanCreateInstanceプロパティをTrueとしておきましょう。これは、AddFavoriteメソッドが呼ばれたときに、このワークフローをインスタンス化するためのプロパティです。

CanCreateInstanceプロパティは重要
 このプロパティがすべてのアクティビティでFalseの場合、ワークフローを起動させることができなくなってしまいます。ワークフローの中で最初に呼び出されるメソッドで、必ずTrueにしておきましょう。

 次に、Codeアクティビティをダブルクリックし、次のようにコードを実装します。

AddFavoriteメソッドのイベントハンドラ
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)の右のオレンジの部分をダブルクリックしましょう。

プロパティのバインドでクリックするオレンジのアイコン
プロパティのバインドでクリックするオレンジのアイコン
クリックする場所について
 先ほどと同じように右端の[...]ボタンをクリックすると、string配列のせいか、[文字列コレクション エディタ]という画面が出てきてしまいます。
[文字列コレクション エディタ]画面
[文字列コレクション エディタ]画面
分かりづらいですが、オレンジの部分をダブルクリックするようにしてください。なお、カーソルをその付近に置くと「プロパティのバインド」というツールチップが表示されます。

 先ほどと同様に[新しいメンバへのバインド]タブの[プロパティの作成]を選択して[OK]ボタンをクリックします。これで、GetFavoritesメソッドの戻り値は、receiveActivity2__ReturnValue_1というプロパティから取得されるようになります。

 後は、Codeアクティビティを実装するだけです。ダブルクリックして次のようにコードを実装します。

GetFavoritesメソッドのイベントハンドラ
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メソッドを次のように書き換えます。

Program.cs
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記事
 WCFについては
を、WCFのホスティングの基本については
をご覧ください。IIS以外のホスティングについては後者の記事で扱っています。

 HostingApplicationプロジェクトを右クリックして[デバッグ]-[新しいインスタンスを開始]を選択すると、ワークフローをWCFサービスとしてホスティングしたコンソールアプリケーションが立ち上がります。[Enter]キーを押すとアプリケーションは終了します。

WCFサービスを呼び出す(WorkflowClientプロジェクト)

 最後に、WCFサービスを呼び出すクライアントを作成しましょう。プロジェクトの新規作成で[Windows フォーム アプリケーション]を選択してWorkflowClientプロジェクトを作成します。

 作成後、これまでと同様に、以下のプロジェクト・.NETアセンブリへの参照を追加します。

  • WcfServiceLibrary
  • System.WorkflowServices
  • System.ServiceModel

 今回は次のような画面構成にしてみました。上のテキストボックスにURLを入れて[追加]ボタンを押すとWCFサービスのAddFavoriteメソッドを呼び、いくつか追加した後に[一覧取得]ボタンを押すとWCFサービスのGetFavoritesメソッドを呼んで、登録したURL一覧をリストに表示します。

クライアントアプリケーションUI
クライアントアプリケーションUI

 コードの実装ですが、まずコンストラクタに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記事

 をご覧ください。本記事と重複する部分もありますが、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サービス呼び出し機能などを用いることで、さまざまなデータソース・サービスを連携させていくこともできるでしょう。

参考資料

 
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

  • WINGSプロジェクト 土井 毅(ドイ ツヨシ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5