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 ».
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.
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.
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.
Faites glisser l'activité de type code de la boite à outils entre le point d'entrée et le point de sortie.
Le workflow contient maintenant une activité.
Changeons le nom de l'activité pour la rendre plus claire. Vous pouvez également introduire une description textuelle.
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.
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.
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 ».
Vous vous retrouvez alors dans le code associé et vous pouvez compléter la méthode avec le code adéquat.
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 ».
Faites maintenant glisser une activité de type code dans chacun des chemins et ajouter le code nécessaire pour chacune des 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.
Console.
WriteLine
(
"Appuyez sur 'Entrer' pour terminer"
);
Console.
ReadLine
(
);
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.
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éé.
<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 ».
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.
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.
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 :
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.
public
void
startWindow
(
)
{
CustomControlLibrary1.
Window1 form =
new
CustomControlLibrary1.
Window1
(
);
form.
ShowDialog
(
);
plaque =
form.
Plaque;
vitesse =
form.
Vitesse;
}
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é.
[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 ».
[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.
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.
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 ».
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 ».
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é.
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.
Dans cette méthode nous devons assigner la valeur adéquate à notre « dependency property ».
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 ».
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.
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.