(2010/07 : RTM 版にあわせて修正)
環境 : Visual Studio 2010 Beta 2 (.NET Framework 4)
WF 4
- そのアイデアとベースクラスの変更
- コードいらずのワークフロー (入門)
- ワークフローのコードと XAML の内部
- フローチャート (FlowCharts)
- アクティビティ (基礎)
- アクティビティコンテキスト (context) と変数 (Variable) の意義
- アクティビティにおけるさまざまな実行管理
- アクティビティのデリゲート
- アクティビティの非同期実行
- アクティビティデザイナー
- ブックマーク
- Workflow Extensions と 永続化、トラッキング
- デザイナーリホスティングとカスタムアプリケーション
こんにちは。
もたもた書いていられないので、乱暴ですみませんが、これらのネタをまとめて記載したいと思います。(多少長くなると思いますが、ご辛抱ください。。。)
Workflow Extensions の基本
乱暴な言い方かもしれませんが、従来の WF をご存じの方には、Workflow Extensions は、ワークフローに登録するサービス (永続化サービス、データ交換サービス、など) と同じと言えばおわかり頂けるでしょう。従来の WF をご存じない方でもわかるように、以下に簡単に例示してみます。
下記の例では、IPrint という Workflow Extension 用のインタフェースを用意し、このインタフェースを ConsolePrint というクラスで実装して、この実装された Workflow Extension を WF のインスタンスに設定しています。カスタムの PrintActivity では、この挿入された Workflow Extension (ConsolePrint) を取り出して、Print メソッドを呼び出しています。
// カスタムの Extension の定義public interface IPrint{ void Print(string val);}public class ConsolePrint : IPrint{ public void Print(string val) { Console.WriteLine("print : {0}", val); }}// アクティビティの定義public class PrintActivity : CodeActivity{ public InArgument<string> PrintText { get; set; } protected override void Execute(CodeActivityContext context) { // Extension を取得して処理を実行 ! string printText = PrintText.Get(context); IPrint printEx = context.GetExtension<IPrint>(); printEx.Print(printText); }}// 動作確認. . .static void Main(string[] args){ AutoResetEvent t = new AutoResetEvent(false); WorkflowApplication app = new WorkflowApplication( new PrintActivity { PrintText="Tsuyoshi" }) { Completed = (e) => { Console.WriteLine("Completed Called !"); t.Set(); } }; // Extension の設定 ConsolePrint printEx = new ConsolePrint(); app.Extensions.Add(printEx); // インスタンスの実行 app.Run(); t.WaitOne(); Console.ReadLine();}
上記のサンプルコードで、例えば、IPrint を実装した別のクラス (例 : プリンターに出力するクラス、メッセージボックスを表示するクラス、など) を挿入した場合を想像してみてください。つまり、ワークフローインスタンスではなく、Main の処理の側で、実際の動きをいろいろと定義することが可能になるわけです。
Workflow Extensions を使用すると、ワークフローインスタンスの構成とは無関係に、こうしたホスト側 (上記の Main) から挿入された Extension にあわせたワークフローの制御がおこなえます。
補足 : 上記では独自な拡張 (Extension) を作成していますが、IWorkflowInstanceExtension インターフェイスを実装して拡張 (Extension) クラスを作成すると、拡張機能の中でワークフロー インスタンスにアクセスして処理をおこなうことができます。(このため、通常は、このインターフェイスを実装します。)
また、Metadata の AddDefaultExtensionProvider メソッド を使用すると、特定の型の Extension がランタイムに設定されていない場合に、その型の Extension を追加することができます。
WF の標準アクティビティでは、このようにして、内部でいくつかの Extension が追加されています。
永続化 (Persistence) とトラッキング (Tracking)
ワークフローでは、例えば、数日にまたがった承認処理など、一般に、長時間の実行がおこなわれることが多いため、実行中のインスタンスをいつもメモリー上にロードしておくというわけにはいかないでしょう。また、承認の経過を表示するようなアプリケーションでは、ワークフローが現在どこまで進んでいるかという追跡 (トラッキング) の仕組みも必要になります。WF では、こうした、永続化 (Persistence) やトラッキング (Tracking) の処理においても、上記の Workflow Extensions のメカニズムが内部で使用されています。
では、まずは、永続化の簡単なサンプル コードを作成して使い方を確認してみましょう。(その後で、Workflow Extension を活用した拡張方法について簡単に説明します。)
今回、Idle 時にメモリから退避 (Unload) させて永続化 (Persist) をおこなうサンプルを作成します。このため、前回作成した WaitInputActivity (下記) を使って実験してみましょう。
public class WaitInputActivity : NativeActivity{ protected override bool CanInduceIdle { get { // Idle になる可能性があることを明示 ! return true; } } protected override void Execute(NativeActivityContext context) { Console.WriteLine("Bookmark Start."); context.CreateBookmark("Bookmark1-MyWorkflow", new BookmarkCallback(OnResume)); } void OnResume(NativeActivityContext context, Bookmark bookmark, object value) { Console.WriteLine("OnResume Called ! : {0}", value.ToString()); }}
WF では、簡単に永続化を実装したい開発者のために、SQL Server 用の永続化の仕組みが既定で提供されています。このサンプルコードでは、この SQL Server 用の永続化ストアを使用します。
まず、永続化させるためのデータベースを SQL Server に作成 (create database 文) しておきましょう。つぎに、%systemroot%\Microsoft.NET\Frameworkv4.0.2\1006\SQL\ja に、永続化用のスキーマ作成のスクリプトがあるので、この中の SqlWorkflowInstanceStoreSchema.sql, SqlWorkflowInstanceStoreLogic.sql を作成したデータベースに対して実行します。
sqlcmd -S .sqlexpress -d TestDB -E -i SqlWorkflowInstanceStoreSchema.sql
sqlcmd -S .sqlexpress -d TestDB -E -i SqlWorkflowInstanceStoreLogic.sql
注意 : 同じフォルダ内に、SqlPersistenceService_Schema.sql, SqlPersistenceService_Logic.sql がありますが、これは、旧 WF 3.5 のときの永続化サービス用のスクリプトがそのまま置かれています。また、SqlPersistenceProviderSchema.sql, SqlPersistenceProviderLogic.sql は、WCF で Durable サービスを実装する際に使用する SQL Server 用の永続化プロバイダーです。
なお、Tech Days 2010 ではデモでお見せする予定ですが、Windows Server AppFabric の Application Extension をインストールして IIS 管理マネージャから初期化をすると、上記のスキーマが Application Extension データベースに作成されます。
つぎに、コードを記述します。
[ワークフローコンソールアプリケーション] のプロジェクトを作成して、System.Runtime.DurableInstancing.dll、System.Activities.DurableInstancing.dll を参照追加します。
そして、上記の WaitInputActivity を使用した下記のコードを実装しましょう。
. . .using System.Runtime.DurableInstancing;using System.Activities.DurableInstancing;static void Main(string[] args){ AutoResetEvent t = new AutoResetEvent(false); InstanceStore ins = new SqlWorkflowInstanceStore( @"Data Source=.sqlexpress;Initial Catalog=TestDB;Integrated Security=True"); InstanceView view = ins.Execute(ins.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30)); ins.DefaultInstanceOwner = view.InstanceOwner; WorkflowApplication app = new WorkflowApplication(new Sequence { Activities = { new WriteLine{ Text="1st activity !" }, new WaitInputActivity(), new WriteLine{ Text="last activity !" } } }) { InstanceStore = ins, PersistableIdle = (e) => { Console.WriteLine("PersistableIdle Called !"); return PersistableIdleAction.Unload; }, Unloaded = (e) => { Console.WriteLine("Unloaded Called !"); t.Set(); }, Completed = (e) => { Console.WriteLine("Completed Called !"); t.Set(); } }; // start and unload app.Run(); Console.WriteLine("Instance Id : {0}", app.Id.ToString()); // load and continue //app.Load(new Guid("Some-Guid-Value")); //app.ResumeBookmark("Bookmark1-MyWorkflow", "Tsuyoshi"); t.WaitOne(); Console.ReadLine();}
上記のコードでは、InstanceStore = ins の行によって、作成するワークフローインスタンス (WorkflowApplication) に SQL Server 用の InstanceStore (SqlWorkflowInstanceStore) を設定しています。
また、以下のコードの箇所により、(永続化可能な) アイドルイベントが発生した場合に、メモリーからインスタンスのアンロード (Unload) をおこなって、データベースへの永続化 (Persist) をおこなうように、ワークフローインスタンスに指示をしています。
PersistableIdle = (e) =>{ Console.WriteLine("PersistableIdle Called !"); return PersistableIdleAction.Unload;}
このため、このワークフロー インスタンスでは、最初の WriteLine が実行され、つぎの WaitInputActivity が実行されると、WaitInputActivity の中でブックマークが発生してアイドル状態になり、メモリからアンロードされて、データベースに永続化されます。(つまり、最後の WriteLine は実行されずに、データベース中に退避されます。)
実行結果は、以下になります。(実行した結果をそのまま貼り付けました。。。)
Instance Id : 483539c4-004e-4abc-98ce-0ebb26ddf4511st activity !Bookmark Start.PersistableIdle Called !Unloaded Called !
ちなみに、上記で作成した SQL Server データベース内の InstancesTable テーブルを SQL Server 上から確認すると、ちゃんとインスタンスが 1 レコード登録されているはずです。
つぎに、上記のコンソールに出力されたワークフロー インスタンスIDを使用して、再び、このワークフロー インスタンスを呼び戻してみましょう。(下記のソースコードで、ワークフローインスタンスIDの箇所は、皆さんの実行結果にあわせて変更しておいてください。)
static void Main(string[] args){ AutoResetEvent t = new AutoResetEvent(false); InstanceStore ins = new SqlWorkflowInstanceStore( @"Data Source=.sqlexpress;Initial Catalog=TestDB;Integrated Security=True"); InstanceView view = ins.Execute(ins.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30)); ins.DefaultInstanceOwner = view.InstanceOwner; WorkflowApplication app = new WorkflowApplication(new Sequence { Activities = { new WriteLine{ Text="1st activity !" }, new WaitInputActivity(), new WriteLine{ Text="last activity !" } } }) { InstanceStore = ins, PersistableIdle = (e) => { Console.WriteLine("PersistableIdle Called !"); return PersistableIdleAction.Unload; }, Unloaded = (e) => { Console.WriteLine("Unloaded Called !"); t.Set(); }, Completed = (e) => { Console.WriteLine("Completed Called !"); t.Set(); } }; // start and unload //app.Run(); //Console.WriteLine("Instance Id : {0}", app.Id.ToString()); // load and continue app.Load(new Guid("483539c4-004e-4abc-98ce-0ebb26ddf451")); app.ResumeBookmark("Bookmark1-MyWorkflow", "Tsuyoshi"); t.WaitOne(); Console.ReadLine();}
実行結果は、下記の通りになります。(ワークフローインスタンスが、再びメモリ中にロードされ、続きから処理がおこなわれているのがわかります。)
OnResume Called ! : Tsuyoshilast activity !Completed Called !Unloaded Called !
ちなみに、この場合、ワークフローが完了すると、上記の InstancesTable テーブルからも該当のレコードは削除されます。
Note : このレコードが削除されるというのは既定の動作ですが、上記のコードで作成した SqlWorkflowInstanceStore のオブジェクト (ins) の InstanceCompletionAction プロパティの値を InstanceCompletionAction.DeleteNothing にすると、完了後もインスタンスの情報がデータベース内に残せます。このように、プロパティを設定して、きめ細かな制御が可能です。
さて、この永続化の拡張方法 (Customization) について考えてみましょう。
WF 4 では、永続化処理そのものを担当する InstanceStore クラス (InstanceStore を継承したクラス) と、Workflow Extension の 1 つである PersistenceParticipant クラス (PersistenceParticipant を継承したクラス) によって、永続化の処理をカスタマイズできます。上記では SQL Server に永続化をおこなう SqlWorkflowInstanceStore クラスを使用しましたが、例えば、独自の InstanceStore クラスを作成して、ファイルに保存をおこなう処理などを実装できます。また、PersistenceParticipant クラスを継承して、このクラスを Workflow Extension に追加することで、永続化をおこなうカスタムなデータを追加できます。永続化のパイプラインは、データを永続化 (Unload) させる際に、Workflow Extension のすべての PersistenceParticipant クラスを取り出し、それぞれの PersistenceParticipant について、CollectValues メソッドを呼び出します。このため、 カスタムな PersistenceParticipant クラスを作成して、CollectValues メソッドを override することで、永続化の際のデータ辞書を独自に追加できます。また逆に、永続化ストアから、メモリ中の Workflow Extension オブジェクトにデータを戻す際 (Load) は、PublishValues メソッドが呼び出されます。(ロードの処理は、この PublishValues メソッドを override します。)
この WF 4 の永続化のパイプライン処理 (各クラスを使用した処理の流れ) については、以下に記載されていますので参考にしてください。
http://msdn.microsoft.com/en-us/library/ee473464(VS.100).aspx
また、今回は永続化の説明だけでしたが、トラッキング (Tracking) も簡単に実現可能で、トラッキングは、TrackingParticipant から継承されたクラス (override メソッドである Track メソッドを実装すれば OK です) を作成して Workflow Extension に追加することで、独自にカスタマイズすることが可能です。(例えば、コンソール上にトラッキングの結果を出力する独自の Extension などを作成できます。) なお、WF 4 には、EtwTrackingParticipant という ETW を使ったトラッキングのクラスが既に用意されており、Windows Server AppFabric ではこれが使用されています。また、トラックする内容をカスタマイズする場合は、TrackingProfile と呼ばれるクラスを使用し、上述した TrackingParticipant の TrackingProfile プロパティにこのプロファイルを設定すれば OK です。
なお、ここでは、ワークフロー サービス (WCF/WF 連携サービス) については説明しませんが、ワークフロー サービスではこうした設定 (上述した InstanceCompletionAction などの細かな設定も含め) を構成ファイル (.config) に定義することが可能です。
ということで、あとはリホスティングの話 (WF 4 では、非常に簡単になっています !) を記載して、最終回にしたいと思います。。。(大急ぎな説明で、すみません。。。)
Categories: Uncategorized
Article well written in very fluent in Japanese.
LikeLike