次の方法で共有


A Developer's Introduction to Windows Workflow Foundation (WF) in .NET 4 (開発者向け .NET 4 の Windows Workflow Foundation (WF) 入門)

Matt Milner、Pluralsight

2009 年 11 月

リリース日: 2010 年 4 月

概要

ソフトウェア開発者が知っているとおり、アプリケーションの作成は困難な場合があり、プロセスを簡略化し、解決しようとしているビジネス上の課題に集中するのに役立つツールとフレームワークを常に探しています。  アセンブラーなどのマシン言語でコードを記述することから、開発を容易にし、メモリ管理などの低レベルの懸念事項を取り除き、開発者としての生産性を向上させる、C# や Visual Basic などの上位レベルの言語に移行しました。  Microsoft 開発者の場合、.NET への移行により、共通言語ランタイム (CLR) はメモリの割り当て、不要なオブジェクトのクリーンアップ、ポインターなどの低レベルのコンストラクトの処理を行うことができます。 

アプリケーションの複雑さの多くは、バックグラウンドで実行されるロジックと処理に存在します。  非同期実行や並列実行、一般にユーザー要求やサービス要求に応答するためのタスクの調整などの問題により、アプリケーション開発者は、ハンドル、コールバック、同期などの低レベルのコーディングにすばやく戻ることができます。開発者は、Windows Presentation Foundation (WPF) のユーザー インターフェイスと同じように、アプリケーションの内部に対する宣言型プログラミング モデルの機能と柔軟性が必要です。 Windows Workflow Foundation (WF) は、アプリケーションおよびサービス ロジックを構築するための宣言型フレームワークを提供し、開発者に非同期、並列タスク、その他の複雑な処理を処理するためのより高いレベルの言語を提供します。

メモリとオブジェクトを管理するランタイムを用意すると、コードを記述する際の重要なビジネス側面に集中する必要が出てきたのです。  同様に、非同期作業の調整の複雑さを管理できるランタイムを使用すると、開発者の生産性を向上させる一連の機能が提供されます。  WF は、ワークフロー (ビジネス ロジック)、ロジックと制御フローを定義するのに役立つアクティビティ、および結果のアプリケーション定義を実行するためのランタイムを宣言するための一連のツールです。  つまり、WF は、開発者の生産性を高め、アプリケーションの管理を容易にし、実装を迅速に変更することを目的として、アプリケーションの記述に高いレベルの言語を使用することです。  WF ランタイムは、ワークフローを自動的に実行するだけでなく、状態の永続化、ブックマーク、ビジネス ロジックの再開などのアプリケーション ロジックを記述する際に重要なサービスと機能も提供します。これにより、スレッドとプロセスの機敏性が実現され、ビジネス プロセスのスケールアップとスケールアウトが可能になります。 

WF を使用してアプリケーションをビルドすることで生産性を向上させる方法の概念的な情報については、「その他のリソース」セクションにある「David Chappell によるワークフローの方法」を参照することをお勧めします。 

WF4 の新機能

Microsoft® .NET Framework のバージョン 4 では、Windows Workflow Foundation では、.NET 3.0 および 3.5 の一部として出荷された以前のバージョンのテクノロジから大幅な変更が導入されています。  実際、チームはプログラミング モデル、ランタイム、ツールのコアを見直し、パフォーマンスと生産性を向上させ、以前のバージョンを使用して顧客エンゲージメントから得られた重要なフィードバックに対処するために、それぞれを再設計しました。  WF を採用する開発者に最適なエクスペリエンスを提供し、WF がアプリケーションで構築できる強力な基盤コンポーネントであり続けるには、大幅な変更が必要でした。 ここでの大きな変化を紹介しますが、各トピックは、より深い扱いを得るでしょう。 

先に進む前に、下位互換性もこのリリースの重要な目標であることを理解しておくことが重要です。  新しいフレームワーク コンポーネントは主に System.Activities.* アセンブリにありますが、下位互換性のあるフレームワーク コンポーネントは System.Workflow.* アセンブリにあります。  System.Workflow.* アセンブリは、.NET Framework 4 の一部であり、完全な下位互換性を提供するため、ワークフロー コードに変更を加えずにアプリケーションを .NET 4 に移行できます。 このペーパーでは、WF4 という名前を使用して、System.Activities.* アセンブリに含まれる新しいコンポーネントを参照し、WF3 を使用して System.Workflow.* アセンブリにあるコンポーネントを参照します。 

デザイナー

改善の最も目に見える領域の 1 つは、ワークフロー デザイナーにあります。 使いやすさとパフォーマンスは、VS 2010 リリースのチームの重要な目標でした。  デザイナーでは、パフォーマンスが低下することなく、はるかに大きなワークフローを操作する機能がサポートされるようになりました。デザイナーはすべて Windows Presentation Foundation (WPF) に基づいており、宣言型 UI フレームワークを使用して構築できる豊富なユーザー エクスペリエンスを最大限に活用しています。  アクティビティ開発者は XAML を使用して、アクティビティの外観を定義し、ビジュアル デザイン環境でユーザーと対話します。  さらに、開発者以外のユーザーがワークフローを表示および操作できるように、独自のアプリケーションでワークフロー デザイナーを再ホストする方がはるかに簡単になりました。 

Data Flow

WF3 では、ワークフロー内のデータフローは不透明でした。  WF4 は、引数と変数の使用におけるデータ フローとスコープの明確で簡潔なモデルを提供します。  これらの概念は、すべての開発者になじみがあり、データ ストレージの定義と、ワークフローやアクティビティとの間のデータのフローの両方を簡略化します。  また、データ フロー モデルにより、特定のアクティビティの予想される入力と出力がより明確になり、データの管理が容易になり、ランタイムのパフォーマンスが向上します。 

フローチャート

開発者がフローチャート モデルを使用してワークフローを定義できるようにするために、Flowchart という新しい制御フロー アクティビティが追加されました。  フローチャートは、ソリューションの作成やビジネス プロセスの設計時に多くのアナリストや開発者が行う概念と思考プロセスによく似ています。  したがって、既に行われた概念的思考と計画をモデル化しやすくするためのアクティビティを提供することは理にかなっています。  フローチャートを使用すると、前の手順に戻ったり、1 つの条件に基づいてロジックを分割したり、スイッチ/ケース ロジックを分割したりするなどの概念が可能になります。 

プログラミング モデル

WF プログラミング モデルは、よりシンプルで堅牢になるように改良されました。  Activity はプログラミング モデルの中核となる基本型であり、ワークフローとアクティビティの両方を表します。  さらに、ワークフローを呼び出すために WorkflowRuntime を作成する必要はなくなりました。インスタンスを作成して実行するだけで、特定の環境を設定する手間をかきたくない単体テストとアプリケーション シナリオを簡略化できます。  最後に、ワークフロー プログラミング モデルは、コードを追加せず、ワークフローの作成を簡略化するアクティビティの完全な宣言型の構成になります。

Windows Communication Foundation (WCF) 統合

WF の利点は、サービスの作成と、サービスの相互作用の使用または調整の両方に最も確実に適用されます。  WCF と WF の統合の強化には多大な労力が必要でした。  新しいメッセージング アクティビティ、メッセージの関連付け、ホスティング サポートの改善、完全な宣言型サービス定義が主な改善点です。 

ワークフローを使用したはじめに

WF を理解する最善の方法は、WF の使用を開始し、概念を適用することです。  ワークフローの基礎についていくつかの主要な概念を取り上げ、いくつかの簡単なワークフローを作成して、それらの概念がどのように相互に関連しているかを説明します。 

ワークフロー構造

アクティビティは WF の構成要素であり、すべてのアクティビティは最終的にアクティビティから派生します。  用語メモ - アクティビティは WF の作業単位です。 アクティビティは、より大きなアクティビティにまとめて構成できます。 アクティビティが最上位のエントリ ポイントとして使用される場合、Main は CLR プログラムの最上位エントリ ポイントを表す別の関数であるのと同様に、"ワークフロー" と呼ばれます。   たとえば、図 1 は、コードに組み込まれている単純なワークフローを示しています。 

シーケンス s = 新しいシーケンス

{

    アクティビティ = {

        new WriteLine {Text = "Hello"},

        新しいシーケンス {

            アクティビティ =

            {

                new WriteLine {Text = "Workflow"},

                new WriteLine {Text = "World"}

            }

        }

    }

};

図 1: 単純なワークフロー

図 1 では、シーケンス アクティビティがルート アクティビティとして使用され、ワークフローのルート 制御フロー スタイルが定義されていることに注意してください。 任意のアクティビティをルートまたはワークフローとして使用し、単純な WriteLine でも実行できます。   Sequence の Activities プロパティを他のアクティビティのコレクションと共に設定することで、ワークフロー構造を定義しました。  さらに、子アクティビティには子アクティビティを含めることができるので、ワークフロー定義全体を構成するアクティビティのツリーが作成されます。 

ワークフロー テンプレートとワークフロー Designer

WF4 には多くのアクティビティが付属しており、Visual Studio 2010 にはアクティビティを定義するためのテンプレートが含まれています。 ルート制御フローに使用される 2 つの最も一般的なアクティビティは、シーケンスとフローチャートです。  アクティビティはワークフローとして実行できますが、これら 2 つのアクティビティは、ビジネス ロジックを定義するための最も一般的な設計パターンを提供します。   図 2 は、受信、保存、および他のサービスに送信される通知の処理を定義するシーケンシャル ワークフロー モデルの例を示しています。   

 

図 2: シーケンシャル ワークフロー設計

フローチャート ワークフローの種類は、既存のユーザーからの一般的な要求 (ワークフローの前の手順に戻ることができるなど) に対処するために WF4 で導入されています。これは、ビジネス ロジックの定義に取り組むアナリストや開発者が行う概念設計に似ているためです。  たとえば、アプリケーションへのユーザー入力を含むシナリオを考えてみましょう。  ユーザーが提供するデータに応答して、プログラムはプロセスを続行するか、前の手順に戻って入力を再度求める必要があります。 シーケンシャル ワークフローでは、何らかの条件が満たされるまで DoWhile アクティビティを使用して処理を続行する図 3 と同様のものが含まれます。 

図 3: 決定分岐のシーケンシャル

図 3 の設計は機能しますが、ワークフローを見ている開発者またはアナリストとして、モデルは記述された元のロジックや要件を表していません。   図 4 のフローチャート ワークフローは、図 3 で使用されるシーケンシャル モデルと同様の技術的な結果を提供しますが、ワークフローの設計は、最初に定義された考え方と要件とより密接に一致します。 

図 4: フローチャート ワークフロー

ワークフロー内のデータ フロー

ほとんどのユーザーがワークフローについて考えるときに最初に考えたのは、ビジネス プロセスまたはアプリケーションのフローです。  ただし、フローと同様に重要なのは、プロセスを駆動するデータと、ワークフローの実行中に収集および格納される情報です。  WF4 では、データの格納と管理が設計上の重要な考慮事項となっています。 

データに関して理解するメイン概念には、変数、引数、式の 3 つがあります。  それぞれの単純な定義は、変数がデータを格納するためのものであり、引数はデータを渡すためのものであり、式はデータを操作するためのものです。 

変数 – データの格納

ワークフロー内の変数は、命令型言語で使用されている変数とよく似ています。これらは、データを格納するための名前付き場所を記述し、特定のスコープルールに従います。  変数を作成するには、まず、変数を使用できるスコープを決定します。  クラスまたはメソッド レベルで使用できる変数がコード内にある場合と同様に、ワークフロー変数はさまざまなスコープで定義できます。  図 5 のワークフローを考えてみましょう。  この例では、ワークフローのルート レベル、またはフィード データの収集シーケンス アクティビティによって定義されたスコープで変数を定義できます。 

図 5: アクティビティをスコープとする変数

引数 – データの受け渡し

引数はアクティビティで定義され、アクティビティとの間のデータ フローを定義します。  命令型コードでメソッドに引数を使用するのと同じように、アクティビティに対する引数を考えることができます。  引数には、In、Out、または In/Out を指定でき、名前と型を指定できます。  ワークフローを構築するときに、ルート アクティビティに引数を定義して、呼び出されたときにデータをワークフローに渡すことができます。  ワークフローでは、引数ウィンドウを使用して変数を定義するのと同じように、引数を定義します。 

ワークフローにアクティビティを追加するときは、アクティビティの引数を構成する必要があります。これは、主にスコープ内変数を参照するか、式を使用して行われます。これについては、次に説明します。  実際、Argument 基本クラスには、引数型の値を返す Activity である Expression プロパティが含まれているため、これらのオプションはすべて関連しており、Activity に依存しています。 

ワークフロー デザイナーを使用して引数を編集する場合は、リテラル値を表す式をプロパティ グリッドに入力するか、図 6 に示すように変数名を使用してスコープ内変数を参照できます。emailResult と emailAddress はワークフローで定義されている変数です。 

図 6: アクティビティに対する引数の構成

式 – データに対して動作する

式は、ワークフローでデータを操作するために使用できるアクティビティです。  式は、Activity を使用し、戻り値に関心がある場所で使用できます。つまり、引数の設定で式を使用したり、While アクティビティや If アクティビティなどのアクティビティの条件を定義したりできます。  WF4 のほとんどの要素は Activity から派生し、式は変わるので、Activity<TResult> の派生物であり、特定の型の値を返します。  さらに、WF4 には、変数と引数、および Visual Basic 式を参照するための一般的な式がいくつか含まれています。  この特殊化された式のレイヤーのため、引数の定義に使用する式には、コード、リテラル値、変数参照を含めることができます。  表 1 に、ワークフロー デザイナーを使用して引数を定義するときに使用できる式の種類の小さなサンプリングを示します。  コードで式を作成する場合、ラムダ式の使用など、いくつかのオプションがあります。 

Expression 式の種類

"hello world"

リテラル文字列値

10

Literal Int32 値

System.String.Concat("hello", " ", "world")

命令型メソッドの呼び出し

"hello " & "world"

Visual Basic 式

argInputString

引数参照 (引数名)

varResult

変数参照 (変数名)

"hello: " & argInputString

リテラルと引数/変数が混在している

表 1: 式の例

最初のワークフローの構築

アクティビティとデータ フローに関する主要な概念について説明したので、これらの概念を使用してワークフローを作成できます。  まず、WF の真の価値提案ではなく、概念に焦点を当てる単純な hello world ワークフローから始めます。  開始するには、Visual Studio 2010 で新しい単体テスト プロジェクトを作成します。  WF のコアを使用するには、System.Activities アセンブリへの参照を追加し、テスト クラス ファイルに System.Activities、System.Activities.Statements、および System.IO の using ステートメントを追加します。  次に、図 7 に示すようにテスト メソッドを追加して、基本的なワークフローを作成して実行します。 

[TestMethod]

public void TestHelloWorldStatic()

{

    StringWriter ライター = 新しい StringWriter();

    Console.SetOut(writer);

    シーケンス wf = 新しいシーケンス

    {

        アクティビティ = {

            new WriteLine {Text = "Hello"},

            new WriteLine {Text = "World"}

        }

    };

    WorkflowInvoker.Invoke(wf);

    Assert.IsTrue(String.Compare(

        "Hello\r\nWorld\r\n",

        作家。GetStringBuilder()。ToString()) == 0,

        "正しくない文字列が書き込まれた");

}

図 7: コードでの hello world の作成

WriteLine アクティビティの Text プロパティは InArgument<文字列> であり、この例ではリテラル値をそのプロパティに渡しました。

次の手順では、変数を使用するようにこのワークフローを更新し、それらの変数をアクティビティ引数に渡します。  図 8 は、変数を使用するように更新された新しいテストを示しています。

[TestMethod]

public void TestHelloWorldVariables()

{

    StringWriter ライター = 新しい StringWriter();

    Console.SetOut(writer);

シーケンス wf = 新しいシーケンス

{

    Variables = {

        new Variable<string>{Default = "Hello", Name = "greeting"},

        new Variable<string> { Default = "Bill", Name = "name" } },

        アクティビティ = {

            new WriteLine { Text = new VisualBasicValue<string>("greeting"),

            new WriteLine { Text = new VisualBasicValue<string>(

            "name + \"Gates\"")}

        }

    };

    WorkflowInvoker.Invoke(wf);

}

図 8: 変数を使用したコード ワークフロー

この場合、変数は型 Variable<string> として定義され、既定値が指定されます。  変数は Sequence アクティビティ内で宣言され、2 つのアクティビティの Text 引数から参照されます。  または、式を使用して変数を初期化することもできます。図 9 に示すように、VisualBasicValue<TResult> クラスを使用して式を表す文字列を渡します。  最初のケースでは、式は変数の名前を参照し、2 番目の場合は変数がリテラル値と連結されます。  テキスト式で使用される構文は、C# でコードを記述する場合でも Visual Basic です。

アクティビティ = {

    new WriteLine { Text = new VisualBasicValue<string>("greeting"),

        TextWriter = writer },

    new WriteLine { Text = new VisualBasicValue<string>("name + \"Gates\""),

        TextWriter = writer }

}

図 9: 式を使用して引数を定義する

コード例は重要なポイントを示すのに役立ち、一般的には開発者にとって快適だと感じていますが、ほとんどのユーザーはデザイナーを使用してワークフローを作成します。  デザイナーでは、アクティビティをデザイン サーフェイスにドラッグし、更新されたが使い慣れたプロパティ グリッドを使用して引数を設定します。  さらに、ワークフロー デザイナーには、ワークフローと変数の引数を編集するための展開可能な領域が下部に含まれています。   デザイナーで同様のワークフローを作成するには、ソリューションに新しいプロジェクトを追加し、[アクティビティ ライブラリ] テンプレートを選択して、新しいアクティビティを追加します。 

ワークフロー デザイナーが最初に表示されるとき、アクティビティは含まれません。  アクティビティを定義する最初の手順は、ルート アクティビティを選択することです。  この例では、ツールボックスの Flowchart カテゴリからフローチャート アクティビティをドラッグして、デザイナーに Flowchart アクティビティを追加します。  フローチャート デザイナーで、ツールボックスから 2 つの WriteLine アクティビティをドラッグし、もう一方のアクティビティをフローチャートに追加します。  次に、フローチャートに従うパスが認識されるように、アクティビティを接続する必要があります。  これを行うには、最初にフローチャートの上部にある緑色の "開始" 円をポイントしてグラブ ハンドルを表示し、1 つをクリックして最初の WriteLine にドラッグし、アクティビティの上部に表示されるドラッグ ハンドルにドロップします。  最初の WriteLine を 2 番目の WriteLine に接続する場合も同じ操作を行います。  デザイン 画面は図 10 のようになります。 

図 10: Hello Flowchart レイアウト

構造体が配置されたら、いくつかの変数を構成する必要があります。  デザイン画面をクリックし、デザイナーの下部にある [変数] ボタンをクリックすると、フローチャートの変数のコレクションを編集できます。  "greeting" と "name" という 2 つの変数を追加して、既定値をそれぞれ "Hello" と "Bill" に設定します。値を設定するときは、リテラル文字列を引用符で囲む必要があるため、値を設定するときは必ず引用符を含めるようにしてください。  図 11 は、2 つの変数とその既定値で構成された変数ウィンドウを示しています。 

図 11: ワークフロー デザイナーで定義された変数

WriteLine アクティビティでこれらの変数を使用するには、最初のアクティビティの Text 引数にプロパティ グリッドに "greeting" (引用符なし) を入力し、2 番目の WriteLine の Text 引数に "name" (再び引用符なし) を入力します。   実行時に Text 引数が評価されると、変数の値が解決され、WriteLine アクティビティによって使用されます。

テスト プロジェクトで、ワークフローを含むプロジェクトへの参照を追加します。  次に、図 12 に示すようなテスト メソッドを追加して、このワークフローを呼び出し、出力をテストできます。  図 12 では、作成されたクラスのインスタンスをインスタンス化することでワークフローが作成されていることを確認できます。  この場合、ワークフローはデザイナーで定義され、Activity から派生したクラスにコンパイルされ、呼び出すことができます。 

[TestMethod]

public void TestHelloFlowChart()

{

    StringWriter tWriter = new StringWriter();

    Console.SetOut(tWriter);

    Workflows.HelloFlow wf = 新しい Workflows.HelloFlow();

    WorkflowInvoker.Invoke(wf);

    アサートを省略しました

}

図 12: フローチャートのテスト

これまでは、ワークフローで変数を使用し、それらを使用して WriteLine アクティビティの引数に値を指定してきました。  ワークフローには、呼び出し時にワークフローにデータを渡したり、ワークフローの完了時に出力を受信したりできる引数を定義することもできます。  前の例の Flowchart を更新するには、"name" 変数を削除し (これを選択して Delete キーを押します)、代わりに string 型の "name" 引数を作成します。  これは、引数エディターを表示するために [引数] ボタンを使用する点を除き、ほとんど同じ方法で行います。  引数には方向を指定することもできます。また、実行時に値がアクティビティに渡されるため、既定値を指定する必要はありません。  変数の場合と同じ名前を引数に使用しているため、WriteLine アクティビティの Text 引数は有効なままです。  実行時に、これらの引数はワークフローの "name" 引数の値を評価して解決し、その値を使用します。  Out の方向と "fullGreeting" の名前を持つ string 型の引数を追加します。これは呼び出し元のコードに返されます。 

図 13: ワークフローの引数の定義

フローチャートで Assign アクティビティを追加し、最後の WriteLine アクティビティに接続します。  To 引数に「fullGreeting」(引用符なし) を入力し、Value 引数に「greeting & name」と入力します (引用符は使用しません)。 これにより、greeting 変数と name 引数の連結が fullGreeting 出力引数に割り当てられます。 

単体テストで、ワークフローを呼び出すときに引数を指定するようにコードを更新します。  引数は、引数の名前である Dictionary<文字列、オブジェクト> としてワークフローに渡されます。  これを行うには、図 14 に示すように、呼び出しを変更してワークフローを呼び出します。 出力引数は、引数名にキーを付けた Dictionary<文字列オブジェクト> コレクションにも含まれていることに注意してください。 

IDictionary<文字列、オブジェクト> の結果 = WorkflowInvoker.Invoke(wf,

    新しい Dictionary<文字列,object> {

        {"name", "Bill" } }

    );

string outValue = results["fullGreeting"].Tostring();

図 14: ワークフローに引数を渡す

アクティビティ、変数、引数をまとめる方法の基本を見たので、低レベルの概念ではなく、ビジネス ロジックに焦点を当てたより興味深いワークフローを可能にするために、フレームワークに含まれるアクティビティのツアーについて説明します。

ワークフロー アクティビティ パレットのツアー

任意のプログラミング言語では、アプリケーション ロジックを定義するためのコア コンストラクトが必要です。  WF などのより高いレベルの開発フレームワークを使用する場合、その期待は変わりません。   If/Else、Switch、While などの .NET 言語のステートメントがあるのと同様に、制御フローを管理するには、宣言型ワークフローでロジックを定義するときにも同じ機能が必要です。  これらの機能は、フレームワークに付属する基本アクティビティ ライブラリの形式で提供されます。  このセクションでは、フレームワークに付属するアクティビティのクイック ツアーを提供して、すぐに提供される機能のアイデアを提供します。

アクティビティ プリミティブとコレクション アクティビティ

宣言型プログラミング モデルに移行する場合、コードの記述時に 2 番目の性質である一般的なオブジェクト操作タスクを実行する方法を簡単に疑問に思い始めるのが簡単です。  オブジェクトを操作していて、プロパティの設定、コマンドの呼び出し、項目のコレクションの管理が必要なタスクには、これらのタスクを念頭に置いて特別に設計された一連のアクティビティがあります。 

アクティビティ 説明

割り当て

場所に値を割り当てます。変数の設定を有効にします。

遅延

指定した時間だけ実行パスを遅延します。

InvokeMethod

.NET オブジェクトのメソッドまたは .NET 型の静的メソッドを呼び出します。必要に応じて、戻り値の型が T です。

WriteLine

指定したテキストをテキスト ライターに書き込みます。既定値は Console.Out です。

AddToCollection<T>

型指定されたコレクションに項目を追加します。

RemoveFromCollection<T>

型指定されたコレクションから項目を削除します。

ClearCollection<T>

コレクションからすべての項目を削除します。

ExistsInCollection<T>

指定した項目がコレクション内に存在するかどうかを示すブール値を返します。 

 

制御フロー アクティビティ

ビジネス ロジックまたはビジネス プロセスを定義する場合、実行フローを制御することが重要です。 制御フロー アクティビティには、手順を順番に実行する必要があるときに共通のコンテナーを提供する Sequence などの基本と、If アクティビティや Switch アクティビティなどの一般的な分岐ロジックが含まれます。  制御フロー アクティビティには、データ (ForEach) と Conditions(While) に基づくループ ロジックも含まれます。  複雑なプログラミングを簡略化するために最も重要なのは並列アクティビティです。これらはすべて、複数の非同期アクティビティが同時に作業を行えるようにすることです。 

アクティビティ 説明

Sequence

一連のアクティビティを実行する場合

While/DoWhile

条件 (式) が true の間に子アクティビティを実行します

ForEach<T>

列挙可能なコレクションを反復処理し、コレクション内の各項目に対して子アクティビティを 1 回実行し、子が完了するのを待ってから、次のイテレーションを開始します。 名前付き引数の形式でイテレーションを実行する個々の項目への型指定されたアクセスを提供します。

If

条件 (式) の結果に応じて、2 つの子アクティビティのいずれかを実行します。

スイッチ<T>

式を評価し、一致するキーを使用して子アクティビティをスケジュールします。 

並列

すべての子アクティビティを一度にスケジュールしますが、特定の条件が満たされた場合に、アクティビティが未処理の子アクティビティを取り消せるように完了条件も提供します。 

ParallelForEach<T>

列挙可能なコレクションを反復処理し、コレクション内の各項目に対して子アクティビティを 1 回実行し、すべてのインスタンスを同時にスケジュールします。 ForEach<T> と同様に、このアクティビティは、名前付き引数の形式で現在のデータ項目へのアクセスを提供します。

[選択]

すべての子 PickBranch アクティビティをスケジュールし、トリガーが完了するように最初の以外のすべてを取り消します。  PickBranch アクティビティには、トリガーとアクションの両方があります。それぞれがアクティビティです。  トリガー アクティビティが完了すると、Pick は他のすべての子アクティビティを取り消します。 

 次の 2 つの例は、これらのアクティビティを一緒に作成する方法を示すために使用されているこれらのアクティビティのいくつかを示しています。  最初の例である図 15 には、ParallelForEach<T> を使用して URL の一覧を使用し、指定したアドレスで RSS フィードを非同期的に取得する方法が含まれています。  フィードが返された後、ForEach<T> を使用してフィード項目を反復処理し、処理します。 

コードでは、System.ServiceModel.Syndication 型への参照を使用して VisualBasicSettings インスタンスを宣言して定義することに注意してください。  この設定オブジェクトは、その名前空間から変数型を参照する VisualBasicValue<T> インスタンスを宣言するときに使用され、これらの式の型解決を有効にします。  また、ParallelForEach アクティビティの本文は ActivityAction を受け取ります。これは、カスタム アクティビティの作成に関するセクションで説明されています。  これらのアクションでは、アクティビティが InArgument と OutArgument を使用するのとほぼ同じ方法で、DelegateInArgument と DelegateOutArgument が使用されます。 

図 15: フロー アクティビティを制御する

2 番目の例である図 16 は、タイムアウトで応答を待機する一般的なパターンです。  たとえば、マネージャーが要求を承認するのを待ち、割り当てられた時間内に応答が到着していない場合はリマインダーを送信します。  DoWhile アクティビティは応答を待機する繰り返しを発生させますが、Pick アクティビティは ManagerResponse アクティビティと Delay アクティビティの両方をトリガーと同時に実行するために使用されます。  Delay が最初に完了すると、アラームが送信され、ManagerResponse アクティビティが最初に完了すると、DoWhile ループから抜け出すフラグが設定されます。 

図 16: Pick アクティビティと DoWhile アクティビティ

図 16 の例では、ワークフローでブックマークを使用する方法も示しています。  ブックマークは、後でその時点から処理を再開できるように、ワークフロー内の場所をマークするアクティビティによって作成されます。  Delay アクティビティには、特定の時間が経過した後に再開されるブックマークがあります。  ManagerResponse アクティビティは、入力を待機し、データが到着したらワークフローを再開するカスタム アクティビティです。  まもなく説明するメッセージング アクティビティは、ブックマーク実行のメインアクティビティです。  ワークフローが作業をアクティブに処理していない場合、ブックマークが再開されるのを待機している場合は、アイドル状態と見なされ、永続的なストアに永続化できます。  ブックマークについては、カスタム アクティビティの作成に関するセクションで詳しく説明します。 

移行

WF3 を使用している開発者にとって、相互運用アクティビティは既存の資産を再利用する上で重要な役割を果たすことができます。 System.Workflow.Runtime アセンブリにあるアクティビティは、既存のアクティビティの種類をラップし、WF4 モデルの引数としてアクティビティのプロパティを表示します。  プロパティは引数であるため、式を使用して値を定義できます。  図 17 は、WF3 アクティビティを呼び出す相互運用アクティビティの構成を示しています。  入力引数は、ワークフロー定義内のスコープ内変数への参照で定義されます。    WF3 に組み込まれたアクティビティには、引数ではなくプロパティが含まれているため、各プロパティには対応する入出力引数が与えられ、アクティビティに送信するデータと、アクティビティの実行後に取得するデータを区別できます。 新しい WF4 プロジェクトでは、ターゲット フレームワークが .NET Framework 4 クライアント プロファイルに設定されているため、ツールボックスにこのアクティビティが見つかりません。  プロジェクトのプロパティのターゲット フレームワークを .NET Framework 4 に変更すると、アクティビティがツールボックスに表示されます。

図 17: 相互運用アクティビティの構成

フローチャート

フローチャート ワークフローを設計する場合、フローチャート内の実行フローを管理するために使用できるコンストラクトがいくつかあります。  これらのコンストラクト自体は、単純なステップ、単一の条件に基づく単純な意思決定ポイント、または switch ステートメントを提供します。  フローチャートの真のパワーは、これらのノードタイプを目的のフローに接続する機能です。

コンストラクト/アクティビティ 説明

フローチャート

一連のフロー ステップのコンテナーは、各フロー ステップに任意のアクティビティまたは次のいずれかのコンストラクトを指定できますが、実行するには、フローチャート内で接続する必要があります。 

FlowDecision

条件に基づいて分岐ロジックを提供します。

FlowSwitch<T>

式の値に基づいて複数の分岐を有効にします。 

FlowStep

他のステップに接続できるフローチャートのステップを表します。 この型はデザイナーによって暗黙的に追加されるため、ツールボックスには表示されません。  このアクティビティは、ワークフロー内の他のアクティビティをラップし、ワークフロー内の次のステップのナビゲーション セマンティクスを提供します。 

フローチャート モデルには特定のアクティビティがありますが、ワークフロー内で他のアクティビティを使用できることに注意してください。  同様に、フローチャート アクティビティを別のアクティビティに追加して、そのワークフロー内で Flowchart の実行と設計のセマンティクスを提供できます。  つまり、複数のアクティビティを含むシーケンスと、真ん中にフローチャートを含めることができます。 

メッセージング アクティビティ

WF4 の主な焦点の 1 つは、WF と WCF の統合が強化されていることです。  ワークフローの観点からは、メッセージの送受信などのメッセージング操作をモデル化するアクティビティを意味します。  実際には、メッセージング用のフレームワークにはいくつかの異なるアクティビティが含まれており、それぞれ機能と目的が若干異なります。 

アクティビティ 説明

送信/受信

メッセージを送受信するためのメッセージング アクティビティの 1 つの方法。  これらの同じアクティビティは、要求/応答スタイルの相互作用に構成されます。 

ReceiveAndSendReply

メッセージを受信し、応答を返すサービス操作をモデル化します。 

SendAndReceiveReply

サービス操作を呼び出し、応答を受け取ります。 

InitializeCorrelation

メッセージから値を抽出するのではなく、ワークフロー ロジックの一部として明示的に関連付け値を初期化できるようにします。 

CorrelationScope

関連付けハンドルにアクセスしてアクティビティを受信および送信できる実行スコープを定義し、複数のメッセージング アクティビティによって共有されるハンドルの構成を簡略化します。 

TransactedReceiveScope

Receive アクティビティを使用して WCF 操作にフローされるのと同じトランザクションにワークフロー ロジックを含めます。

ワークフロー内からサービス操作を呼び出すには、ワークフロー プロジェクトにサービス参照を追加する使い慣れた手順に従います。  その後、Visual Studio のプロジェクト システムはサービスのメタデータを使用し、コントラクトで見つかったサービス操作ごとにカスタム アクティビティを作成します。  これは、C# または Visual Basic プロジェクトで作成される WCF プロキシと同等のワークフローと考えることができます。   たとえば、図 18 に示すサービス コントラクトを使用してホテル予約を検索する既存のサービスを取得します。にサービス参照を追加すると、図 19 に示すカスタム アクティビティが生成されます。 

[ServiceContract]

パブリック インターフェイス IHotelService

{

    [OperationContract]

    List<HotelSearchResult> SearchHotels(

        HotelSearchRequest requestDetails);

}

図 18: サービス コントラクト

図 19: カスタム WCF アクティビティ

WCF サービスとして公開されるワークフローの構築の詳細については、このペーパーで後述する「Workflow Services」セクションを参照してください。 

トランザクションとエラー処理

信頼性の高いシステムの作成は困難な場合があり、アプリケーションで一貫性のある状態を維持するために、エラーを処理して管理する適切な作業を行う必要があります。  ワークフローの場合、例外処理とトランザクションを管理するためのスコープは、ActivityAction 型または Activity 型のプロパティを持つアクティビティを使用してモデル化され、開発者はエラー処理ロジックをモデル化できます。  これらの一貫性の一般的なパターンを超えて、WF4 には、補正と確認を通じて長時間の分散調整をモデル化できるアクティビティも含まれています。 

アクティビティ 説明

CancellationScope

ワークフロー開発者が作業の本文が取り消された場合に対応できるようにするために使用されます。 

TransactionScope

トランザクションでスコープ内のすべての子アクティビティを実行することで、コード内でトランザクション スコープを使用するのと同様のセマンティクスを有効にします。 

TryCatch/Catch<T>

例外処理をモデル化し、型指定された例外をキャッチするために使用されます。 

Throw

アクティビティから例外をスローするために使用できます。  

Rethrow

一般に TryCatch アクティビティを使用してキャッチされた例外を再スローするために使用されます。 

CompensableActivity

成功後にアクションを補正する必要がある可能性がある子アクティビティを実行するためのロジックを定義します。  補正ロジック、確認ロジック、および取り消し処理のプレースホルダーを提供します。

Compensate

コンペンザブル アクティビティの補正処理ロジックを呼び出します。

Confirm

コンペンザブル アクティビティの確認ロジックを呼び出します。 

TryCatch アクティビティは、発生する可能性のある例外をキャッチするために一連の作業のスコープを設定するための使い慣れた方法を提供し、Catch<T> アクティビティは例外処理ロジック用のコンテナーを提供します。  このアクティビティの使用方法の例を図 20 に示します。各型指定された例外ブロックは Catch T> アクティビティによって定義され、各 catch<の左側にある名前付き引数を使用して例外にアクセスできます。   必要が発生した場合は、Rethrow アクティビティを使用してキャッチされた例外を再スローするか、Throw アクティビティを使用して独自の新しい例外をスローできます。 

図 20: TryCatch アクティビティ

トランザクションは、有効期間の短い作業の一貫性を確保するのに役立ちます。TransactionScope アクティビティは、TransactionScope クラスを使用して .NET コードで取得できるのと同じ種類のスコープを提供します。 

最後に、一貫性を維持する必要があるが、リソース全体でアトミック トランザクションを使用できない場合は、補正を使用できます。  補正を使用すると、完了した場合に、加えられた変更を補正する一連のアクティビティを含めることができる一連の作業を定義できます。  簡単な例として、電子メールが送信される図 21 の Compensable アクティビティを考えてみましょう。  アクティビティが完了した後に例外が発生した場合は、補正ロジックを呼び出して、システムを一貫した状態に戻すことができます。この場合は、前のメッセージを取り消すフォローアップ メール。 

図 21: コンペンズ可能なアクティビティ

ワークフローの作成と実行

他のプログラミング言語と同様に、ワークフローには、ワークフローを定義して実行するという 2 つの基本的な操作があります。  どちらの場合も、WF には柔軟性と制御を提供するいくつかのオプションが用意されています。 

ワークフローを設計するためのオプション

ワークフローを設計または定義する場合、コードと XAML の 2 つのメインオプションがあります。 XAML は真に宣言型のエクスペリエンスを提供し、ワークフローの定義全体を XML マークアップで定義し、.NET を使用して構築されたアクティビティと型を参照できるようにします。  ほとんどの開発者は、ワークフロー デザイナーを使用してワークフローを構築し、宣言型 XAML ワークフロー定義を作成する可能性があります。  ただし、XAML は単なる XML であるため、任意のツールを使用して作成できます。これは、アプリケーションを構築するための強力なモデルである理由の 1 つです。  たとえば、図 22 に示す XAML は単純なテキスト エディターで作成され、コンパイルすることも、定義されているワークフローのインスタンスを直接実行するために使用することもできます。 

<p:Activity x:Class="Workflows.HelloSeq" xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities/design" xmlns:p="https://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">

  <x:Members>

    <x:Property Name="greeting" Type="p:InArgument(x:String)" />

    <x:Property Name="name" Type="p:InArgument(x:String)" />

  </x:Members>

  <p:Sequence>

    <p:WriteLine>[greeting &amp;" from workflow"]</p:WriteLine>

    <p:WriteLine>[name]</p:WriteLine>

  </p:Sequence>

</p:Activity>

図 22: XAML で定義されたワークフロー

この同じ XAML はデザイナーによって生成され、カスタム ツールによって生成できるため、管理がはるかに簡単になります。  さらに、前に示したように、コードを使用してワークフローを構築することもできます。  これには、フレームワーク内のさまざまなアクティビティとカスタム アクティビティを使用してアクティビティ階層を作成する必要があります。  コードで完全に定義されているワークフローの例をいくつか見てきました。  WF3 とは異なり、コードでワークフローを作成し、XAML に簡単にシリアル化できるようになりました。を使用すると、ワークフロー定義のモデリングと管理の柔軟性が向上します。 

私が示すように、ワークフローを実行するために必要なのはアクティビティであり、コードに組み込まれたインスタンスでも、XAML から作成されたインスタンスでもかまいません。  各モデリング手法の最終的な結果は、Activity から派生したクラス、または逆シリアル化またはアクティビティにコンパイルできる XML 表現のいずれかです。 

ワークフローを実行するためのオプション

ワークフローを実行するには、ワークフローを定義するアクティビティが必要です。  実行できるアクティビティを取得するには、コードでアクティビティを作成するか、XAML ファイルで読み取り、コンテンツをアクティビティに逆シリアル化するという 2 つの一般的な方法があります。  最初のオプションは簡単で、いくつかの例を既に示しています。  XAML ファイルを読み込むには、静的な Load メソッドを提供する ActivityXamlServices クラスを使用する必要があります。  Stream オブジェクトまたは XamlReader オブジェクトを渡すだけで、XAML で表されるアクティビティが返されます。  

アクティビティを作成したら、それを実行する最も簡単な方法は、前の単体テストで行ったように WorkflowInvoker クラスを使用することです。  このクラスの Invoke メソッドは、Activity 型のパラメーターを持ち、IDictionary<文字列オブジェクトを返します>。  ワークフローに引数を渡す必要がある場合は、最初にワークフローで引数を定義してから、値を Activity と共に名前と値のペアのディクショナリとして Invoke メソッドに渡します。  同様に、ワークフローで定義されている Out 引数または In/Out 引数は、 メソッドを実行した結果として返されます。  図 23 は、XAML ファイルからワークフローを読み込み、ワークフローに引数を渡し、結果の出力引数を取得する例を示しています。 

アクティビティ mathWF;

using (Stream mathXaml = File.OpenRead("Math.xaml"))

{

    mathWF = ActivityXamlServices.Load(mathXaml);

}

var outputs = WorkflowInvoker.Invoke(mathWF,

    新しい Dictionary<文字列、オブジェクト> {

    { "operand1", 5 },

    { "operand2", 10 },

    { "operation", "add" } });

Assert.AreEqual<int>(15, (int)outputs["result"], "Incorrect result returned");

図 23: in 引数と out 引数を使用した "Loose XAML" ワークフローの呼び出し

この例では、アクティビティが XAML ファイルから読み込まれ、引き続き引数を受け入れて返すことができることに注意してください。  ワークフローは Visual Studio のデザイナーを使用して簡単に開発されましたが、カスタム デザイナーで開発し、任意の場所に格納できます。  XAML は、実行のためにランタイムに渡される前に、データベース、SharePoint ライブラリ、またはその他のストアから読み込むことができます。  

WorkflowInvoker を使用すると、有効期間の短いワークフローを実行するための最も簡単なメカニズムが提供されます。  基本的にワークフローはアプリケーション内のメソッド呼び出しのように動作するため、WF 自体をホストするために多くの作業を行わなくても、WF のすべての利点をより簡単に利用できます。  これは、アクティビティとワークフローを単体テストする場合に特に便利です。これにより、テスト対象のコンポーネントを実行するために必要なテスト設定が減ります。 

もう 1 つの一般的なホスティング クラスは WorkflowApplication であり、ランタイムで実行されているワークフローに対して安全なハンドルを提供し、実行時間の長いワークフローをより簡単に管理できます。  WorkflowApplication では、WorkflowInvoker と同じ方法でワークフローに引数を渡すことができますが、Run メソッドを使用して実際にワークフローの実行を開始します。  この時点で、ワークフローは別のスレッドで実行を開始し、コントロールは呼び出し元のコードに戻ります。 

ワークフローは非同期的に実行されるようになったため、ホスティング コードでは、ワークフローが完了したとき、または例外がスローされたかどうかを知りたい場合があります。このような種類の通知の場合、WorkflowApplication クラスにはアクション<T> 型のプロパティのセットがあり、イベントのように使用して、中止、未処理の例外、完了、アイドル状態、アンロードなど、ワークフロー実行の特定の条件に対応するコードを追加できます。  WorkflowApplication を使用してワークフローを実行する場合は、図 24 に示すようなコードを使用し、アクションを使用してイベントを処理できます。 

WorkflowApplication wf = 新しい WorkflowApplication(new Flowchart1());

Wf。Completed = delegate(WorkflowApplicationCompletedEventArgs e)

{

    Console.WriteLine("Workflow {0} complete", e.InstanceId);

};

Wf。Aborted = delegate(WorkflowApplicationAbortedEventArgs e)

{

    Console.WriteLine(e.Reason);

};

Wf。OnUnhandledException =

  delegate(WorkflowApplicationUnhandledExceptionEventArgs e)

  {

      Console.WriteLine(e.UnhandledException.ToString());

      return UnhandledExceptionAction.Terminate;

  };

Wf。Run();

図 24: WorkflowApplication アクション

この例では、単純なコンソール アプリケーションであるホスト コードの目的は、ワークフローが完了したとき、またはエラーが発生した場合にコンソールを介してユーザーに通知することです。  実際のシステムでは、ホストはこれらのイベントに関心を持ち、障害に関する情報を管理者に提供したり、インスタンスをより適切に管理したりするために、異なる方法で対応する可能性があります。

ワークフロー拡張機能

WF3 以降の WF のコア機能の 1 つは、.NET アプリケーション ドメインでホストできる軽量であるという点です。  ランタイムは異なるドメインで実行でき、カスタマイズされた実行セマンティクスが必要な場合があるため、ランタイム動作のさまざまな側面をランタイムから外部化する必要があります。  ここでワークフロー拡張機能が機能します。  ワークフロー拡張機能を使用すると、ホスト コードを記述する開発者は、カスタム コードを使用してランタイムに動作を追加できます。 

WF ランタイムが認識する 2 つの拡張機能の種類は、永続化と追跡拡張機能です。 永続化拡張機能は、ワークフローの状態を永続ストアに保存し、必要なときにその状態を取得するためのコア機能を提供します。  フレームワークに付属する永続化拡張機能には Microsoft SQL Server のサポートが含まれていますが、拡張機能は他のデータベース システムまたは永続ストアをサポートするように記述できます。 

永続性

永続化拡張機能を使用するには、まず状態を保持するようにデータベースを設定する必要があります。これは、%windir%\Microsoft.NET\Framework\v4.0.30319\sql\<language> (例: c:\windows\microsoft.net\framework\v4.0.30319\sql\en\) にある SQL スクリプトを使用して実行できます。 データベースを作成した後、2 つのスクリプトを実行して、データベース内に構造 (SqlWorkflowInstanceStoreSchema.sql) とストアド プロシージャ (SqlWorkflowInstanceStoreLogic.sql) の両方を作成します。 

データベースをセットアップしたら、WorkflowApplication クラスと共に SqlWorkflowInstanceStore を使用します。  最初にストアを作成して構成し、図 25 に示すように WorkflowApplication に指定します。

WorkflowApplication アプリケーション = 新しい WorkflowApplication(activity);

InstanceStore instanceStore = 新しい SqlWorkflowInstanceStore(

    @"Data Source=.\\SQLEXPRESS;Integrated Security=True");

InstanceView ビュー = instanceStore.Execute(

    instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(),

    TimeSpan.FromSeconds(30));

instanceStore.DefaultInstanceOwner = view。InstanceOwner;

アプリケーション。InstanceStore = instanceStore;

図 25: SQL 永続化プロバイダーの追加

ワークフローを永続化するには、2 つの方法があります。  1 つ目は、ワークフローで Persist アクティビティを直接使用することです。  このアクティビティを実行すると、ワークフローの状態がデータベースに永続化され、ワークフローの現在の状態が保存されます。  これにより、ワークフローの現在の状態を保存することが重要な場合に、ワークフロー作成者が制御できるようになります。  2 番目のオプションを使用すると、ワークフロー インスタンスでさまざまなイベントが発生したときに、ホスティング アプリケーションでワークフローの状態を保持できます。最も可能性の高いのは、ワークフローがアイドル状態の場合です。  WorkflowApplication で PersistableIdle アクションに登録することで、ホスト コードは イベントに応答して、インスタンスを永続化、アンロード、またはどちらも行う必要があるかどうかを示すことができます。  図 26 は、ワークフローがアイドル状態で永続化されたときに通知を受け取るために登録されているホスト アプリケーションを示しています。 

Wf。PersistableIdle = (waie) => PersistableIdleAction.Persist;

図 26: ワークフローがアイドル状態になったときのアンロード

ワークフローがアイドル状態になり、永続化されると、WF ランタイムはメモリからワークフローをアンロードし、処理が必要な他のワークフローのリソースを解放できます。  PersistableIdle アクションから適切な列挙を返すことで、ワークフロー インスタンスをアンロードするように選択できます。  ワークフローがアンロードされると、ある時点でホストは永続化ストアから読み込み直して再開します。  そのためには、インスタンス ストアとインスタンス識別子を使用してワークフロー インスタンスを読み込む必要があります。 これにより、インスタンス ストアに状態の読み込みが求められます。  状態が読み込まれると、WorkflowApplication の Run メソッドを使用するか、図 27 に示すようにブックマークを再開できます。  ブックマークの詳細については、「カスタム アクティビティ」セクションを参照してください。 

WorkflowApplication アプリケーション = 新しい WorkflowApplication(activity);

アプリケーション。InstanceStore = instanceStore;

アプリケーション。Load(id);

アプリケーション。ResumeBookmark(readLineBookmark, input);

図 27: 永続化されたワークフローの再開

WF4 の変更点の 1 つは、ワークフローの状態がどのように永続化され、引数と変数の新しいデータ管理手法に大きく依存しているかです。  アクティビティ ツリー全体をシリアル化し、ワークフローの有効期間にわたって状態を維持するのではなく、スコープ変数と引数の値と共に、ブックマーク情報などの一部のランタイム データのみが永続化されます。  図 27 では、永続化されたインスタンスが再読み込みされると、インスタンス ストアの使用に加えて、ワークフローを定義するアクティビティも渡されます。 基本的に、データベースからの状態は、指定されたアクティビティに適用されます。  この手法により、バージョン管理の柔軟性が大幅に向上し、状態のメモリ占有領域が小さくなり、パフォーマンスが向上します。 

追跡

永続化により、ホストは実行時間の長いプロセスをサポートし、ホスト間でインスタンスを負荷分散し、その他のフォールト トレラントな動作をサポートできます。  ただし、ワークフロー インスタンスが完了すると、データベース内の状態は役に立たなくなったため、多くの場合削除されます。  運用環境では、ワークフローの現在の実行内容と実行内容に関する情報を取得することは、ワークフローの管理とビジネス プロセスに関する分析情報の取得の両方にとって重要です。  アプリケーションで何が起こっているかを追跡できることは、WF ランタイムを使用する魅力的な機能の 1 つです。  

追跡は、参加者の追跡と追跡プロファイルの 2 つの主要なコンポーネントで構成されます。  追跡プロファイルは、ランタイムで追跡するイベントとデータを定義します。プロファイルには、主に次の 3 種類のクエリを含めることができます。

  • ActivityStateQuery – アクティビティの状態 (閉じているなど) と、データを抽出するための変数または引数を識別するために使用されます
  • WorkflowInstanceQuery – ワークフロー イベントを識別するために使用されます
  • CustomTrackingQuery – 通常はカスタム アクティビティ内でデータを追跡するための明示的な呼び出しを識別するために使用されます

たとえば、図 28 は、ActivityStateQuery と WorkflowInstanceQuery を含む TrackingProfile が作成されていることを示しています。   クエリは、情報を収集するタイミングと収集するデータの両方を示していることに注意してください。  ActivityStateQuery の場合、変数の値を抽出して追跡対象データに追加する必要がある変数の一覧を含めていました。 

TrackingProfile プロファイル = 新しい TrackingProfile

{

    Name = "SimpleProfile",

    クエリ = {

        新しい WorkflowInstanceQuery {

            States = { "*" }

        },

        new ActivityStateQuery {

            ActivityName = "WriteLine",

            States={ "*" },

            Variables = {"Text" }

        }

    }

};

図 28: 追跡プロファイルの作成

追跡参加要素は、ランタイムに追加できる拡張機能であり、出力された追跡レコードの処理を担当します。  TrackingParticipant 基本クラスは、TrackingProfile と追跡を処理する Track メソッドを提供するプロパティを定義します。  図 29 は、コンソールにデータを書き込む制限付きカスタム追跡参加要素を示しています。  追跡参加要素を使用するには、追跡プロファイルを使用して初期化してから、ワークフロー インスタンスの extensions コレクションに追加する必要があります。 

public クラス ConsoleTrackingParticipant : TrackingParticipant

{

   protected override void Track(TrackingRecord record, TimeSpan timeout)

   {

      ActivityStateRecord aRecord = ActivityStateRecord としてのレコード。

      if (aRecord != null)

      {

         Console.WriteLine(" が{0} 状態 {1}",

            aRecord.Activity.Name、aRecord.State);

         foreach (var item in aRecord.Arguments)

         {

            Console.ForegroundColor = ConsoleColor.Cyan;

            Console.WriteLine("Variable:{0} has value: {1}",

                項目。キー、項目。値);

            Console.ResetColor();

         }

      }

   }

}

図 29: コンソール追跡参加者

WF には EtwTrackingParticipant (ETW = Enterprise Trace for Windows) が付属しています。これをランタイムに追加して、データのハイ パフォーマンス追跡を有効にすることができます。  ETW は、Windows のネイティブ コンポーネントであるトレース システムであり、ドライバーやその他のカーネル レベル のコードなど、OS 内の多くのコンポーネントとサービスで使用されます。  ETW に書き込まれるデータは、カスタム コードを使用するか、今後の Windows Server AppFabric などのツールを使用して使用できます。  WF3 から移行するユーザーの場合、フレームワークの一部として SQL 追跡参加要素が配布されないため、これは変更になります。  ただし、Windows Server AppFabric には、ETW データを収集して SQL データベースに格納する ETW コンシューマーが付属します。  同様に、ETW コンシューマーを構築し、任意の形式でデータを格納したり、環境にとってより理にかなっている独自の追跡参加要素を作成したりできます。 

カスタム アクティビティの作成

基本アクティビティ ライブラリには、サービス、オブジェクト、コレクションを操作するためのアクティビティの豊富なパレットが含まれていますが、データベース、電子メール サーバー、カスタム ドメイン オブジェクトやシステムなどのサブシステムと対話するためのアクティビティは提供されません。  WF4 の使用を開始する一部として、ワークフローの構築時に必要な主要なアクティビティや必要なアクティビティを把握します。  優れた点は、コア作業単位を構築する際に、開発者がワークフローで使用できるより粗い粒度のアクティビティに組み合わせることができることです。 

アクティビティ クラス階層

アクティビティはアクティビティですが、すべてのアクティビティが同じ要件またはニーズを持っているわけではありません。  そのため、Activity から直接派生するすべてのアクティビティではなく、図 30 に示すアクティビティ基本クラスの階層があり、カスタム アクティビティを構築するときに選択できます。 

図 30: アクティビティ クラス階層

ほとんどのカスタム アクティビティでは、AsyncCodeActivity、CodeActivity、NativeActivity (またはジェネリックバリアントのいずれか) から派生するか、アクティビティを宣言的にモデル化します。  大まかに言うと、4 つの基本クラスは次のように記述できます。

  • アクティビティ – 他のアクティビティを作成してアクティビティをモデル化するために使用されます。通常は XAML を使用して定義されます。
  • CodeActivity – 作業を完了するためにコードを記述する必要がある場合に簡略化された基本クラス。
  • AsyncCodeActivity – アクティビティが非同期的に何らかの作業を実行するときに使用されます。
  • NativeActivity – アクティビティがランタイム内部にアクセスする必要がある場合 (たとえば、他のアクティビティのスケジュールやブックマークの作成など)。

次のセクションでは、これらの各基底クラスの使用方法を確認するために、いくつかのアクティビティを構築します。  一般に、どの基底クラスを使用するかを考える際には、まず Activity 基本クラスから始めて、次のセクションで示すように宣言型ロジックを使用してアクティビティをビルドできるかどうかを確認する必要があります。  次に、タスクを実行するために .NET コードを記述する必要があると判断した場合は CodeActivity によって簡略化されたモデルが提供され、アクティビティを非同期的に実行する場合は AsyncCodeActivity が提供されます。  最後に、フレームワークで見つかったような制御フロー アクティビティ (While、Switch、If など) を記述する場合は、子アクティビティを管理するために NativeActivity 基底クラスから派生する必要があります。 

アクティビティ デザイナーを使用したアクティビティの作成

新しいアクティビティ ライブラリ プロジェクトを作成するとき、または WF プロジェクトに新しい項目を追加してアクティビティ テンプレートを選択すると、空の Activity 要素を含む XAML ファイルが取得されます。  デザイナーでは、これはアクティビティの本文を作成できるデザイン サーフェイスとして表示されます。  複数のステップを含むアクティビティを開始するには、通常、Sequence アクティビティを Body としてドラッグし、本文が 1 つの子アクティビティを表す実際のアクティビティ ロジックを設定するのに役立ちます。 

アクティビティは、引数を持つコンポーネントのメソッドと同じように考えることができます。  アクティビティ自体で、引数とその方向性を定義して、アクティビティのインターフェイスを定義できます。  アクティビティ内で使用する変数は、前に説明したルート シーケンスなど、本文を構成するアクティビティで定義する必要があります。  たとえば、GetManager と SendMail という 2 つの単純なアクティビティを構成する NotifyManager アクティビティを構築できます。 

まず、Visual Studio 2010 で新しい ActivityLibrary プロジェクトを作成し、Activity1.xaml ファイルの名前を NotifyManager.xaml に変更します。  次に、ツールボックスから Sequence アクティビティをドラッグし、デザイナーに追加します。  シーケンスが設定されたら、図 31 に示すように、子アクティビティを設定できます。 (この例で使用するアクティビティは、参照先プロジェクトのカスタム アクティビティであり、フレームワークでは使用できない点に注意してください)。

図 31: マネージャーアクティビティに通知する

このアクティビティでは、従業員名とメッセージ本文の引数を受け取る必要があります。 GetManager アクティビティは、従業員の上司を検索し、OutArgument<文字列として電子メールを提供します>。  最後に、SendMail アクティビティがマネージャーにメッセージを送信します。  次の手順では、デザイナーの下部にある [引数] ウィンドウを展開し、必要な 2 つの入力引数の情報を入力して、アクティビティの引数を定義します。 

これらの項目を作成するには、NotifyManager アクティビティで指定された入力引数を個々の子アクティビティに渡すことができる必要があります。  GetManager アクティビティには、アクティビティの in 引数である従業員名が必要です。 引数名は、図 31 に示すように、GetManager アクティビティの employeeName 引数のプロパティ ダイアログで使用できます。  SendMail アクティビティには、上司のメール アドレスとメッセージが必要です。  メッセージには、メッセージを含む引数の名前を入力できます。  ただし、電子メール アドレスの場合は、GetManager アクティビティから SendMail アクティビティの in 引数に out 引数を渡す方法が必要です。  このためには変数が必要です。 

Sequence アクティビティを強調表示した後、[変数] ウィンドウを使用して、"mgrEmail" という名前の変数を定義できます。  これで、GetManager アクティビティの ManagerEmail out 引数と SendMail アクティビティの To in 引数の両方にその変数名を入力できます。  GetManager アクティビティが実行されると、出力データはその変数に格納され、SendMail アクティビティが実行されると、その変数から in 引数としてデータが読み取られます。 

先ほど説明したアプローチは、アクティビティ本体を定義するための純粋に宣言型モデルです。  状況によっては、コードでアクティビティの本文を指定することもできます。  たとえば、アクティビティには、子アクティビティのセットを駆動するプロパティのコレクションが含まれる場合があります。一連の Assign アクティビティの作成を推進する名前付き値のセットは、コードの使用が推奨されるケースの 1 つです。  このような場合は、アクティビティから派生したクラスを記述し、Implementation プロパティにコードを記述して、アクティビティ (およびすべての子要素) を作成してアクティビティの機能を表すことができます。  最終的な結果はどちらの場合も同じです。ロジックは他のアクティビティを作成することによって定義され、本文が定義されるメカニズムのみが異なります。  図 32 は、コードで定義されているのと同じ NotifyManager アクティビティを示しています。 

public クラス NotifyManager : Activity

{

    public InArgument<string> EmployeeName { get; set; }

    public InArgument<string> Message { get; set; }

    保護されたオーバーライド Func<アクティビティ> の実装

    {

        get

        {

            return () =>

            {

                変数<文字列> mgrEmail =

                new Variable<string> { Name = "mgrEmail" };

                シーケンス s = 新しいシーケンス

                {

                    Variables = { mgrEmail },

                    アクティビティ = {

                        new GetManager {

                            EmployeeName =

                                new VisualBasicValue<string>("EmployeeName"),

                                Result = mgrEmail,

                        },

                        新しい SendMail {

                            ToAddress = mgrEmail,

                            MailBody = 新しい VisualBasicValue<string>("Message"),

                            From = "test@contoso.com",

                            件名 = "自動メール"

                        }

                    }

                };

                s を返します。

            };

        }

        set { base。Implementation = value;}

    }

}

図 32: コードを使用したアクティビティの構成

基底クラスが Activity であり、Implementation プロパティが 1 つの Activity を返す Func<アクティビティ> であることに注意してください。 このコードはワークフローの作成時に前に示したコードとよく似ていますが、どちらの場合もアクティビティを作成することが目標であるため、驚くべきことではありません。  さらに、コード アプローチの引数では、変数を使用して設定することも、2 つの子アクティビティで使用される EmployeeName 引数と Message 引数の場合と同様に、式を使用して 1 つの引数を別の引数に接続することもできます。 

カスタム アクティビティ クラスの作成

他のアクティビティを作成してアクティビティ ロジックを実行できないと判断した場合は、コード ベースのアクティビティを記述できます。  コードでアクティビティを記述する場合は、適切なクラスから派生し、引数を定義し、Execute メソッドをオーバーライドします。  Execute メソッドは、アクティビティが処理を行う時間になると、ランタイムによって呼び出されます。

前の例で使用した SendMail アクティビティをビルドするには、最初に基本の種類を選択する必要があります。  Activity 基本クラスを使用して SendMail アクティビティの機能を作成し、TryCatch<T> アクティビティと InvokeMethod アクティビティを作成することは可能ですが、ほとんどの開発者にとって、CodeActivity 基本クラスと NativeActivity 基本クラスのどちらかを選択し、実行ロジックのコードを記述する方が自然です。  このアクティビティはブックマークを作成したり、他のアクティビティをスケジュールしたりする必要がないため、CodeActivity 基本クラスから派生させることができます。  さらに、このアクティビティは 1 つの出力引数を返すので、OutArgument<TResult> を提供する CodeActivity<TResult> から派生する必要があります。  最初の手順では、電子メール プロパティに対して複数の入力引数を定義します。  次に、Execute メソッドをオーバーライドして、アクティビティの機能を実装します。  図 33 は、完了したアクティビティ クラスを示しています。 

public クラス SendMail : CodeActivity<SmtpStatusCode>

{

    public InArgument<string> To { get; set; }

    public InArgument<string> From { get; set; }

    public InArgument<string> Subject { get; set; }

    public InArgument<string> Body { get; set; }

    protected override SmtpStatusCode Execute(

    CodeActivityContext コンテキスト)

    {

        試す

        {

            SmtpClient クライアント = 新しい SmtpClient();

            クライアント。Send(

            From.Get(context), ToAddress.Get(context),

            Subject.Get(context), MailBody.Get(context));

        }

        catch (SmtpException smtpEx)

        {

            smtpEx.StatusCode を返します。

        }

        SmtpStatusCode.Ok を返します。

    }

}

図 33: SendMail カスタム アクティビティ

引数の使用に関して注意すべき点がいくつかあります。  InArgument<T> 型を使用して、いくつかの標準 .NET プロパティを宣言しました。  これは、コードベースのアクティビティが入力引数と出力引数を定義する方法です。  しかし、コード内では、これらのプロパティを参照して引数の値を取得することはできません。指定された ActivityContext を使用して値を取得するには、 引数をハンドルの一種として使用する必要があります。この場合は CodeActivityContext。  引数クラスの Get メソッドを使用して値を取得し、Set メソッドを使用して値を引数に割り当てます。 

アクティビティが Execute メソッドを完了すると、ランタイムはこれを検出し、アクティビティが完了したと見なして閉じます。  非同期または実行時間の長い作業の場合は、この動作を変更する方法があります。これについては、今後のセクションで説明しますが、この場合、メールが送信されると作業が完了します。 

ここでは、NativeActivity 基本クラスを使用して子アクティビティを管理するアクティビティを調べてみましょう。  特定の回数だけいくつかの子アクティビティを実行する反復子アクティビティを作成します。  まず、実行する反復回数を保持する引数、実行するアクティビティのプロパティ、および現在の反復回数を保持する変数が必要です。 Execute メソッドは BeginIteration メソッドを呼び出して最初のイテレーションを開始し、後続のイテレーションも同じ方法で呼び出されます。  図 34 では、変数が Get メソッドと Set メソッドを使用して引数と同じように操作されていることに注意してください。 

public クラス反復子 : NativeActivity

{

    public Activity Body { get; set; }

    public InArgument<int> RequestedIterations { get; set; }

    public Variable<int> CurrentIteration { get; set; }

    public Iterator()

    {

        CurrentIteration = new Variable<int> { Default = 0 };

    }

    protected override void Execute(NativeActivityContext context)

    {

        BeginIteration(context);

    }

    private void BeginIteration(NativeActivityContext context)

    {

        if (RequestedIterations.Get(context) > CurrentIteration.Get(context))

        {

            コンテキスト。ScheduleActivity(Body,

            new CompletionCallback(ChildComplete),

            new FaultCallback(ChildFaulted));

        }

    }

}

図 34: 反復子ネイティブ アクティビティ

BeginIteration メソッドをよく見ると、ScheduleActivity メソッドの呼び出しがわかります。  これは、アクティビティがランタイムと対話して別のアクティビティの実行をスケジュールする方法です。これは、NativeActivity から派生したこの機能が必要であるためです。  また、ActivityExecutionContext で ScheduleActivity メソッドを呼び出すと、2 つのコールバック メソッドが提供されることにも注意してください。  どのアクティビティが本文として使用されるか、完了するまでの時間がわからないため、WF は非常に非同期のプログラミング環境であるため、コールバックは、子アクティビティが完了したときにアクティビティに通知するために使用され、対応するコードを記述できます。

2 番目のコールバックは FaultCallback であり、子アクティビティが例外をスローした場合に呼び出されます。  このコールバックでは、例外を受け取り、ActivityFaultContext を介して、エラーが処理されたことをランタイムに示す機能を持ちます。これにより、アクティビティがバブルアップしたり、アクティビティの外に出されたりするのを防げます。 図 35 は、Iterator アクティビティのコールバック メソッドを示しています。このコールバック メソッドでは、次のイテレーションがスケジュールされ、FaultCallback によってエラーが処理され、実行が次のイテレーションに継続されます。

private void ChildComplete(NativeActivityContext context,

ActivityInstance インスタンス)

{

    CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);

    BeginIteration(context);

}

private void ChildFaulted(NativeActivityFaultContext context, Exception ex,

ActivityInstance インスタンス)

{

    CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);

    コンテキスト。HandleFault();

}

図 35: アクティビティ コールバック

アクティビティが実行時間の長い作業を行う必要がある場合は、ワークフロー スレッドを使用して他のアクティビティの処理を続行したり、他のワークフローの処理に使用したりできるように、作業を別のスレッドに渡すのが理想的です。  ただし、単にスレッドを起動してランタイムに制御を戻すだけで、作成したスレッドがランタイムによって監視されないため、問題が発生する可能性があります。  ランタイムがワークフローがアイドル状態であると判断した場合、ワークフローがアンロードされ、コールバック メソッドが破棄されるか、ランタイムがアクティビティが完了したと想定してワークフロー内の次のアクティビティに移動する可能性があります。  アクティビティで非同期プログラミングをサポートするには、AsyncCodeActivity または AsyncCodeActivity<T> から派生します。 

図 36 は、RSS フィードを読み込む非同期アクティビティの例を示しています。  execute メソッドのこのシグネチャは、IAsyncResult を返すという点で非同期アクティビティでは異なります。  非同期操作を開始するには、execute メソッドでコードを記述します。  EndExecute メソッドをオーバーライドして、非同期操作が完了したときにコールバックを処理し、実行の結果を返します。 

public sealed クラス GetFeed : AsyncCodeActivity<SyndicationFeed>

{

    public InArgument<Uri> FeedUrl { get; set; }

    protected override IAsyncResult BeginExecute(

    AsyncCodeActivityContext コンテキスト、AsyncCallback コールバック、

    オブジェクトの状態)

    {

        var req = (HttpWebRequest)HttpWebRequest.Create(

        FeedUrl.Get(context));

        Req。メソッド = "GET";

        コンテキスト。UserState = req;

        req を返します。BeginGetResponse(new AsyncCallback(callback), state);

    }

    protected override SyndicationFeed EndExecute(

    AsyncCodeActivityContext コンテキスト、IAsyncResult 結果)

    {

        HttpWebRequest req = context。UserState を HttpWebRequest として指定します。

        WebResponse wr = req。EndGetResponse(result);

        SyndicationFeed localFeed = SyndicationFeed.Load(

        XmlReader.Create(wr.GetResponseStream()));

        return localFeed;

    }

}

図 36: 非同期アクティビティの作成

追加のアクティビティの概念

これで、基底クラス、引数、変数を使用してアクティビティを作成し、実行を管理する基本を確認しました。アクティビティ開発で使用できるより高度な機能について簡単に説明します。 

ブックマークを使用すると、アクティビティ作成者はワークフローの実行に再開ポイントを作成できます。  ブックマークが作成されたら、ブックマークを再開して、中断した場所からワークフロー処理を続行できます。  ブックマークは状態の一部として保存されるため、非同期アクティビティとは異なり、ブックマークが作成された後は、ワークフロー インスタンスが永続化およびアンロードされる問題ではありません。  ホストがワークフローを再開する場合は、ワークフロー インスタンスを読み込み、ResumeBookmark メソッドを呼び出して、中断した場所からインスタンスを再開します。  ブックマークを再開すると、データをアクティビティにも渡すことができます。   図 37 は、入力を受け取るブックマークを作成し、データが到着したときに呼び出されるコールバック メソッドを登録する ReadLine アクティビティを示しています。  ランタイムは、アクティビティに未処理のブックマークがある場合を認識し、ブックマークが再開されるまでアクティビティを閉じません。  ResumeBookmark メソッドを WorkflowApplication クラスで使用して、名前付きブックマークにデータを送信し、BookmarkCallback に通知できます。  

public クラス ReadLine : NativeActivity<文字列>

{

    public OutArgument<string> InputText { get; set; }

    protected override void Execute(NativeActivityContext context)

    {

        コンテキスト。CreateBookmark("ReadLine",

        new BookmarkCallback(BookmarkResumed);

    }

    private void BookmarkResumed(NativeActivityContext context,

        ブックマーク bk、オブジェクトの状態)

    {

        Result.Set(context, state);

    }

}

図 37: ブックマークの作成

アクティビティ作成者のためのもう 1 つの強力な機能は、ActivityAction の概念です。  ActivityAction は、命令型コードの Action クラスに相当するワークフローです。共通のデリゲートを記述します。しかし、一部の人にとっては、テンプレートの考え方が理解しやすいかもしれません。  一連のデータを反復処理し、各データ項目の子アクティビティをスケジュールできる ForEach<T> アクティビティについて考えてみましょう。  アクティビティのコンシューマーが本文を定義し、T 型のデータ項目を使用できるようにする何らかの方法が必要です。基本的には、T 型の項目を受け入れてそれに対処できるアクティビティが必要です。ActivityAction<T> は、このシナリオを有効にするために使用され、項目を処理するための引数とアクティビティの定義への参照を提供します。  カスタム アクティビティで ActivityAction を使用して、アクティビティのコンシューマーが適切なポイントに独自のステップを追加できるようにします。  これにより、ある種のワークフローまたはアクティビティ テンプレートを作成できます。このテンプレートでは、コンシューマーが関連する部分を入力して、使用する実行をカスタマイズできます。  アクティビティを呼び出す必要があるデリゲートが値を返す場合は、ActivityFunc<TResult> と関連する代替手段を使用することもできます。  

最後に、アクティビティを検証して、個々の設定を確認したり、許容される子アクティビティを制限したりすることで、アクティビティが正しく構成されていることを確認できます。一般的に特定の引数を要求する必要がある場合は、アクティビティのプロパティ宣言に RequiredArgument 属性を追加できます。  より複雑な検証を行うために、アクティビティのコンストラクターで制約を作成し、それを Activity クラスに表示される制約のコレクションに追加します。  制約は、ターゲット アクティビティを検査し、有効性を確認するために記述されたアクティビティです。  図 38 は、RequestedIterations プロパティが設定されていることを検証する Iterator アクティビティのコンストラクターを示しています。 最後に、CacheMetadata メソッドをオーバーライドして、ActivityMetdata パラメーターで AddValidationError メソッドを呼び出して、アクティビティの検証エラーを追加できます。 

var act = new DelegateInArgument<Iterator> { Name = "constraintArg" };

var vctx = new DelegateInArgument<ValidationContext>();

制約<反復子> の短所 = 新しい制約<反復子>

{

    Body = 新しい ActivityAction<反復子、ValidationContext>

    {

        Argument1 = act、

        Argument2 = vctx、

        Handler = 新しい AssertValidation

        {

            Message = "Iteration must be provided",

            PropertyName = "RequestedIterations",

            Assertion = new InArgument<bool>(

                (e) => act。Get(e)。RequestedIterations != null)

        }

    }

};

ベース。Constraints.Add(cons);

図 38: 制約

アクティビティ デザイナー

WF の利点の 1 つは、アプリケーション ロジックを宣言的にプログラミングできることですが、ほとんどのユーザーは手動で XAML を記述したくないので、WF でのデザイナー エクスペリエンスが非常に重要です。  カスタム アクティビティを構築するときに、アクティビティのコンシューマーに表示と視覚的な対話エクスペリエンスを提供するデザイナーを作成する必要がある場合もあります。  WF のデザイナーは、Windows Presentation Foundationに基づいています。つまり、スタイル、トリガー、データバインド、およびデザイナー用の豊富な UI を構築するためのその他すべての優れたツールのすべての機能を備えています。  さらに、WF には、個々の子アクティビティまたはアクティビティのコレクションを表示するタスクを簡略化するためにデザイナーで使用できるユーザー コントロールがいくつか用意されています。  4 つの主なコントロールは次のとおりです。

  • ActivityDesigner – アクティビティ デザイナーで使用されるルート WPF コントロール
  • WorkflowItemPresenter – 1 つのアクティビティを表示するために使用されます
  • WorkflowItemsPresenter – 子アクティビティのコレクションを表示するために使用されます
  • ExpressionTextBox – 引数などの式のインプレース編集を有効にするために使用されます

WF4 には、最初に成果物を作成するために使用できる ActivityDesignerLibrary プロジェクト テンプレートと ActivityDesigner 項目テンプレートが含まれています。  デザイナーを初期化したら、上記の要素を使用して、子アクティビティのデータと視覚的な表現を表示する方法をレイアウトすることで、アクティビティの外観をカスタマイズできます。  デザイナー内では、実際のアクティビティを抽象化し、アクティビティのすべてのプロパティを表示する ModelItem にアクセスできます。 

WorkflowItemPresenter コントロールを使用すると、アクティビティの一部である単一のアクティビティを表示できます。  たとえば、前に示した Iterator アクティビティにアクティビティをドラッグし、Iteractor アクティビティに含まれるアクティビティを表示する機能を提供するには、図 39 に示す XAML を使用して、WorkflowItemPresenter を ModelItem.Body にバインドします。  図 40 は、ワークフロー デザインサーフェイスで使用されているデザイナーを示しています。 

<sap:ActivityDesigner x:Class="CustomActivities.Presentation.IteratorDesigner"

  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

  xmlns:sap="clr-namespace:System.Activities.Presentation;

assembly=System.Activities.Presentation"

  xmlns:sapv="clr-namespace:System.Activities.Presentation.View;

assembly=System.Activities.Presentation">

  <グリッド>

    <Border BorderBrush="MidnightBlue" BorderThickness="3" CornerRadius="10">

<sap:WorkflowItemPresenter MinHeight="50"

Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"

HintText="ここに本文を追加" />

    </国境>

  </グリッド>

</sap:ActivityDesigner>

図 39: アクティビティ デザイナーでの WorkflowItemPresenter

図 40: 反復子デザイナー

WorkflowItemsPresenter には、シーケンス内の子などの複数の項目を表示するための同様の機能が用意されています。 項目の表示方法のテンプレートと向きを定義したり、コネクタ、矢印などのアクティビティ間に視覚的な要素を追加したりするためのスペーサー テンプレートを定義できます。  図 41 は、それぞれの間に水平線を持つ子アクティビティを表示するカスタム シーケンス アクティビティの単純なデザイナーを示しています。

<sap:ActivityDesigner x:Class="CustomActivities.Presentation.CustomSequenceDesigner"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:sap="clr-namespace:System.Activities.Presentation;

assembly=System.Activities.Presentation"

    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;

assembly=System.Activities.Presentation">

    <グリッド>

      <StackPanel>

         <Border BorderBrush="Goldenrod" BorderThickness="3">

     <sap:WorkflowItemsPresenter HintText="Drop Activities Here"

  Items="{Binding Path=ModelItem.ChildActivities}">

     <sap:WorkflowItemsPresenter.SpacerTemplate>

  <DataTemplate>

    <Rectangle Height="3" Width="40"

 Fill="MidnightBlue" Margin="5" />

                     </Datatemplate>

                  </sap:WorkflowItemsPresenter.SpacerTemplate>

                  <sap:WorkflowItemsPresenter.ItemsPanel>

                      <ItemsPanelTemplate>

                          <StackPanel Orientation="Vertical"/>

                      </ItemsPanelTemplate>

                   </sap:WorkflowItemsPresenter.ItemsPanel>

            </sap:WorkflowItemsPresenter>

        </国境>

      </Stackpanel>

    </グリッド>

</sap:ActivityDesigner>

図 41: WorkflowItemsPresenter を使用したカスタム シーケンス デザイナー

図 42: シーケンス デザイナー

最後に、ExpressionTextBox コントロールを使用すると、プロパティ グリッドに加えて、デザイナー内のアクティビティ引数をインプレース編集できます。 Visual Studio 2010 では、入力される式に対する Intellisense のサポートが含まれます。 図 43 は、先ほど作成した GetManager アクティビティのデザイナーを示しています。これにより、デザイナー内で EmployeeName 引数と ManagerEmail 引数を編集できます。 実際のデザイナーを図 44 に示します。 

<sap:ActivityDesigner x:Class="CustomActivities.Presentation.GetManagerDesigner"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:sap="clr-namespace:System.Activities.Presentation;

assembly=System.Activities.Presentation"

    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;

assembly=System.Activities.Presentation"

xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;

assembly=System.Activities.Presentation">

    <sap:ActivityDesigner.Resources>

        <sapc:ArgumentToExpressionConverter

x:Key="ArgumentToExpressionConverter"

x:Uid="swdv:ArgumentToExpressionConverter_1" />

    </sap:ActivityDesigner.Resources>

    <グリッド>

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="50" />

             <ColumnDefinition Width="*" />

        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>

            <RowDefinition />

            <RowDefinition />

            <RowDefinition />

        </Grid.RowDefinitions>

        <TextBlock VerticalAlignment="Center"

 HorizontalAlignment="Center">Employee:</TextBlock>

        <sapv:ExpressionTextBox Grid.Column="1"

              Expression="{Binding Path=ModelItem.EmployeeName, Mode=TwoWay,

       Converter={StaticResource ArgumentToExpressionConverter},

       ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"

MinLines="1" MaxLines="1" MinWidth="50"

HintText="&lt;従業員名&gt;"/>

        <TextBlock Grid.Row="1" VerticalAlignment="Center"

              HorizontalAlignment="Center">Mgr Email:</TextBlock>

        <sapv:ExpressionTextBox Grid.Column="2" Grid.Row="1"

    UseLocationExpression="True"

           Expression="{Binding Path=ModelItem.ManagerEmail, Mode=TwoWay,

    Converter={StaticResource ArgumentToExpressionConverter},

    ConverterParameter=Out}" OwnerActivity="{Binding Path=ModelItem}"

    MinLines="1" MaxLines="1" MinWidth="50" />

    </グリッド>

</sap:ActivityDesigner>

図 43: ExpressionTextBox を使用してデザイナーで引数を編集する

図 44: GetManager アクティビティ デザイナー

ここで紹介するデザイナーの例は、特定の機能を強調するのは簡単ですが、WPF の完全な機能を自由に使用して、アクティビティの構成を容易にし、コンシューマーに対してより自然に使用できます。   デザイナーを作成したら、アクティビティに関連付ける方法として、アクティビティ クラスに属性を適用する方法と、IRegisterMetadata インターフェイスを実装する方法の 2 つがあります。  アクティビティ定義でデザイナーの選択を促すには、DesignerAttribute をアクティビティ クラスに適用し、デザイナーの型を指定します。これは WF3 とまったく同じように機能します。  疎結合の実装では、IRegisterMetadata インターフェイスを実装し、アクティビティ クラスとプロパティに適用する属性を定義できます。  図 45 は、2 つのカスタム アクティビティにデザイナーを適用する実装例を示しています。 Visual Studio は実装を検出して自動的に呼び出しますが、次に説明するカスタム デザイナー ホストは メソッドを明示的に呼び出します。

public クラス メタデータ : IRegisterMetadata

{

    public void Register()

    {

        AttributeTableBuilder ビルダー = 新しい AttributeTableBuilder();

        ビルダー。AddCustomAttributes(typeof(SendMail), new Attribute[] {

            new DesignerAttribute(typeof(SendMailDesigner))});

        ビルダー。AddCustomAttributes(typeof(GetManager), new Attribute[]{

            new DesignerAttribute(typeof(GetManagerDesigner))});

        MetadataStore.AddAttributeTable(builder.CreateTable());

    }

}

図 45: アクティビティにデザイナーを登録する

ワークフロー デザイナーのリホスト

以前は、開発者は多くの場合、ワークフローを作成または編集する機能をユーザーに提供したいと考えていました。  以前のバージョンの Windows Workflow Foundation では、これは可能でしたが、簡単な作業ではありません。  WPF への移行に伴い、新しいデザイナーが登場し、リホストは開発チームにとって主要なユース ケースでした。  図 46 に示すようなカスタム WPF アプリケーションでデザイナーをホストし、数行の XAML と数行のコードを使用できます。 

図 46: リホストされたワークフロー デザイナー

ウィンドウの XAML (図 47) は、グリッドを使用して 3 つの列をレイアウトし、各セルに罫線コントロールを配置します。  最初のセルでは、ツールボックスは宣言によって作成されますが、コードで作成することもできます。  デザイナー ビュー自体とプロパティ グリッドには、残りの各セルに 2 つの空の罫線コントロールがあります。  これらのコントロールはコードに追加されます。 

<ウィンドウ x:Class="WorkflowEditor.MainWindow"

        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:sys="clr-namespace:System;assembly=mscorlib"

 xmlns:sapt="clr-namespace:System.Activities.Presentation.Toolbox;

assembly=System.Activities.Presentation"

        Title="Workflow Editor" Height="500" Width="700" >

    <Window.Resources>

        <sys:String x:Key="AssemblyName">System.Activities, Version=4.0.0.0,

Culture=neutral,PublicKeyToken=31bf3856ad364e35</sys:String>

        <sys:String x:Key="MyAssemblyName">CustomActivities</sys:String>

    </Window.Resources>

    <Grid x:Name="DesignerGrid">

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="2*" />

            <ColumnDefinition Width="7*" />

            <ColumnDefinition Width="3*" />

        </Grid.ColumnDefinitions>

        <Border>

            <sapt:ToolboxControl>

                <sapt:ToolboxControl.Categories>

                    <sapt:ToolboxCategory CategoryName="Basic">

                        <sapt:ToolboxItemWrapper

  AssemblyName="{StaticResource AssemblyName}" >

                            <sapt:ToolboxItemWrapper.ToolName>

                                System.Activities.Statements.Sequence

                            </sapt:ToolboxItemWrapper.ToolName>

                        </sapt:ToolboxItemWrapper>

                        <sapt:ToolboxItemWrapper

AssemblyName="{StaticResource MyAssemblyName}">

                            <sapt:ToolboxItemWrapper.ToolName>

                                CustomActivities.SendMail

                             </sapt:ToolboxItemWrapper.ToolName>

                        </sapt:ToolboxItemWrapper>

                    </sapt:ToolboxCategory>

                </sapt:ToolboxControl.Categories>

            </sapt:ToolboxControl>

        </国境>

        <Border Name="DesignerBorder" Grid.Column="1"

BorderBrush="Salmon" BorderThickness="3" />

        <Border x:Name="PropertyGridExpander" Grid.Column="2" />

    </グリッド>

</ウィンドウ>

図 47: XAML のリホストDesigner

デザイナーとプロパティ グリッドを初期化するコードを図 48 に示します。  OnInitialized メソッドの最初の手順は、ワークフロー デザイナーで使用されるすべてのアクティビティのデザイナーを登録することです。  DesignerMetadata クラスは、フレームワークに付属するアクティビティに関連付けられたデザイナーを登録し、Metadata クラスはカスタム アクティビティのデザイナーを登録します。  次に、WorkflowDesigner クラスを作成し、View プロパティと PropertyInspectorView プロパティを使用して、レンダリングに必要な UIElement オブジェクトにアクセスします。  デザイナーは空の Sequence で初期化されますが、メニューを追加したり、ファイル システムやデータベースなどのさまざまなソースからワークフロー XAML を読み込んだりすることもできます。 

public MainWindow()

{

    InitializeComponent();

}

protected override void OnInitialized(EventArgs e) {

    ベース。OnInitialized(e);

    RegisterDesigners();

    PlaceDesignerAndPropGrid();

}

private void RegisterDesigners() {

    新しい DesignerMetadata()。Register();

    新しい CustomActivities.Presentation.Metadata()。Register();

}

private void PlaceDesignerAndPropGrid() {

    WorkflowDesigner デザイナー = 新しい WorkflowDesigner();

    デザイナー。Load(new System.Activities.Statements.Sequence());

    DesignerBorder.Child = designer。ビュー;

    PropertyGridExpander.Child = designer。PropertyInspectorView;

}

図 48: コードのリホストDesigner

この少しのコードとマークアップを使用すると、ワークフローの編集、ツールボックスからのアクティビティのドラッグ アンド ドロップ、ワークフロー内の変数の構成、アクティビティの引数の操作を行うことができます。  デザイナーで Flush を呼び出し、WorkflowDesigner の Text プロパティを参照することで、XAML のテキストを取得することもできます。  実行できる操作ははるかに多く、作業を開始する方が以前よりもずっと簡単です。 

ワークフロー サービス

前述のように、Windows Workflow Foundation のこのリリースにおける大きな重点領域の 1 つは、Windows Communication Foundation との緊密な統合です。  アクティビティのチュートリアルの例では、ワークフローからサービスを呼び出す方法を示しました。このセクションでは、ワークフローを WCF サービスとして公開する方法について説明します。 

開始するには、新しいプロジェクトを作成し、WCF ワークフロー サービス プロジェクトを選択します。 テンプレートが完成すると、web.config ファイルと XAMLX 拡張子を持つファイルを含むプロジェクトが見つかります。  XAMLX ファイルは宣言型のサービスまたはワークフロー サービスであり、他の WCF テンプレートと同様に、要求を受け取り、応答を返す、機能する単純なサービス実装を提供します。  この例では、契約を変更して経費報告書を受け取る操作を作成し、レポートが受信されたことを示すインジケーターを返します。  まず、図 49 に示すような ExpenseReport クラスをプロジェクトに追加します。

[DataContract]

public クラス ExpenseReport

{

    [DataMember]

    public DateTime FirstDateOfTravel { get; set; }

    [DataMember]

    public double TotalAmount { get; set; }

    [DataMember]

    public string EmployeeName { get; set; }

}

図 49: 経費明細書の契約の種類

ワークフロー デザイナーに戻り、変数の "data" 変数の型を、定義した ExpenseReport 型に変更します (型選択ダイアログに表示する型のプロジェクトをビルドする必要がある場合があります)。 [コンテンツ] ボタンをクリックし、ダイアログの種類を ExpenseReport の種類に変更して、受信アクティビティを更新します。  図 50 に示すように、アクティビティのプロパティを更新して、新しい OperationName と ServiceContractName を定義します。 

図 50: 受信場所の構成

これで、SendReply アクティビティで Value を True に設定して、領収書のインジケーターを返すことができます。  明らかに、より複雑なサンプルでは、ワークフローの完全な機能を使用して、データベースに情報を保存し、レポートの検証を行うことができます。応答を決定する前に。 WCFTestClient アプリケーションを使用してサービスを参照すると、図 51 に示すように、アクティビティ構成によって公開されるサービス コントラクトがどのように定義されるかが示されます。 

図 51: ワークフロー サービスから生成されたコントラクト

ワークフロー サービスを作成したら、WCF サービスと同様に、ワークフロー サービスをホストする必要があります。  前の例では、ホストに最も簡単なオプション IIS を使用しました。  独自のプロセスでワークフロー サービスをホストするには、System.ServiceModel.Activities アセンブリにある WorkflowServiceHost クラスを使用します。  System.WorkflowServices アセンブリには、WF3 アクティビティを使用して構築されたワークフロー サービスをホストするために使用される、この同じ名前の別のクラスがあることに注意してください。  セルフホスティングの最も簡単なケースでは、図 52 に示すようなコードを使用して、サービスをホストし、エンドポイントを追加できます。 

WorkflowServiceHost ホスト = new

    WorkflowServiceHost(XamlServices.Load("ExpenseReportService.xamlx"),

    new Uri("https://localhost:9897/Services/Expense"));

ホスト。AddDefaultEndpoints();

ホスト。Description.Behaviors.Add(

    新しい ServiceMetadataBehavior { HttpGetEnabled = true });

ホスト。Open();

Console.WriteLine("Host is open");

Console.ReadLine();

図 52: ワークフロー サービスのセルフホスティング

Windows でマネージド ホスティング オプションを利用する場合は、XAMLX ファイルをディレクトリにコピーし、動作、バインディング、エンドポイントなどに必要な構成情報を指定することで、IIS でサービスをホストできます。をweb.config ファイルに格納します。  実際、前述のように、宣言型ワークフロー サービス プロジェクト テンプレートのいずれかを使用して Visual Studio で新しいプロジェクトを作成すると、web.config ファイルと XAMLX で定義されたワークフロー サービスを含むプロジェクトが取得され、デプロイの準備が整います。 

WorkflowServiceHost クラスを使用してワークフロー サービスをホストする場合でも、ワークフロー インスタンスを構成および制御できる必要があります。  サービスを制御するために、WorkflowControlEndpoint という新しい標準エンドポイントがあります。  コンパニオン クラス WorkflowControlClient は、事前に構築されたクライアント プロキシ を提供します。  サービスで WorkFlowControlEndpoint を公開し、WorkflowControlClient を使用してワークフローの新しいインスタンスを作成および実行したり、実行中のワークフローを制御したりできます。  この同じモデルを使用してローカル コンピューター上のサービスを管理するか、リモート コンピューター上のサービスを管理するために使用できます。  図 53 は、サービスでワークフロー 制御エンドポイントを公開する更新された例を示しています。 

WorkflowServiceHost ホスト = new

    WorkflowServiceHost("ExpenseReportService.xamlx",

    new Uri("https://localhost:9897/Services/Expense"));

ホスト。AddDefaultEndpoints();

WorkflowControlEndpoint wce = 新しい WorkflowControlEndpoint(

    new NetNamedPipeBinding(),

    new EndpointAddress("net.pipe://localhost/Expense/WCE"));

ホスト。AddServiceEndpoint(wce);

ホスト。Open();

図 53: ワークフロー 制御エンドポイント

WorkflowInstance を直接作成していないため、拡張機能をランタイムに追加するには 2 つのオプションがあります。  1 つ目は、いくつかの拡張機能を WorkflowServiceHost に追加でき、ワークフロー インスタンスで正しく初期化されるということです。  2 つ目は、構成を使用することです。  たとえば、追跡システムは、アプリケーション構成ファイルで完全に定義できます。   構成ファイルで追跡参加者と追跡プロファイルを定義し、動作を使用して、図 54 に示すように特定の参加者をサービスに適用します。 

<system.serviceModel>

    <tracking>

      <participants>

        <add name="EtwTrackingParticipant"

             type="System.Activities.Tracking.EtwTrackingParticipant, System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

             profileName="HealthMonitoring_Tracking_Profile"/>

      </参加者>

      <profiles>

        <trackingProfile name="HealthMonitoring_Tracking_Profile">

          <workflow activityDefinitionId="*">

            <workflowInstanceQuery>

              <states>

                <state name="Started"/>

                <state name="Completed"/>

              </状態>

            </workflowInstanceQuery>

          </ワークフロー>

        </Trackingprofile>

      </プロファイル>

</追跡>

. . .

<behaviors>

      <serviceBehaviors>

        <behavior name="SampleTrackingSample.SampleWFBehavior">

          <etwTracking profileName=" HealthMonitoring_Tracking_Profile" />

        </動作>

      </serviceBehaviors>

</動作>

. . .

図 54: サービスの追跡の構成

".svc-less" サービスや、ファイル拡張子、既定のバインド、既定のエンドポイントを必要としないサービスなど、ワークフローで利用できる WCF サービスのホスティングと構成には、多くの新しい機能強化があります。  WCF4 のこれらの機能とその他の機能の詳細については、「その他のリソース」セクションにある WCF に関するコンパニオン ペーパーを参照してください。 

Windows Workflow Foundation は、ホスティングの機能強化に加えて、WCF の他の機能を利用します。一部の直接および他の間接的な。  たとえば、メッセージの関連付けは、注文番号や従業員 ID などのメッセージ内のデータに基づいて、特定のワークフロー インスタンスにメッセージを関連付けるサービスを構築する上で重要な機能になりました。関連付けは、要求と応答の両方に対してさまざまなメッセージング アクティビティで構成でき、メッセージ内の複数の値に対する関連付けをサポートします。  ここでも、これらの新機能の詳細については、WCF4 のコンパニオン ペーパーを参照してください。   

まとめ

.NET Framework 4 に付属する新しい Windows Workflow Foundation テクノロジは、パフォーマンスと開発者の生産性の大幅な向上を表し、ビジネス ロジック用の宣言型アプリケーション開発の目標を実現します。  このフレームワークは、複雑な作業の短い単位を簡略化するためのツールと、相関メッセージ、永続化された状態、および追跡による豊富なアプリケーションの可視性を使用して複雑な長時間実行サービスを構築するためのツールを提供します。 

著者について

Matt は Pluralsight の技術スタッフのメンバーで、接続システム テクノロジ (WCF、WF、BizTalk、AppFabric、Azure Services Platform) に重点を置いています。 Matt は、Microsoft .NET アプリケーションの設計と開発を専門とする独立したコンサルタントでもあります。 ライターとして、MSDN マガジンを含む複数のジャーナルや雑誌に投稿し、現在は Foundations 列のワークフロー コンテンツを執筆しています。 Matt は、Tech Ed などの地元、地域、および国際会議で講演することで、技術に対する彼の愛を定期的に共有しています。 Microsoft は、コネクテッド システム テクノロジに関するコミュニティ コントリビューションの MVP として Matt を認識しています。 彼のブログを介してマットにお問い合わせください: http://www.pluralsight.com/community/blogs/matt/default.aspx.

その他のリソース