Les bases de Windows Workflow Foundation.
Date de publication : 21/05/2007
Par
Jean-Alain Baeyens (autres articles)
I. Introduction
A. Qu'est-ce qu'un workflow
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
A. Qu'est-ce qu'un workflow
Selon Wikipédia:
"Un workflow est un flux d'informations au sein d'une organisation, comme 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 lité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).
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 page 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 controlé par
le moteur de workflow. Il est évident que cette application
pourrait tout aussi bien être mise en oeuvre 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, 3 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é et un point de sortie. Comme à l'habitude depuis .NET 2.0,
la classe est divisée en 2 classes partielles. Une contenant
le code généré par le designer et l'autre votre code.

La workflow par défaut
Faite glisser l'activité de type code de la boite à outils entre
le point d'entré 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 se faire,
faite glisser depuis la barre d'outils le composant ifelse sur le
signe + entre l'activité et la sortie du workflow.

Ajouter un chemin conditionnel
Une 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 première 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 click droit sur l'objet de sélection et
de choisir "Add branch".

Ajouter un chemin conditionel
Faite 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 vrai 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 language .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 2 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 façe 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é 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émaré 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ême
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'instance sous peine de voir les performances
s'effondrées.
|
VII. Appeler une méthode externe et attendre le résultat
Au lieu de coder directement dans la classe, nous aurrions 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 clase 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 ou à 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 remplaçée
par une pair "CallExternalMethod", "HandleExternalEvent".

Le couple CallExternalMethod, HandleExternalEvent.
Pour paramétrer l'activité "CallExternalMethod", il est nécessaire
de réaliser deux choses:
- Associer une propriété au paramêtre de la méthode.
- 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 persistence, être
capable de récupérer les tâches en attentes et introduire la notion
d'acteur.


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 oeuvre intellectuelle protégée par les droits d'auteurs. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
Cette page est déposée à la
SACD.