Les bases de Windows Workflow Foundation.

Jean-Alain Baeyens

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Les bases de Windows Workflow Foundation.

- Auteur : Jean-Alain Baeyens

- Parution : 21 mai 2007

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2007 Jean-Alain Baeyens. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Qu'est-ce qu'un workflow

- I-B. Windows Workflow Foundation (WF)

- II. Que faut-il pour développer avec WF ?

- III. Mon premier workflow

- IV. Créer un Workflow en mode XAML

- V. Utiliser les « Rules » pour définir vos conditions

- VI. Le workflow dans une application Windows WPF

- VII. Appeler une méthode externe et attendre le résultat

- VIII. Conclusion

I - Introduction

I-A - Qu'est-ce qu'un workflow

Selon Wikipédia: « Un workflow est un flux d'informations au sein d'une organisation, par exemple la transmission automatique de documents entre des personnes. On appelle « workflow » (traduisez littéralement « flux de travail ») la modélisation et la gestion informatique de l'ensemble des tâches à accomplir et des différents acteurs impliqués dans la réalisation d'un processus métier (aussi appelé processus opérationnel ou bien procédure d'entreprise). Le terme de « workflow » pourrait donc être traduit en français par « gestion électronique des processus métier ». De façon plus pratique, le workflow décrit le circuit de validation, les tâches à accomplir entre les différents acteurs d'un processus, les délais, les modes de validation, et fournit à chacun des acteurs les informations nécessaires pour la réalisation de sa tâche. Pour un processus de publication en ligne par exemple, il s'agit de la modélisation des tâches de l'ensemble de la chaîne éditoriale. Il permet généralement un suivi et identifie les acteurs en précisant leur rôle et la manière de le remplir au mieux. Le moteur de workflow est le dispositif logiciel permettant d'exécuter une ou plusieurs définitions de workflow. Par abus de langage, on peut appeler ce dispositif logiciel tout simplement »workflow« . »

Toutefois la traduction littérale, « flux de travail », lui confère une portée beaucoup plus grande. Cette technique peut parfaitement être appliquée sur des flux plus simples à un seul acteur (homme) ou deux acteurs (homme, machine).

I-B - Windows Workflow Foundation (WF)

Windows Workflow Foundation en abrégé WF et non WWF correspond à la base du workflow selon Microsoft. Il ne s'agit pas d'un moteur de workflow à part entière, mais uniquement des briques de base nécessaires à la réalisation d'un moteur de workflow. Ces briques doivent vous permettre soit de créér votre propre moteur complet soit de donner une orientation résolument workflow à vos développements sans toutefois faire appel à un environnement workflow complet.

II - Que faut-il pour développer avec WF ?

Pour développer avec Workflow Foundation, vous devez disposer :

- du runtime du framework .NET 3.0 ;

- du SDK du framework .NET 3.0 ;

- de Visual Studio 2005. (Attention les versions expresses ne supportent pas l'extension pour WF) ;

- de Visual Studio Extensions for Windows Workflow Foundation.

En option, si votre Workflow utilise des pages XAML, vous devrez également installer les extensions Visual Studio 2005 pour le framework 3.0 (WCF,WPF). La version actuelle est la CTP de novembre 2006 (8.0.6). Il faut au minimum cette version pour la version définitive du framework 3.0.

En ce qui concerne l'extension pour WF, la version minimum est la version 3.0.4203.2.

III - Mon premier workflow

La toute première approche du workflow consiste à réaliser une application classique dont le cheminement va être contrôlé par le moteur de workflow. Il est évident que cette application pourrait tout aussi bien être mise en œuvre par des moyens traditionnels.

L'exemple, au demeurant fort peu sympathique, illustre le traitement d'un radar. Le n° de plaque et la vitesse sont saisis manuellement. Une vitesse inférieure ou égale à 130 km/h n'entraine aucune poursuite. Une vitesse comprise entre 130 et 150 km/h entraine le paiement d'une amende alors que pour une vitesse supérieure, le dossier est transmis au parquet.

Pour faire simple, nous allons créer une application en mode console. Pour créer l'application, il suffit de créer un nouveau projet de type « Sequential Worrkflow Console Application ».

créer le projet

Comme vous pouvez le voir dans la solution ci-dessous, trois fichiers ont été automatiquement créés. Ils vont assurer non seulement la gestion du moteur, mais également la définition de la procédure de workflow à prendre en charge.

La solution créée

Le fichier « program.cs » permet le démarrage du moteur de workflow. Ce démarrage est assuré par l'instanciation d'un objet de type « WorkflowRuntime ». Il réalise également le démarrage d'une instance du workflow que nous devons encore définir. Ce démarrage est assuré par l'appel de la méthode « CreateWorkflow ». Le type de la classe passé en paramètre correspond à la classe qui contient la procédure de Workflow que nous désirons démarrer.

extrait de program.cs

class Program
{
static void Main(string[] args)
{
using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted += delegate(object sender
, WorkflowCompletedEventArgs e) {waitHandle.Set();};
workflowRuntime.WorkflowTerminated += delegate(object sender
, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};

WorkflowInstance instance = workflowRuntime.CreateWorkflow(
typeof(WorkflowConsoleApplication2.Workflow1));
instance.Start();

waitHandle.WaitOne();
}
}
}

Si vous cliquez sur le second fichier, la procédure de workflow par défaut est ouverte en mode design. Elle contient uniquement un point d'entrée et un point de sortie. Comme à l'habitude depuis .NET 2.0, la classe est divisée en deux classes partielles. Une contenant le code généré par le designer et l'autre votre code.

Le workflow par défaut

Faites glisser l'activité de type code de la boite à outils entre le point d'entrée et le point de sortie.

Boite à outils: Activité de type code

Le workflow contient maintenant une activité.

Activité de type code

Changeons le nom de l'activité pour la rendre plus claire. Vous pouvez également introduire une description textuelle.

Nommer l'activité

Continuons le workflow en plaçant un chemin conditionnel. Pour ce faire, faites glisser depuis la barre d'outils le composant ifelse sur le signe + entre l'activité et la sortie du workflow.

Ajouter un chemin conditionnel

Un graphique complexe est maintenant ajouté à notre flux. Il s'agit d'une activité de type « IfElse ». Comme vous pouvez le constater, elle est destinée à contenir d'autres activités.

Chemins conditionnels

Pour chaque condition, vous devez la nommer correctement, lui donner une éventuelle description, mais surtout définir la condition. Il est possible de définir la condition soit par code soit via le mode déclaratif. Dans ce premier exemple, nous nous limiterons à une condition via le code. Pour réaliser ces différentes actions, utilisez la fenêtre de propriétés. Pour effectivement créer le code de la condition, tapez le nom de la méthode que vous allez utiliser et ensuite appuyez sur la touche « Entrer ».

Propriétés d'un chemin condition

Vous vous retrouvez alors dans le code associé et vous pouvez compléter la méthode avec le code adéquat.

Condition

private void inf130(object sender, ConditionalEventArgs e)
{
if (vitesse<=130) {
e.Result=true;
}else{
e.Result=false;
}
}

Il vous reste à faire de même pour l'autre condition.

Vous pouvez ajouter d'autres branches à votre condition. Pour cela, il suffit de faire un clic droit sur l'objet de sélection et de choisir « Add branch ».

Ajouter un chemin conditionnel

Faites maintenant glisser une activité de type code dans chacun des chemins et ajouter le code nécessaire pour chacune des activités.

Le workflow complet

Code pour les différentes activités

private void ActivityLower130_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Vitesse autorisée non dépassée");
}

private void ActivityLower150_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Vitesse autorisée dépassée");
Console.WriteLine("Envoi d'une amende automatisée");
}

private void ActivityUpper150_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Vitesse autorisée fortement dépassée");
Console.WriteLine("Envoi du dossier au parquet");
}

Pour visualiser plus facilement le résultat, ajoutez un point d'arrêt à la fin du programme.

Point d'arrêt

Console.WriteLine("Appuyez sur 'Entrer' pour terminer");
Console.ReadLine();

Fenêtre d'exécution

Plutôt que d'utiliser une méthode pour chaque condition, il est possible d'utiliser la même méthode associée à chaque branche.

Méthode globale pour tester toutes les branches d'une condition

private void CtrlVitesse(object sender, ConditionalEventArgs e)
{
switch (((IfElseBranchActivity)sender).Name)
{
case "Lower130":
{
if (vitesse <= 130)
{
e.Result = true;
}
else
{
e.Result = false;
}
}
break;

case "De130a150":
{
if (vitesse > 130 && vitesse<=150)
{
e.Result = true;
}
else
{
e.Result = false;
}
}
break;

case "PlusDe150":
{
if (vitesse > 150)
{
e.Result = true;
}
else
{
e.Result = false;
}
}
break;
default:
e.Result = false;
break;
}
}

C'est le switch sur le nom du contrôle qui permet de réaliser le bon test.

Remarquez que la méthode sera appelée jusqu'à ce qu'une condition soit vraie ou que toutes les conditions soient fausses. Si vos conditions ne sont pas exclusives, une seule activité sera exécutée. Si aucune condition n'est rencontrée, le workflow continue sur la tâche suivante.

IV - Créer un Workflow en mode XAML

Plutôt que de créer un workflow complètement en code, vous pouvez créer vos workflows avec séparation du code. Votre code sera écrit dans votre langage .NET habituel alors que la définition du workflow se fera au travers de XAML (dans un fichier dont l'extension est XOML). Le code XAML est bien plus lisible que le code généré dans le fichier « designer ».

Nous pouvons reconstruire exactement le même exemple que précédemment. Dans ce modèle, il existe également deux fichiers pour définir le workflow. Il s'agit des fichiers « workflow1.xoml » et « workflow1.xoml.cs ». En dehors du constructeur qui n'est pas défini, le fichier « workflow1.xoml.cs » est identique au fichier « workflow1.cs » de notre exemple précédent. La grosse différence résidente dans le fichier de description XAML qui est créé.

workflow1.xoml

<SequentialWorkflowActivity
x:Class="WorkflowConsoleApplication2.Workflow2" x:Name="Workflow2"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<CodeActivity x:Name="input" ExecuteCode="input_ExecuteCode" />
<IfElseActivity x:Name="SelectionBySpeed">
<IfElseBranchActivity x:Name="Lower130">
<IfElseBranchActivity.Condition>
<CodeCondition Condition="CtrlVitesse" />
</IfElseBranchActivity.Condition>
<CodeActivity x:Name="ActivityLower130"
ExecuteCode="ActivityLower130_ExecuteCode" />
</IfElseBranchActivity>
<IfElseBranchActivity x:Name="De130a150">
<IfElseBranchActivity.Condition>
<CodeCondition Condition="CtrlVitesse" />
</IfElseBranchActivity.Condition>
<CodeActivity x:Name="ActivityLower150"
ExecuteCode="ActivityLower150_ExecuteCode" />
</IfElseBranchActivity>
<IfElseBranchActivity x:Name="PlusDe150">
<IfElseBranchActivity.Condition>
<CodeCondition Condition="CtrlVitesse" />
</IfElseBranchActivity.Condition>
<CodeActivity x:Name="ActivityUpper150"
ExecuteCode="ActivityUpper150_ExecuteCode" />
</IfElseBranchActivity>
</IfElseActivity>
</SequentialWorkflowActivity>

Le code parle de lui-même.

Cette façon de travailler offre aussi la possibilité de modifier ou d'ajouter des workflows sans devoir recompiler l'application. Il faut toutefois mettre en place un mécanisme ad hoc et respecter certaines règles. Nous ne verrons pas cette technique dans le cadre de cet article, mais sachez qu'elle existe.

Pour ouvrir le fichier xml depuis Visual Studio, choisissez « Open with ».

La fenêtre 'ouvrir avec'

V - Utiliser les « Rules » pour définir vos conditions

Dans le chapitre précédent, nous avons vu comment séparer la déclaration du workflow des traitements. Toutefois, les conditions déterminant le chemin de parcours devraient également être considérées comme faisant partie de la déclaration du workflow et donc être également codée en XAML.

Pour cela, nous devons déclarer nos conditions non plus comme « Code Condition », mais comme « Declarative Rule Condition ».

Cliquez sur le bouton en face de « ConditionName » pour atteindre la fenêtre de gestion des règles ou vous pourrez sélectionner, modifier ou ajouter vos règles. Les règles seront alors conservées dans le fichier « workflow1.rules » qui contient également du XAML.

Accès aux règles

Fenêtre de gestion des règles

Fenêtre de définition de la condition

Je vous ferrai grâce de vous présenter le code XAML généré, car il est un peu long pour cet article, mais je vous conseille lorsque vous aurez créé votre premier fichier de règles de regarder son contenu.

VI - Le workflow dans une application Windows WPF

Pour intégrer le workflow dans une application Windows, nous avons besoin d'un projet « Windows Application », d'un projet « Workflow project » et d'un projet « Custom Control Library ». Le projet « Windows Application » est le projet principal. Il a en référence le projet Workflow. Le projet « Control library » héberge les composants WPF appelés depuis le Workflow. De cette façon nous évitons les références croisées et la portabilité s'en trouve améliorée.

Le workflow est démarré en pressant le bouton.

Projet WF/WPF

Code de démarrage du Workflow

public partial class Window1 : System.Windows.Window
{
private WorkflowRuntime workflowRuntime = new WorkflowRuntime();
private AutoResetEvent waitHandle = new AutoResetEvent(false);

public Window1()
{
InitializeComponent();

}

private void Start(Object senderStart, EventArgs eventStart)
{
workflowRuntime.WorkflowCompleted += delegate(object sender
, WorkflowCompletedEventArgs e) { waitHandle.Set(); };
workflowRuntime.WorkflowTerminated += delegate(object sender
, WorkflowTerminatedEventArgs e) { waitHandle.Set(); };

WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(WorkflowProject1.Workflow1));
instance.Start();
waitHandle.WaitOne();
}
}

Le workflow est identique à celui vu précédemment, mais le code exécuté en action est différent. Le code d'introduction des données devient :

Code appelé par le workflow

private void input_ExecuteCode(object sender, EventArgs e)
{
Thread winThread = new Thread(startWindow);
winThread.SetApartmentState(ApartmentState.STA);
winThread.Start();
while (winThread.IsAlive)
{
}
}

Pour exécuter une fenêtre WPF, il faut absolument que le thread soit de type STA. C'est pourquoi il est nécessaire de démarrer nous-mêmes un thread.

Il faut attendre la fin de l'exécution du thread sinon le workflow continue sans attendre. C'est le rôle de la boucle sur la propriété IsAlive.

Code exécuté dans le thread

public void startWindow()
{
CustomControlLibrary1.Window1 form = new CustomControlLibrary1.Window1();
form.ShowDialog();
plaque = form.Plaque;
vitesse = form.Vitesse;
}

Fenêtre WPF d'introduction des données

Cette façon de faire n'est envisageable que pour une application stand alone, car le moteur de workflow ne peut rester en attente que sur un nombre restreint d'instances sous peine de voir les performances s'effondrer.

VII - Appeler une méthode externe et attendre le résultat

Au lieu de coder directement dans la classe, nous aurions pu appeler une méthode externe en utilisant une activité du type « CallExternalActivity ». Pour que le workflow puisse communiquer avec une méthode externe, celle-ci doit implémenter une interface. Cette interface devra contenir la signature de la méthode à appeler, mais aussi l'EventHandler renvoyé par la méthode quand elle a terminé.

Définition de l'interface

[ExternalDataExchange]
public interface IXAMLFormLoader
{
void ShowForm(Guid id);
event EventHandler<XAMLEventArgs> XAMLFormClosed;
}

Notez la présence de l'attribut « ExternalDataExchange » et de l'« id » de type « Guid » passé à la méthode.

Il reste alors à définir la classe qui va implémenter l'interface, mais aussi à définir un EventArgs pour renvoyer les données. Celui-ci doit impérativement hériter de « ExternalDataEventArgs ».

Code pour l'EnventArgs

[Serializable]
public class XAMLEventArgs : ExternalDataEventArgs
{
private short vitesse;

public short Vitesse
{
get { return vitesse; }
}
private string plaque;

public string Plaque
{
get { return plaque; }
}

public XAMLEventArgs(Guid id, string plq, short vit):base(id)
{
vitesse = vit;
plaque = plq;
}
}

Dans l'EventArgs, nous retournons également l'« id ». Il est en fait le Guid de l'instance du Workflow qui a fait l'appel. Cette information est nécessaire au fonctionnement du Workflow.

Il faut maintenant écrire la classe qui va implémenter l'interface.

Code pour la classe

public class Window1FormLoader : IXAMLFormLoader
{
private Guid workflowId;

public event EventHandler<XAMLEventArgs> XAMLFormClosed;

public void ShowForm(Guid id)
{
workflowId = id;
Thread winThread = new Thread(startWindow);
winThread.SetApartmentState(ApartmentState.STA);
winThread.Start();
}

public void startWindow()
{
CustomControlLibrary1.Window1 form = new CustomControlLibrary1.Window1();
form.ShowDialog();
XAMLEventArgs args = new XAMLEventArgs(workflowId
, form.Plaque, form.Vitesse);
args.WaitForIdle = true;
EventHandler<XAMLEventArgs> closed = XAMLFormClosed;
closed(null, args);
}
}

L'événement permettra de transmettre les informations au workflow, mais également de lui indiquer que le traitement est terminé.

Avant de modifier notre Workflow, il est encore nécessaire avant de démarrer l'instance de définir cette nouvelle classe comme un service pour le Workflow. Cet appel est ajouté dans le constructeur de la fenêtre principale.

Fenêtre où a lieu le démarrage du Workflow

public Window1()
{
InitializeComponent();

ExternalDataExchangeService dataService = new ExternalDataExchangeService();
workflowRuntime.AddService(dataService);
Window1FormLoader activityWindow1 = new Window1FormLoader();
dataService.AddService(activityWindow1);
}

Dans le designer Workflow, nous pouvons remplacer la première étape par une activité de type « CallExternalMethod ». Mais c'est insuffisant, car il faut également un élément permettant d'attendre que la méthode externe soit terminée. Pour cela il est nécessaire de ce mettre à l'écoute de l'événement envoyé par la méthode externe. L'activité « HandleExternalEvent » va s'occuper de cette tâche. L'activité initiale est donc remplacée par une paire « CallExternalMethod », « HandleExternalEvent ».

Le couple CallExternalMethod, HandleExternalEvent.

Pour paramétrer l'activité « CallExternalMethod », il est nécessaire de réaliser deux choses :

1. Associer une propriété au paramètre de la méthode ;

2. Définir une méthode qui sera appelée juste avant la méthode externe.

Pour la première étape, dans les propriétés de l'activité, vous cliquez sur le bouton tout à droite du paramètre « id ». La fenêtre d'association s'ouvre. Il ne reste plus qu'a choisir de créer une nouvelle propriété dans l'onglet « Associer à un nouveau membre ».

Associer une propriété

Cette action ajoute le code nécessaire pour faire la liaison entre une propriété du workflow et le paramètre de l'activité. L'activité recevra donc automatiquement comme « id » le contenu de cette nouvelle propriété.

Propriété associée

public static System.Workflow.ComponentModel.DependencyProperty
callExternalMethodActivity1_id1Property =
System.Workflow.ComponentModel.DependencyProperty.Register(
"callExternalMethodActivity1_id1", typeof(System.Guid)
, typeof(WorkflowProject1.Workflow1));
...
[DesignerSerializationVisibilityAttribute(
DesignerSerializationVisibility.Visible)]
[BrowsableAttribute(true)]
[CategoryAttribute("Parameters")]
public Guid callExternalMethodActivity1_id1
{
get
{
return ((System.Guid)(base.GetValue(
WorkflowProject1.Workflow1.callExternalMethodActivity1_id1Property)));
}
set
{
base.SetValue(
WorkflowProject1.Workflow1.callExternalMethodActivity1_id1Property
, value);
}
}

Comme vous pouvez le constater, il s'agit d'une DependencyProperty. Une fois cette opération terminée, il nous reste à placer la valeur dans la propriété. Ce qui est généralement fait juste avant le lancement de l'activité. La propriété MethodInvoking de l'activité permet justement de définir une méthode qui sera automatiquement appelée à ce moment.

Paramétrage de l'activité CallExternalMethod

Dans cette méthode nous devons assigner la valeur adéquate à notre « dependency property ».

Méthode invoquée automatiquement avant l'appel de l'activité

private void showForm1_MethodInvoking(object sender, EventArgs e)
{
callExternalMethodActivity1_id1 = this.WorkflowInstanceId;
}

Il reste encore à récupérer les valeurs en retour. Pour rappel, nous avons mis ces valeurs dans l'EventArgs. Pour les récupérer, nous devons à nouveau utiliser une dependency property. Cette fois sur le paramètre « e » de l'activité « HandleExternalEvent ».

Paramétrage de l'activité HandleExternalEvent

Grâce à la propriété Invoked de l'activité, vous pouvez définir une méthode qui sera exécutée directement après. Dans cette méthode vous pouvez récupérer les valeurs contenues dans l'EventArgs et les replacer dans les propriétés adéquates.

Méthode invoquée automatiquement après l'appel de l'activité

private void handleExternalEventActivity1_Invoked(object sender
, ExternalDataEventArgs e)
{
plaque = this.handleExternalEventActivity1_e1.Plaque;
vitesse = this.handleExternalEventActivity1_e1.Vitesse;
}

VIII - Conclusion

Cet article vous a décrit les bases du workflow sur un scénario très simple. Mais dans la pratique, le workflow sera plutôt utilisé dans des scénarios ou l'exécution dure sur de longues périodes et de préférence ou plusieurs acteurs interviennent. Mais pour cela il vous faudra encore maitriser la persistance, être capable de récupérer les tâches en attentes et introduire la notion d'acteur.