WF (ワークフロー) を使った Web ページ の画面遷移 : 実践編 (Part 1)

環境:
Internet Information Services 7.0
Visual Studio 2008 Beta 2


こんにちは。


以前、こちらのポスト <http://blogs.msdn.com/tsmatsuz/archive/2007/06/07/wf-asp-net.aspx> で、WF (Windows Workflow Foundation) を使って Web の画面遷移をおこなう方法について、コードなしで無責任なポストを投稿しましたので、今回は実際にやってみました。
暇で作っていたわけではありません。Tech ED のスペシャルセッションで、もしお時間があればお見せしたいと思い作成した簡易なものです (スペシャルセッションのデモでは、この機能に加え、もう少し手の込んだものを作っています、、、)。
少なくとも Tech ED では、ソースの中をご紹介する時間はないので、このブログではソースと考え方を以下にご紹介していきたいと思います。


今回は、その内容について 3 回にわけて記述したいと思います。


Part1 : アクティビティ (Activity) の実装
Part2 : フロー (Workflow) の実装
Part3 : IHttpHandler を使ったホストの実装


Part1, Part2 で作成するワークフローのプロジェクト (完成品) を添付しておきます。(see this blog’s attachment file.)


Part3 については、IHttpHnadler のクラス 1 つなので、その回でコードを記載します。


ここでは、サンプルとして作成しています。手を抜いた箇所もありますので、実際の開発で使用される際はもっときれいにしてください。(例えば、Part3 で説明しますが、このソースではすべて GET でリダイレクトして画面遷移するので、POST 要求などの長い文字列(例: バイト列のエンコードされた情報、など)を渡す手段は提供していません。こうした改良ポイントなども気づいたら可能な限り記載していきたいと思います。)


例により、以下でご紹介している WF の概念や、データ交換サービス、永続化サービス、などをご理解した上で読んでください。(でないと、きっと書いている意味がさっぱりわからないはずです。)


http://www.microsoft.com/japan/msdn/windows/windowsserver2008/tab/code/eds.aspx


基本の考え方は以前記載した通りですが、再度簡単に記載しますと、ワークフローとホスト (この場合は、w3wp.exe のワーカープロセス) の連携は、データ交換サービスを使用します。ワークフロー側では、次のアクション (遷移する画面) を解決して、データ交換サービス (CallExternalMethod アクティビティ)  を使ってそれを通知し、ホスト側ではその通知されたアクションの内容によってリダイレクト (Response.Redirect()) をおこないます。
ワークフロー側は、このアクティビティの実行後、HandlerExternalEvent アクティビティで待機し、次の要求を待ってアイドルしています。画面表示後は、エンドユーザは表示された ASP.NET や htm, php などの画面上で編集作業をおこない (Tech ED では、php を使用してデモするつもりです)、確定したらワークフロー用に設定するホスト側のハンドラ (IHttpHandler) に POST などをして遷移します。処理を受け取ったホスト側のハンドラは、今度はデータ交換サービスを使って、ワークフローインスタンス側にイベントを投げることで、再度アイドル状態だったインスタンスがスタートされます。


このため、Visual Studio で [Sequential Workflow Library] のプロジェクトを作成したら、まずデータ交換サービス用のインタフェースを用意しておく必要があります。
また、ワークフロー側では、ユーザが入力したデータを使って処理することもあるでしょう。よって、ホスト側からワークフロー側に、ユーザが入力したフォームデータの内容などをハッシュテーブルに入れて渡す仕様にします。このため、普通の ExternalDataEventArgs ではなく (これだと、ワークフローインスタンス ID しか渡しません)、これを継承して以下のような独自の BuyStepEventArgs を作成しておきましょう。
また、逆に、ワークフローインスタンス側からホスト側に「・・・の画面を表示せよ」と通知するために、引数に、「どの画面か」を表す目印を入れておく必要があります。今回は、それを action という string で渡す仕様にしましょう。
すると、データ交換用のインタフェースなどのコードは、以下のようになります。(なお、以前のブログでも記載しましたが、この action は、何も「画面」でなくても良いのです。つまり、action を受け取って画面を遷移させるか、コンソールに表示するかは、ホストが決めることで、ワークフロー側の関心事ではありません。)


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Workflow.Runtime;
using System.Workflow.Activities;


namespace BuyStepflow
{
    [ExternalDataExchange]
    public interface IBuyStepExchange
    {
        /// <summary>
        /// データ交換サービスにおけるワークフローインスタンスからホストプログラムへの処理の呼び出し
        /// </summary>
        /// <param name=”id”>現在実行中のワークフローインスタンスID</param>
        /// <param name=”action”>ホスト側に要求するアクションの種類をあらわす文字列 (Select:商品の選択, Payment:購入金額等の確認, Confirm:購入確定の確認)</param>
        /// <remarks>ワークフローインスタンスが処理を完了して次のアクションが決定すると、インスタンスがこのメソッドを呼び出します</remarks>
        void DoProceed(Guid id, string action);


        /// <summary>
        /// データ交換サービスにおけるホストプログラムからワークフローインスタンスへの終了通知
        /// </summary>
        /// <remarks>ホストプログラム側で actoin で要求した一連の処理が終了した際に、この処理を呼び出します。BuyStepEventArgs として収集した情報をハッシュテーブルで渡すことで、ワークフローインスタンスは次の要求内容を解決します。</remarks>
        event EventHandler<BuyStepEventArgs> Processed;
    }


    [Serializable()]
    public class BuyStepEventArgs : ExternalDataEventArgs
    {
        private Hashtable buyVariables = new Hashtable();


        public Hashtable BuyVariables
        {
            get { return buyVariables; }
            set { buyVariables = value; }
        }


        public BuyStepEventArgs(Guid InstanceId, Hashtable pBuyVariables)
            : base(InstanceId)
        {
            BuyVariables = pBuyVariables;
        }
    }


}


では、1つの画面を処理するアクティビティを作成していきましょう。
上述で記載したシナリオ(流れ)の通り、ある画面要求を処理するアクティビティ (添付したサンプルコードでは、BuyActivity クラス) の内部では、画面表示をホストに要求する CallExternalMethod と、その画面での入力完了の通知を受け取る HandleExternalEvent が必要です (この 2 つが揃って、1 つの画面の処理が完結します)。ですので、アイテムの追加で [Activity] を追加し、このアクティビティの継承元を SequenceActivity にして、このアクティビティの中にこれらのアクティビティ (CallExternalMethod, HandleExternalEvent) を含めておきます。



デザイナーの CallExternalMethod のプロパティウィンドウで、上記で作成したデータ交換サービスのインタフェースを下記の通り選択します。


InterfaceType : IBuyStepExchange
MethodName : DoProceed


HandleExternalEvent についても同様です。


InterfaceType : IBuyStepExchange
EventName : Processed


さて、追加した CallExternalMethod では、ホスト側に自分のインスタンスの識別子を教えるため、ワークフローインスタンスID (Guid) を渡さなければいけません。でも待ってください、Activity 内部ではインスタンスの ID などは知らぬ、存ぜぬです。
そこで、フロー側からこの Guid を渡せるように、DependencyProperty として、コード中に以下の通りプロパティを追加します。(これにより、ワークフロー側でこのアクティビティを追加すると、アクティビティのプロパティウィンドウに  WorkflowId というプロパティが表示され、デザイナー上でワークフロー側の変数とバインドできるようになります。)


public static DependencyProperty WorkflowIdProperty = DependencyProperty.Register(“WorkflowId”, typeof(Guid), typeof(BuyActivity));
[ValidationOption(ValidationOption.Required)]
public Guid WorkflowId
{
    get { return (Guid)base.GetValue(WorkflowIdProperty); }
    set { base.SetValue(WorkflowIdProperty, value); }
}


デザイナーの CallExternalMethod アクティビティのプロパティウィンドウで、DoProceed のパラメータ(引数) id として、この WorkflowId という変数を選択して設定しておきましょう。(これにより、この変数に設定された値が、DoProceed メソッドのパラメータ id に渡されて実行されます。)


さらに、今回の場合、DoProceed のパラメータとして action の名前も渡す必要があります。
このアクティビティでは、「商品選択画面を表示せよ」という目印として、 「Select」という文字列を渡すようにしましょう。ですので、デザイナーから定数を入れれば良いですが、ここもちょっと待ってください。この値についても、アクティビティ側ではいかなるワークフローからでも使えるように汎用的に作成しておき、ワークフロー側からこの「Select」という文字を設定できるようにしておきましょう。よってここも DependencyProperty として以下の通り宣言し、これを同様にデザイナーを使って action パラメータに設定しておきましょう。


public static DependencyProperty ActionTypeProperty = DependencyProperty.Register(“ActionType”, typeof(string), typeof(BuyActivity));
[ValidationOption(ValidationOption.Required)]
public string ActionType
{
    get { return (string)base.GetValue(ActionTypeProperty); }
    set { base.SetValue(ActionTypeProperty, value); }
}


さて、あともう少しで今日のブログは終了です。
今度は、HandleExternalEvent に渡すプロパティを整理しましょう。
HandleExternalEvent では、EventArgs として上記で作成した BuyStepEventArgs 型のオブジェクトと、送り主のオブジェクトの参照 (sender) が渡ってきます。そして、上述の通り、ユーザが画面などで入力したデータの一式が、この BuyStepEventArgs の中の BuyVariables プロパティに入ってくるはずです。
ですので、送り主 (sender) はどうでも良いですが、この EventArgs はアクティビティ内部で受け取って利用できるようにしなければいけません。そこで、この EventArgs を受け取る入れ物を以下の通り作成しておき、デザイナーのパラメータ e として、下記の EArgs を選択して設定しておきましょう。


private BuyStepEventArgs eArgs;
public BuyStepEventArgs EArgs
{
    get { return eArgs; }
    set { eArgs = value; }
}


さて、この取得した BuyVariables プロパティの Hashtable ですが、誰が使うんでしょうか?そうです、これを使うのはアクティビティではなく、このアクティビティを使うワークフローのほうです (値を元に演算をしたり、次の画面の判断をしたり、など)。
よって、この Hashtable をワークフロー側でバインドできるように、まずは、以下のように、Hashtable 型の入れ物を DependencyProperty として追加しておきましょう。


public static DependencyProperty BuyVariablesProperty = DependencyProperty.Register(“BuyVariables”, typeof(Hashtable), typeof(BuyActivity));
[ValidationOption(ValidationOption.Required)]
public Hashtable BuyVariables
{
    get { return (Hashtable)base.GetValue(BuyVariablesProperty); }
    set { base.SetValue(BuyVariablesProperty, value); }
}


そして、先ほど受け取った EArgs からこの BuyVariables に値を入れ込む必要があります。よって、HandleExternalEvent をダブルクリックして、Invoked ハンドラ (処理「後」に呼ばれるハンドラ) を以下の通り実装しておきましょう。


private void ActionEndActivity_Invoked(object sender, ExternalDataEventArgs e)
{
    BuyVariables = EArgs.BuyVariables;
}


これで画面遷移を扱える汎用的なアクティビティが仕上がりました。


余談ですが、同じ処理は、コーディングするよりも、ブログで書くほうがはるかに時間がかかるということに気づきました、、、

BuyStepflow.zip

Advertisements

One thought on “WF (ワークフロー) を使った Web ページ の画面遷移 : 実践編 (Part 1)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s