Quand je parle de Workflows ou d’activités (au passage, il s’agit de la même chose dans WF4 ;) ), je fais souvent la comparaison avec une méthode classique :

  • Une activité peut avoir un retour, comme une méthode.
  • Une activité peut avoir des arguments d’entrée, comme une méthode.
  • Une activité peut avoir des arguments passés en référence, comme une méthode.
  • Une activité peut avoir des arguments d’entrée qui servent aussi de sortie, comme une méthode (out).
  • Une activité peut avoir des arguments génériques, comme une méthode (Action<T>).

En lisant cela on a vite envie de dire qu’un argument WF n’est ni plus ni moins qu’un argument sur une méthode…

Mais forcement dans WF4, les choses sont un peu plus subtiles. Il a donc un certain nombre de notions qu’il faut garder à l’esprit :

 

1) Argument != Variable

La petite chose qui n’est pas forcément évidente, c’est qu’un Argument WF4 est une classe différente de Variable. L’argument ne rend pas accessible que des variables contenues dans scope. Il peut très bien servir à faire passer une expression Vb à une activité. Par contre, il faut rester logique : un argument en sortie ne pourra référencer qu’une Variable… et oui, il faut bien que la valeur de sortie soit récupérés par quelqu'un ;)

 

2) Le cycle de vie de l’activité

Autre notion assez peut évidente : l’argument “vivant avec son activité”, il a un comportement différent en fonction de la situation dans la quelle se trouve l’activité.

Même si on ne s’en rend pas compte immédiatement, une activité s’utilise dans  deux situations très différentes :

  • La construction du Workflow (Design).
  • L’exécution du Workflow.

Petit exemple : si on a une activité comme celle-ci :

public sealed class MonWriteString : CodeActivity
{        
    public InArgument<String> TextEntree { get; set; }
    public OutArgument<String> TextSortie { get; set; }
}

Durant la phase de Design, l’argument est manipulé tel une propriété classique. Il n’y a donc rien de choquant durant cette phase à voir un code comme celui-ci :

Variable<String> maVariable1;
Variable<String> maVariable2;
MonWriteString w = new MonWriteString();
w.TextEntree = maVaraible1;
w.TextSortie = maVaraible2;

Ce qui se traduit par :

  • maVariable1 serra accessible en lecture via l’argument TextEntree.
  • maVariable2 serra accessible en écriture via l’argument TextSortie.

Et on pourrait très bien utilisée une expression an entrée au lieu de la simple Variable :

w.TextEntree = new VisualBasicValue<String>(@"maVariable1.ToString() & ""Ma string ajoutée""");

A cet instant, passer une Variable est possible.

Ensuite durant l’exécution, il est “interdit” de toucher à un argument hors de don activité (mais si cela reste possible, il ne faut pas le faire : c’est dangereux). Un argument doit être manipulé par son activité. Cet elle, qui change son contenu en fonction de ses besoins. Si vous avez associé une Variable à un Argument en mode Design, puis que durant l’exécution de l’activité, celle-ci manipule son Argument, ce serra la valeur de la Variable qui serra manipulée.

protected override void Execute(CodeActivityContext context)
{
    String entree = context.GetValue(this.TextEntree);

    String sortie = String.Concat("Mon nouveau text ", entree);

    context.SetValue(this.TextSortie, sortie);
}

Dans cet exemple, si on exécute ce qui a été écrit un peu plus haut durant le Design, on retrouve la String : StringSortie dans la Variable : maVariable2.

 

3) Le chois de l’Argument

Des classe Argument on en a un petit paquet. De manière générales, nous avons trois arguments : InArgument<T>, OutArgument<T> et InOutArgument<T>. Les préfixes In et Out indiquent le sens dans lequel chaque Argument est destiné à être utilisé.

Dans l’exemple précédent, la méthode Execute() utilise les méthode du context pour manipuler les arguments. Il existe une écriture différent basée sur les arguments qui met un peu plus en avant l’orientation de l’argument (In,Out ou In/Out)

protected override void Execute(CodeActivityContext context)
{
    String entree = this.TextEntree.Get(context);

    String sortie = String.Concat("Mon nouveau text ", entree);

    this.TextSortie.Set(context, sortie);
}

Cette écriture peut vous éviter d’écrire des choses aberrante comme ceci :

context.SetValue(this.TextEntree, sortie);

TextEntree est un Argument de type InArgument<String>, il ne permet donc pas de sortir une valeur de l’activité. Pourtant Visual Studio permettra la compilation et n’annoncera aucune warning.

L’utilisation des méthode Set() et Get() de l’argument vous permettrons de vous assurer que vous avez bien choisi vos arguments et que vous les utilisez correctement. Voici un petit graphe qui résume l’accessibilité de ces méthode en fonction du type d’argument utilisé :

  Get Accessible Set accessible
InArgument Oui Non
InOutArgument Oui Oui
OutArgument Non Oui

4) Eviter les classe abstraites (tant que vous ne les maitrisez pas!)

Dans le mesure du possible, utilisez les classe générique.Effectivement, les classe abstraites InArgument, OutArgument et InOutArgument existent. Mais leur emploi sous-entend d’effectuer certaines manipulations qui nécessitent des connaissances pointues de Workflow Foundation (cette remarque s’applique aussi au type abstrait Variable).

Si vous n’avez pas encore touché à la Metadata dans WF4, passez votre chemin!