アクティビティ検証の呼び出し

アクティビティの検証は、アクティビティを実行する前にアクティビティの構成エラーを特定および報告する手段です。 検証が発生するのは、ワークフローがワークフロー デザイナーで修正され、検証エラーまたは警告がワークフロー デザイナーに表示されたときです。 ワーク フローの呼び出し時に検証も行われます。検証エラーが発生すると、既定の検証ロジックによって InvalidWorkflowException がスローされます。 Windows Workflow Foundation (WF) には、アクティビティを明示的に検証するためにワークフロー アプリケーションやツールの開発者が使用できる ActivityValidationServices クラスが用意されています。 このトピックでは、ActivityValidationServices を使用してアクティビティの検証を実行する方法を説明します。

ActivityValidationServices の使用

ActivityValidationServices には、アクティビティの検証ロジックの呼び出しに使用される 2 つの Validate オーバーロードがあります。 1 つ目のオーバーロードは、検証されるルート アクティビティを受け取り、一連の検証エラーと警告を返します。 次の例では、2 つの必須引数を持つカスタムの Add アクティビティが使用されています。

public sealed class Add : CodeActivity<int>  
{  
    [RequiredArgument]  
    public InArgument<int> Operand1 { get; set; }  
  
    [RequiredArgument]  
    public InArgument<int> Operand2 { get; set; }  
  
    protected override int Execute(CodeActivityContext context)  
    {  
        return Operand1.Get(context) + Operand2.Get(context);  
    }  
}  

Add アクティビティが Sequence 内で使用されていますが、次の例に示すように、その 2 つの必須引数はバインドされていません。

Variable<int> Operand1 = new Variable<int>{ Default = 10 };  
Variable<int> Operand2 = new Variable<int>{ Default = 15 };  
Variable<int> Result = new Variable<int>();  
  
Activity wf = new Sequence  
{  
    Variables = { Operand1, Operand2, Result },  
    Activities =
    {  
        new Add(),  
        new WriteLine  
        {  
            Text = new InArgument<string>(env => "The result is " + Result.Get(env))  
        }  
    }  
};  

このワーク フローは、Validate を呼び出して検証できます。 Validate は、次の例に示すように、アクティビティと子に含まれる一連の検証エラーと警告を返します。

ValidationResults results = ActivityValidationServices.Validate(wf);  
  
if (results.Errors.Count == 0 && results.Warnings.Count == 0)  
{  
    Console.WriteLine("No warnings or errors");  
}  
else  
{  
    foreach (ValidationError error in results.Errors)  
    {  
        Console.WriteLine("Error: {0}", error.Message);  
    }  
    foreach (ValidationError warning in results.Warnings)  
    {  
        Console.WriteLine("Warning: {0}", warning.Message);  
    }  
}  

このサンプル ワークフローで Validate が呼び出されると、次の 2 つの検証エラーが返されます。

エラー: 必須のアクティビティ引数 'Operand2' の値が指定されませんでした。
エラー: 必須のアクティビティ引数 'Operand1' の値が指定されませんでした。 このワークフローが呼び出された場合、次の例に示すように、InvalidWorkflowException がスローされることになります。

try  
{  
    WorkflowInvoker.Invoke(wf);  
}  
catch (Exception ex)  
{  
    Console.WriteLine(ex);  
}  

System.Activities.InvalidWorkflowException:
ワークフロー ツリーの処理中に次のエラーが発生しました:'Add': 必須のアクティビティ引数 'Operand2' の値が指定されませんでした。'Add': 必須のアクティビティ引数 'Operand1' の値が指定されませんでした。このサンプル ワークフローを有効にするには、Add アクティビティの 2 つの必須引数をバインドする必要があります。 次の例では、2 つの必須引数と結果値がワークフロー変数にバインドされています。 この例では、2 つの必須変数に加え、Result 引数がバインドされています。 Result 引数はバインドする必要がなく、バインドされていなくても検証エラーは発生しません。 Result の値がワークフローの別の場所で使用される場合、この引数のバインドはワークフローの作成者が行う必要があります。

new Add  
{  
    Operand1 = Operand1,  
    Operand2 = Operand2,  
    Result = Result  
}  

ルート アクティビティでの必須引数の検証

ワーク フローのルート アクティビティに引数がある場合、ワーク フローが呼び出され、パラメーターがワーク フローに渡されるまでこれらの引数はバインドされません。 次のワーク フローは検証を渡しますが、次の例に示すように、ワーク フローが必要な引数に渡されずに呼び出されると、例外がスローされます。

Activity wf = new Add();  
  
ValidationResults results = ActivityValidationServices.Validate(wf);  
// results has no errors or warnings, but when the workflow  
// is invoked, an InvalidWorkflowException is thrown.  
try  
{  
    WorkflowInvoker.Invoke(wf);  
}  
catch (Exception ex)  
{  
    Console.WriteLine(ex);  
}  

System.ArgumentException: ルート アクティビティの引数の設定が正しくありません。
ワークフロー定義を修正するか、これらのエラーを修正する入力値を指定してください:'Add': 必須のアクティビティ引数 'Operand2' の値が指定されませんでした。'Add': 必須のアクティビティ引数 'Operand1' の値が指定されませんでした。正しい引数が渡された後、次の例に示すように、ワークフローは正常に完了します。

Add wf = new Add();  
  
ValidationResults results = ActivityValidationServices.Validate(wf);  
// results has no errors or warnings, and the workflow completes  
// successfully because the required arguments were passed.  
try  
{  
    Dictionary<string, object> wfparams = new Dictionary<string, object>  
    {  
        { "Operand1", 10 },  
        { "Operand2", 15 }  
    };  
  
    int result = WorkflowInvoker.Invoke(wf, wfparams);  
    Console.WriteLine("Result: {0}", result);  
}  
catch (Exception ex)  
{  
    Console.WriteLine(ex);  
}  

注意

この例では、前の例の Add とは異なり、ルート アクティビティは Activity として宣言されています。 このため、WorkflowInvoker.Invoke メソッドは、Add 引数のディクショナリではなく、out アクティビティの結果を表す 1 つの整数を返すことができます。 また、変数 wfActivity<int> として宣言することも可能です。

ルート引数を検証する場合、ワークフローが呼び出されたときにすべての必須変数が渡されたことを確認するのはホスト アプリケーションが担当します。

命令型コードに基づく検証の呼び出し

命令型コードに基づく検証は、アクティビティでアクティビティ自身に関する検証を可能にする簡単な方法を提供し、CodeActivityAsyncCodeActivity、および NativeActivity から派生するアクティビティで使用できます。 検証のエラーまたは警告を判断する検証コードがアクティビティに追加されます。 検証がアクティビティで呼び出されると、これらの警告またはエラーは、Validate への呼び出しで返されるコレクションに格納されます。 次の例では、 CreateProduct アクティビティを定義します。 CostPrice よりも高い場合、検証エラーが CacheMetadata オーバーライドのメタデータに追加されます。

public sealed class CreateProduct : CodeActivity  
{  
    public double Price { get; set; }  
    public double Cost { get; set; }  
  
    // [RequiredArgument] attribute will generate a validation error
    // if the Description argument is not set.  
    [RequiredArgument]  
    public InArgument<string> Description { get; set; }  
  
    protected override void CacheMetadata(CodeActivityMetadata metadata)  
    {  
        base.CacheMetadata(metadata);  
        // Determine when the activity has been configured in an invalid way.  
        if (this.Cost > this.Price)  
        {  
            // Add a validation error with a custom message.  
            metadata.AddValidationError("The Cost must be less than or equal to the Price.");  
        }  
    }  
  
    protected override void Execute(CodeActivityContext context)  
    {  
        // Not needed for the sample.  
    }  
}  

この例では、ワーク フローは CreateProduct のアクティビティを使用して構成されます。 このワーク フローで、CostPrice より大きく、必須の Description 引数は設定されません。 検証を呼び出すと、次のエラーが返されます。

Activity wf = new Sequence  
{  
    Activities =
    {  
        new CreateProduct  
        {  
            Cost = 75.00,  
            Price = 55.00  
            // Cost > Price and required Description argument not set.  
        },  
        new WriteLine  
        {  
            Text = "Product added."  
        }  
    }  
};  
  
ValidationResults results = ActivityValidationServices.Validate(wf);  
  
if (results.Errors.Count == 0 && results.Warnings.Count == 0)  
{  
    Console.WriteLine("No warnings or errors");  
}  
else  
{  
    foreach (ValidationError error in results.Errors)  
    {  
        Console.WriteLine("Error: {0}", error.Message);  
    }  
    foreach (ValidationError warning in results.Warnings)  
    {  
        Console.WriteLine("Warning: {0}", warning.Message);  
    }  
}  

エラー: コストを価格以下する必要があります。
エラー: 必須のアクティビティ引数 'Description' の値が指定されませんでした。

注意

カスタム アクティビティの作成者は、アクティビティの CacheMetadata オーバーライドに検証ロジックを指定できます。 CacheMetadata からスローされる例外は、検証エラーとして処理されません。 これらの例外は、Validate への呼び出しからエスケープされ、呼び出し元によって処理される必要があります。

ValidationSettings の使用

既定では、アクティビティ ツリー内のすべてのアクティビティは、検証が ActivityValidationServices に呼び出されたときに評価されます。 ValidationSettings を使用すると、その 3 つのプロパティを構成することによって、さまざまな方法で検証をカスタマイズできます。 SingleLevel は、バリデーターがアクティビティ ツリー全体を調べるか、指定したアクティビティにのみ検証ロジックを適用するかを指定します。 この値の既定値は、false です。 AdditionalConstraints は、型から制約のリストへの追加の制約マッピングを指定します。 検証されているアクティビティ ツリーの各アクティビティの基本型について、AdditionalConstraints への参照が行われます。 一致する制約リストが見つかると、アクティビティについて、リストのすべての制約が評価されます。 OnlyUseAdditionalConstraints は、バリデーターがすべての制約を評価するか、AdditionalConstraints で指定された制約のみを評価するかを指定します。 既定値は false です。 AdditionalConstraints および OnlyUseAdditionalConstraints は、FxCop のようなツールのポリシーの制約など、ワークフローの検証をワークフロー ホストの作成者がさらに追加する場合に便利です。 制約の詳細については、「宣言の制約」を参照してください。

ValidationSettings を使用するには、必要なプロパティを構成し、Validate の呼び出しで渡します。 この例では、カスタムの Sequence アクティビティを持つ Add で構成されるワークフローが検証されます。 この Add アクティビティには必須引数が 2 つあります。

public sealed class Add : CodeActivity<int>  
{  
    [RequiredArgument]  
    public InArgument<int> Operand1 { get; set; }  
  
    [RequiredArgument]  
    public InArgument<int> Operand2 { get; set; }  
  
    protected override int Execute(CodeActivityContext context)  
    {  
        return Operand1.Get(context) + Operand2.Get(context);  
    }  
}  

次の Add アクティビティは Sequence で使用されますが、その 2 つの必須変数はバインドされていません。

Variable<int> Operand1 = new Variable<int> { Default = 10 };  
Variable<int> Operand2 = new Variable<int> { Default = 15 };  
Variable<int> Result = new Variable<int>();  
  
Activity wf = new Sequence  
{  
    Variables = { Operand1, Operand2, Result },  
    Activities =
    {  
        new Add(),  
        new WriteLine  
        {  
            Text = new InArgument<string>(env => "The result is " + Result.Get(env))  
        }  
    }  
};  

次の例では、SingleLeveltrue に設定されて検証が実行されるため、ルート アクティビティ Sequence のみが検証されます。

ValidationSettings settings = new ValidationSettings  
{  
    SingleLevel = true  
};  
  
ValidationResults results = ActivityValidationServices.Validate(wf, settings);  
  
if (results.Errors.Count == 0 && results.Warnings.Count == 0)  
{  
    Console.WriteLine("No warnings or errors");  
}  
else  
{  
    foreach (ValidationError error in results.Errors)  
    {  
        Console.WriteLine("Error: {0}", error.Message);  
    }  
    foreach (ValidationError warning in results.Warnings)  
    {  
        Console.WriteLine("Warning: {0}", warning.Message);  
    }  
}  

このコードを実行すると、次の出力が表示されます。

警告やエラーはありませんAdd アクティビティにバインドされていない必須引数がある場合でも、ルート アクティビティのみが評価されるため、検証は正常に実行されています。 このタイプの検証は、デザイナーで 1 つのアクティビティのプロパティの変更を検証するなど、アクティビティ ツリーの特定の要素のみを検証する場合に便利です。 このワークフローが呼び出された場合、ワークフローで構成された完全な検証が評価され、InvalidWorkflowException がスローされます。 ActivityValidationServices および ValidationSettings で構成されるのは、ホストによって明示的に呼び出される検証だけであり、ワークフローが呼び出されたときに発生する検証ではありません。