Les bases de Windows Workflow Foundation.

N'hésitez pas à commenter cet article ! Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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".

Fenêtre de création d'un projet workflow
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.

Fenêtre de la solution créée
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
Sélectionnez
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.

Workflow par défaut en mode design
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.

L'activité de type code dans la toolbox
Boite à outils: Activité de type code

Le workflow contient maintenant une activité.

Le workflow avec une activité de type code
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é depuis la fenêtre de propriété
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.

Ajout d'un chemin conditionnel
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.

Design des chemins conditionnels
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".

Fenêtre des propriétés associée à un chemin conditionnel
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
Sélectionnez
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".

Menu contextuel pour ajouter un chemin conditionnel
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.

Fenêtre de design du workflow
Le workflow complet
Code pour les différentes activités
Sélectionnez

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
Sélectionnez
Console.WriteLine("Appuyez sur 'Entrer' pour terminer");
Console.ReadLine();
Console d'exécution du workflow
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
Sélectionnez

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
Sélectionnez
<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".

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.

Image non disponible
Accès aux règles
Image non disponible
Fenêtre de gestion des règles
Image non disponible
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.

Image non disponible
Projet WF/WPF
Code de démarrage du Workflow
Sélectionnez

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
Sélectionnez

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
Sélectionnez

public void startWindow()
{
    CustomControlLibrary1.Window1 form = new CustomControlLibrary1.Window1();
    form.ShowDialog();
    plaque = form.Plaque;
    vitesse = form.Vitesse;
}
Image non disponible
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
Sélectionnez

[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
Sélectionnez

[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
Sélectionnez
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
Sélectionnez

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".

Image non disponible
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".

Image non disponible
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
Sélectionnez

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.

Image non disponible
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é
Sélectionnez

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".

Image non disponible
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é
Sélectionnez

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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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.