データ交換サービスインターフェースの実装
次に、データ交換サービスインターフェースを実装しましょう。
今回はホスティングアプリケーションであるStateMachineWorkflowSampleApplicationプロジェクトのForm1クラスで、先ほど定義したIOrderServiceを実装します。
StateMachineWorkflowSampleApplicationプロジェクトで、[プロジェクト]-[参照の追加]から「StateMachineWorkflowLibrary」プロジェクトを追加します。
同様に「System.Workflow.Activitiesアセンブリ」「System.Workflow.ComponentModelアセンブリ」「System.Workflow.Runtimeアセンブリ」への参照も追加します。
Form1.csに以下のコードを追加してください。IOrderServiceインターフェースで定義した発送イベントOrderShippedと、状態更新メソッドItemStatusUpdate
を実装しています。
public event EventHandler<OrderEventArgs> OrderShipped; delegate void ItemStatusUpdateDelegate(Guid instanceId, string message); public void ItemStatusUpdate(Guid instanceId, string message) { //Invokeメソッド呼び出しが必要な場合 //(UIスレッド以外からの呼び出しの場合) if (this.InvokeRequired) { //delegateを介してInvoke呼び出しを行う //→UIスレッドからこのメソッドを呼び直す ItemStatusUpdateDelegate itemStatusUpdate = new ItemStatusUpdateDelegate(ItemStatusUpdate); object[] args = new object[2] { instanceId, message }; this.Invoke(itemStatusUpdate, args); } else { //インスタンスIDとメッセージを表示 MessageBox.Show(instanceId + ":" + message); //コンボボックスから完了したワークフローのインスタンスIDを削除 this.comboBox1.Items.Remove(instanceId); if (this.comboBox1.Items.Count == 0) { this.comboBox1.Enabled = false; } } }
OrderShippedイベントはそのままとして、ItemStatusUpdate
メソッド内でInvokeRequiredプロパティの値により分岐を行っているのを不思議に思われるかもしれません。
ワークフロー側から呼び出されたItemStatusUpdateメソッドはUIスレッドで実行されていないため、直接UI上のコントロール(ここではコンボボックスなど)の値を変更することができません。そのため、Invoke
メソッドを介してUIスレッドに処理を委譲しています。
ItemStatusUpdate
メソッドの実際の処理は、ワークフローから通知されたメッセージをダイアログ表示し、完了したワークフローのインスタンスIDをコンボボックスから削除する、というものです。
ワークフロー ランタイムへのデータ交換サービスの登録
次に、定義したインターフェースを、ホスティングアプリケーションでワークフロー ランタイムに登録する必要があります。こちらもStateMachineWorkflowLibraryプロジェクトのForm1クラスで行います。コンストラクタ前後を以下のように書き換えてください。
//ワークフロー ランタイム WorkflowRuntime workflowRuntime; public Form1() { InitializeComponent(); //ワークフロー ランタイムの生成 workflowRuntime = new WorkflowRuntime(); //(1) データ交換サービス(ExternalDataExchangeService)の生成 ExternalDataExchangeService dataService = new ExternalDataExchangeService(); //(2) ワークフロー ランタイムへのデータ交換サービスの登録 workflowRuntime.AddService(dataService); //(3) ExternalDataExchange属性を持つ自分自身を // データ交換サービスに追加 workflowRuntime.AddService(this); workflowRuntime.StartRuntime(); }
データ交換サービスはワークフローを実行する前にワークフロー ランタイムに登録しておく必要があります。
ExternalDataExchangeServiceクラスを生成し(1)、ワークフロー ランタイムにサービスを登録します(2)。System.Workflow.Activities.ExternalDataExchangeServiceは、WFの提供するデータ交換サービスクラスです。先ほど定義したIOrderServiceインターフェースの実装クラス(自分自身)をデータ交換サービスに登録します(3)。
これにより、ワークフローに対してIOrderServiceインターフェースが紐付けられます。
先ほどワークフローのデザインでIOrderServiceインターフェースのOrderShippedイベントを指定しましたが、このイベントはワークフロー ランタイムに登録されたデータ交換サービスを介して受け取ることになります。
ワークフローの起動
では、いよいよワークフローを起動します。StateMachineWorkflowLibraryプロジェクトのForm1.csの[商品注文]ボタンをダブルクリックし、イベントハンドラに以下の内容を記述します。
private void button1_Click(object sender, EventArgs e) { //ワークフロー インスタンスを生成 WorkflowInstance instance = workflowRuntime.CreateWorkflow( typeof(StateMachineWorkflowLibrary.Workflow1)); //ワークフロー開始 instance.Start(); //InstanceIdをコンボボックスに追加 this.comboBox1.Items.Add(instance.InstanceId); this.comboBox1.Enabled = true; this.comboBox1.SelectedIndex = 0; }
ワークフローの生成はシーケンシャル ワークフローの場合と同様に、ワークフロー ランタイムのCreateWorkflow
メソッドを使います。
ワークフロー開始後、インスタンスに自動的に割り当てられるインスタンスID(InstanceIdプロパティ)をコンボボックスに追加します。起動したワークフロー インスタンスをどこかに保存するのではなく、インスタンスIDだけ保存していることに注目してください。以降はこのインスタンスIDを使ってワークフローを指定することになります。
なお、ここで起動したワークフローは、初期状態のWorkflow1InitialStateでイベント発生待ちになります。
イベントの送信
次に、ワークフローへイベントを送信しましょう。[指定商品発送]ボタンをダブルクリックし、イベントハンドラに以下の内容を記述します。
private void button2_Click(object sender, EventArgs e) { //コンボボックスが選択されている場合は if (this.comboBox1.SelectedItem != null) { //発送イベント(OrderShipped)を発行する this.OrderShipped(null, new OrderEventArgs((Guid)this.comboBox1.SelectedItem)); } }
コンボボックスで、起動済のワークフローのインスタンスIDが選択されているかどうかをチェックし、OrderShippedイベントを発行します。
ここで発行したOrderShippedイベントは、データ交換サービスを介してワークフローへ渡され、デザインしたとおりに状態遷移を発生させます。
サンプル実行
では、[F5]キーを押して実行してみましょう。
[商品注文]ボタンを押すとワークフローが起動し、コンボボックスにインスタンスIDが設定されます。
[指定商品発送]ボタンを押すとイベントが送信され、ワークフローからItemStatusUpdate
メソッドが呼び出され、結果がダイアログ表示されます。
画面上に表示される内容はシンプルですが、背後でステートマシン ワークフローが動作しており、ホスティングアプリケーションからのイベント通知およびワークフローからのメソッド呼び出しがきちんと機能していることに注目してください。
まとめ
非常にシンプルなパターンではありますが、これでステートマシン ワークフローの動作を確認することができました。ホスティングアプリケーション <-> ワークフロー間の相互呼び出しの仕組みであるデータ交換サービスは重要ですので、しっかり理解しておきましょう。
前回サンプルとして使ったような、「起動後は投げっぱなし」といったワークフローでは不要ですが、ホスティングアプリケーション側でワークフローの処理状況を表示したり、ユーザー入力などをワークフローに渡す場合には、シーケンシャル ワークフローにおいてもデータ交換サービスを活用することになります。
今回のような簡易的なアプリケーションであれば、ワークフローを使う意義はあまりありません。しかし、より多くの状態を持つ、より複雑な業務を実装する場合には、すべてソースコードで実現するよりも、WFのステートマシン ワークフローを使うことで、ステート管理の煩雑さを解決し、可視化された分かりやすいプログラムとすることができます。
さて、最終回となる後編では、永続化サービスなどWFの提供する便利な機能について触れ、このシリーズの結びとします。
参考資料
・@IT『InvokeRequiredプロパティについて』