CodeZine(コードジン)

特集ページ一覧

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

Visual Studio 2008 徹底入門 (8)

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

サンプル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で送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:Visual Studio 2008徹底入門

もっと読む

著者プロフィール

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

    静岡県榛原町生まれ。一橋大学経済学部卒業後、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