Création de workflows, d'activités et d'expressions à l'aide du code impératif

Une définition de workflow est une arborescence d’objets d’activité configurés. Cette arborescence d'activités peut être définie de nombreuses façons, notamment en modifiant manuellement des données XAML ou en utilisant le Workflow Designer pour produire des données XAML. L'utilisation de XAML n'est toutefois pas impérative. Les définitions de workflow peuvent également être créées par programmation. Cette rubrique fournit une vue d'ensemble de la création des définitions, des activités et des expressions de workflow à l'aide du code. Pour obtenir des exemples d’utilisation de workflows XAML à l’aide de code, consultez Sérialisation des workflows et des activités vers et depuis XAML.

Création de définitions de workflow

Une définition de workflow peut être créée en instanciant une instance d'un type d'activité et configurant les propriétés de l'objet d'activité. Pour les activités qui ne contiennent pas d'activités enfants, cette opération peut être effectuée à l'aide de quelques lignes de code.

Activity wf = new WriteLine
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

Notes

Les exemples de cette rubrique utilisent WorkflowInvoker pour exécuter les exemples de workflow. Pour plus d’informations sur l’appel de workflow, le passage d’arguments et les différents choix d’hébergement disponibles, consultez Utilisation de WorkflowInvoker et WorkflowApplication.

Dans cet exemple, un workflow qui consiste en une activité WriteLine unique est créé. L'argument WriteLine de l'activité Text est défini et le workflow est appelé. Si une activité contient des activités enfants, la méthode de construction est semblable. L'exemple suivant utilise une activité Sequence qui contient deux activités WriteLine.

Activity wf = new Sequence
{
    Activities =
    {
        new WriteLine
        {
            Text = "Hello"
        },
        new WriteLine
        {
            Text = "World."
        }
    }
};

WorkflowInvoker.Invoke(wf);

Utilisation d'initialiseurs d'objets

Les exemples de cette rubrique utilisent la syntaxe d'initialisation d'objet. La syntaxe d'initialisation d'objet peut être une méthode utile pour créer des définitions de workflow dans le code, car elle fournit une vue hiérarchique des activités dans le workflow et affiche la relation entre les activités. Il n'est pas impératif d'utiliser la syntaxe d'initialisation d'objet lorsque vous créez des workflows par programmation. L'exemple suivant est d'un point de vue fonctionnel équivalent à l'exemple précédent :

WriteLine hello = new WriteLine();
hello.Text = "Hello";

WriteLine world = new WriteLine();
world.Text = "World";

Sequence wf = new Sequence();
wf.Activities.Add(hello);
wf.Activities.Add(world);

WorkflowInvoker.Invoke(wf);

Pour plus d’informations sur les initialiseurs d'objets, consultez Guide pratique : initialiser des objets sans appeler de constructeur (Guide de programmation C#) et Guide pratique : déclarer un objet à l'aide d'un initialiseur d'objet.

Utilisation de variables, de valeurs littérales et d'expressions

Lorsque vous créez une définition de workflow à l'aide de code, tenez compte de ce que le code exécute dans le cadre de la création de la définition de workflow et de ce qu'il exécute dans le cadre de l'exécution d'une instance de ce workflow. Par exemple, le workflow suivant est conçu pour générer un nombre aléatoire et l'écrire dans la console.

Variable<int> n = new Variable<int>
{
    Name = "n"
};

Activity wf = new Sequence
{
    Variables = { n },
    Activities =
    {
        new Assign<int>
        {
            To = n,
            Value = new Random().Next(1, 101)
        },
        new WriteLine
        {
            Text = new InArgument<string>((env) => "The number is " + n.Get(env))
        }
    }
};

Lorsque ce code de définition de workflow est exécuté, l'appel à Random.Next est passé et le résultat est stocké dans la définition de workflow comme valeur littérale. De nombreuses instances de ce workflow peuvent être appelées et toutes afficheraient le même nombre. Pour que la génération du nombre aléatoire ait lieu pendant l'exécution du workflow, une expression qui est évaluée à chaque exécution du workflow doit être utilisée. Dans l'exemple suivant, une expression Visual Basic est utilisée avec VisualBasicValue<TResult>.

new Assign<int>
{
    To = n,
    Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}

L'expression dans l'exemple précédent pourrait également être implémentée à l'aide de CSharpValue<TResult> et d'une expression C#.

new Assign<int>  
{  
    To = n,  
    Value = new CSharpValue<int>("new Random().Next(1, 101)")  
}  

Les expressions C# doivent être compilées avant que le workflow qui les contient ne soit appelé. Si les expressions C# ne sont pas compilées, une NotSupportedException est levée lorsque le workflow est appelé avec un message similaire à celui-ci : Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled. Dans la plupart des scénarios qui impliquent des workflows créés dans Visual Studio, les expressions C# sont compilées automatiquement, mais dans certains scénarios, tels que les workflows avec code, les expressions C# doivent être compilées manuellement. Pour obtenir un exemple expliquant comment compiler des expressions C#, consultez la section Utilisation d'expressions C# dans les workflows avec code de la rubrique Expressions C#.

VisualBasicValue<TResult> représente une expression dans la syntaxe Visual Basic qui peut être utilisée comme r-value dans une expression et CSharpValue<TResult> représente une expression dans la syntaxe C# qui peut être utilisée comme r-value dans une expression. Ces expressions sont évaluées chaque fois que l'activité conteneur est exécutée. Le résultat de l'expression est affecté à la variable de workflow n et ces résultats sont utilisés par l'activité suivante dans le workflow. Pour accéder à la valeur de la variable de workflow n au moment de l'exécution, le ActivityContext est requis. Elle est accessible à l’aide de l’expression lambda suivante.

Notes

Notez que ces deux exemples de code utilisent C# comme langage de programmation, mais l'un d'eux utilise VisualBasicValue<TResult> et l'autre utilise CSharpValue<TResult>. VisualBasicValue<TResult> et CSharpValue<TResult> peuvent être utilisés dans les projets Visual Basic et C#. Par défaut, les expressions créées dans le concepteur de workflow correspondent au langage du projet d'hébergement. Lors de la création des workflows dans le code, le langage souhaité est à la discrétion de l'auteur de workflow.

Dans ces exemples, le résultat de l'expression est affecté à la variable de workflow n et ces résultats sont utilisés par l'activité suivante dans le workflow. Pour accéder à la valeur de la variable de workflow n au moment de l'exécution, le ActivityContext est requis. Elle est accessible à l’aide de l’expression lambda suivante.

new WriteLine
{
    Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}

Pour plus d’informations sur les expressions lambda, consultez Expressions lambda (référence C#) et Expressions lambda (Visual Basic).

Les expressions lambda ne sont pas sérialisables au format XAML. Si une tentative de sérialiser un workflow avec les expressions lambda est faite, LambdaSerializationException est levée avec le message suivant : « Ce workflow contient des expressions lambda spécifiées dans le code. Ces expressions ne peuvent pas être sérialisées en XAML. Pour cela, utilisez VisualBasicValue/VisualBasicReference ou ExpressionServices.Convert(lambda). Cela convertit vos expressions lambda en activités d’expression. » Pour rendre cette expression compatible avec XAML, utilisez ExpressionServices et Convert, comme illustré dans l’exemple suivant.

new WriteLine
{
    //Text = new InArgument<string>((env) => "The number is " + n.Get(env))
    Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
}

Un VisualBasicValue<TResult> pourrait également être utilisé. Notez qu'aucune expression lambda n'est requise lors de l'utilisation d'une expression Visual Basic.

new WriteLine
{
    //Text = new InArgument<string>((env) => "The number is " + n.Get(env))
    //Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
    Text = new VisualBasicValue<string>("\"The number is \" + n.ToString()")
}

Au moment de l'exécution, les expressions Visual Basic sont compilées dans des expressions LINQ. Les deux exemples précédents sont sérialisables en XAML, mais si le code XAML sérialisé est conçu pour être affiché et modifié dans le concepteur de workflow, utilisez VisualBasicValue<TResult> pour vos expressions. Les workflows sérialisés qui utilisent ExpressionServices.Convert peuvent être ouverts dans le concepteur, mais la valeur de l'expression sera vide. Pour plus d'informations sur la sérialisation de workflows vers XAML, consultez Sérialisation de workflows et d'activités vers et à partir de XAML.

Expressions littérales et types de référence

Les expressions littérales sont représentées dans les workflows par l'activité Literal<T>. Les activités WriteLine suivantes sont équivalentes du point de vue fonctionnel.

new WriteLine  
{  
    Text = "Hello World."  
},  
new WriteLine  
{  
    Text = new Literal<string>("Hello World.")  
}  

Elle n'est pas valide pour initialiser une expression littérale avec un type de référence, sauf String. Dans l'exemple suivant, la propriété Assign d'une activité Value est initialisée avec une expression littérale à l'aide de List<string>.

new Assign  
{  
    To = new OutArgument<List<string>>(items),  
    Value = new InArgument<List<string>>(new List<string>())  
},  

Lorsque le workflow contenant cette activité est validé, l'erreur de validation suivante est retournée : « Un littéral prend uniquement en charge les types valeur et le type immuable System.String. Le type System.Collections.Generic.List'1[System.String] ne peut pas être utilisé comme littéral. » Si le workflow est appelé, une InvalidWorkflowException est levée qui contient le texte de l’erreur de validation. Il s’agit d’une erreur de validation, car la création d’une expression littérale avec un type de référence ne crée pas de nouvelle instance du type de référence pour chaque instance du workflow. Pour résoudre ce problème, remplacez l'expression littérale par une expression qui crée et retourne une nouvelle instance du type de référence.

new Assign  
{  
    To = new OutArgument<List<string>>(items),  
    Value = new InArgument<List<string>>(new VisualBasicValue<List<string>>("New List(Of String)"))  
},  

Pour plus d’informations sur les expressions, consultez Expressions.

Appeler des méthodes sur des objets à l'aide d'expressions et de l'activité InvokeMethod

L'activité InvokeMethod<TResult> peut être utilisée pour appeler les méthodes statiques et d'instance des classes dans le .NET Framework. Dans un exemple précédent de cette rubrique, un nombre aléatoire a été généré à l'aide de la classe Random.

new Assign<int>
{
    To = n,
    Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}

L'activité InvokeMethod<TResult> peut avoir été utilisée pour appeler la méthode Next de classe Random.

new InvokeMethod<int>  
{  
    TargetObject = new InArgument<Random>(new VisualBasicValue<Random>("New Random()")),  
    MethodName = "Next",  
    Parameters =
    {  
        new InArgument<int>(1),  
        new InArgument<int>(101)  
    },  
    Result = n  
}  

Étant donné que Next n'est pas une méthode statique, une instance de la classe Random est fournie pour la propriété TargetObject. Dans cet exemple une nouvelle instance est créée à l'aide d'une expression Visual Basic, mais elle peut également avoir été créée précédemment et stockée dans une variable de workflow. Dans cet exemple, il serait plus simple d'utiliser l'activité Assign<T> plutôt que l'activité InvokeMethod<TResult>. Si l'appel de la méthode appelée en dernier lieu par l'activité Assign<T> ou InvokeMethod<TResult> est long, InvokeMethod<TResult> présente un avantage, car il a une propriété RunAsynchronously. Lorsque cette propriété a la valeur true, la méthode appelée s'exécute de façon asynchrone par rapport au workflow. Si d'autres activités existent en parallèle, elles ne seront pas bloquées pendant que la méthode s'exécute de façon asynchrone. En outre, si la méthode à appeler n'a aucune valeur de retour, InvokeMethod<TResult> est la méthode appropriée pour appeler la méthode.

Arguments et activités dynamiques

Une définition de workflow est créée dans le code en assemblant des activités dans une arborescence d’activité et en configurant l’ensemble des propriétés et arguments. Les arguments existants peuvent être liés, mais les nouveaux arguments ne peuvent pas être ajoutés aux activités. Cela inclut les arguments de workflow passés à l’activité racine. Dans le code impératif, les arguments de workflow sont spécifiés comme propriétés sur un nouveau type CLR et, en XAML, ils sont déclarés à l'aide de x:Class et x:Member. Étant donné qu'il n'existe aucun nouveau type CLR créé lorsqu'une définition de workflow est créée comme une arborescence d'objets en mémoire, les arguments ne peuvent pas être ajoutés. Toutefois, les arguments peuvent être ajoutés à une DynamicActivity. Dans cet exemple, un DynamicActivity<TResult> qui accepte deux arguments entiers, les additionne et retourne le résultat est créé. Une DynamicActivityProperty est créée pour chaque argument et le résultat de l’opération est affecté à l’argument Result de DynamicActivity<TResult>.

InArgument<int> Operand1 = new InArgument<int>();
InArgument<int> Operand2 = new InArgument<int>();

DynamicActivity<int> wf = new DynamicActivity<int>
{
    Properties =
    {
        new DynamicActivityProperty
        {
            Name = "Operand1",
            Type = typeof(InArgument<int>),
            Value = Operand1
        },
        new DynamicActivityProperty
        {
            Name = "Operand2",
            Type = typeof(InArgument<int>),
            Value = Operand2
        }
    },

    Implementation = () => new Sequence
    {
        Activities =
        {
            new Assign<int>
            {
                To = new ArgumentReference<int> { ArgumentName = "Result" },
                Value = new InArgument<int>((env) => Operand1.Get(env) + Operand2.Get(env))
            }
        }
    }
};

Dictionary<string, object> wfParams = new Dictionary<string, object>
{
    { "Operand1", 25 },
    { "Operand2", 15 }
};

int result = WorkflowInvoker.Invoke(wf, wfParams);
Console.WriteLine(result);

Pour plus d’informations sur les activités dynamiques, consultez Création d’une activité au moment de l’exécution.

Activités compilées

Les activités dynamiques sont un moyen de définir une activité qui contient des arguments à l’aide du code, mais les activités peuvent également être créées dans le code et être compilées en types. Il est possible de créer des activités simples qui dérivent de CodeActivity, et des activités asynchrones qui dérivent de AsyncCodeActivity. Ces activités peuvent avoir des arguments, des valeurs de retour, et définir leur logique en utilisant du code impératif. Pour obtenir des exemples de création de ces types d’activités, consultez Classe de base CodeActivity et Création d’activités asynchrones.

Les activités qui dérivent de NativeActivity peuvent définir leur logique à l'aide du code impératif et elles peuvent également contenir des activités enfants qui définissent la logique. Ils ont également un accès complet aux fonctionnalités du runtime telles que la création de signets. Pour obtenir des exemples de création d’une activité basée sur NativeActivity, consultez Classe de base NativeActivity, Guide pratique : créer une activité et l’exempleComposite personnalisé à l’aide de l’activité native.

Les activités qui dérivent de Activity définissent leur logique uniquement via l'utilisation d'activités enfants. Ces activités sont généralement créées à l'aide du concepteur de workflow Designer, mais peuvent également être définies à l'aide du code. Dans l'exemple suivant, une activité Square qui dérive d'Activity<int> est définie. L'activité Square a un seul InArgument<T> nommé Value, et définit sa logique en spécifiant une activité Sequence à l'aide de la propriété Implementation. L'activité Sequence contient une activité WriteLine et une activité Assign<T>. Ensemble, ces trois activités implémentent la logique de l'activité Square.

class Square : Activity<int>  
{  
    [RequiredArgument]  
    public InArgument<int> Value { get; set; }  
  
    public Square()  
    {  
        this.Implementation = () => new Sequence  
        {  
            Activities =  
            {  
                new WriteLine  
                {  
                    Text = new InArgument<string>((env) => "Squaring the value: " + this.Value.Get(env))  
                },  
                new Assign<int>  
                {  
                    To = new OutArgument<int>((env) => this.Result.Get(env)),  
                    Value = new InArgument<int>((env) => this.Value.Get(env) * this.Value.Get(env))  
                }  
            }  
        };  
    }  
}  

L'exemple suivant appelle une définition de workflow composée d'une activité Square unique à l'aide de WorkflowInvoker.

Dictionary<string, object> inputs = new Dictionary<string, object> {{ "Value", 5}};  
int result = WorkflowInvoker.Invoke(new Square(), inputs);  
Console.WriteLine("Result: {0}", result);  

Lorsque le workflow est appelé, la sortie suivante s'affiche sur la console :

Mise au carré de la valeur : 5
Résultat : 25