Amine Mostefai's Blog

Architecture is my passion :)

Amine

Hi and welcome to my blog. I share in this space a lot of posts related to software architecture, and software development. Content is mainly related to .NET CORE development, Angular, Sharepoint, Azure and Office 365. I hope that my articles are helpful and that you enjoy using them 😉

Tutoriel 5.1 - Création d’un service de recrutement–Partie 2

Dans la partie prĂ©cĂ©dente, nous avons crĂ©Ă© les services et les workflows nĂ©cessaires Ă  notre opĂ©ration de recrutement. Durant cette partie, nous ferons le reste : les applications clientes. Etape 7 : CrĂ©ation de l’application candidat L’objectif de cette Ă©tape est de crĂ©er l’application « Candidat » qui permettra Ă  un postulant de soumettre une candidature. Cette application sera Ă©galement serveur puisqu’elle hĂ©bergera un service de notification. Ouvrez Visual Studio dans une nouvelle fenĂȘtre en mode administrateur CrĂ©ez une nouvelle application WPF et appelez-la « CandidatApp » Ouvrez « MainWindow » en mode design Supprimez la grille Glissez un « DockPanel » sur la fenĂȘtre principale avec les propriĂ©tĂ©s « HorizontalAlignment » et « VerticalAlignment » Ă  « Stretch », supprimez les propriĂ©tĂ©s « Height » et « Width ». « LastChildFill » doit ĂȘtre «True » Glissez un « Label » sur le « StackPanel » avec la propriĂ©tĂ© « Content » Ă  « Nom : » et « Margin » Ă  3 et « DockPanel.Dock » Ă  « Top » Glissez un « TextBox » avec la propriĂ©tĂ© « Name » Ă  « txtNom » et « Margin » Ă  3 et « DockPanel.Dock » Ă  « Top » Glissez un deuxiĂšme « Label » en dessous de la « TextBox » avec la propriĂ©tĂ© « Content » Ă  « Date de naissance : » et « Margin » Ă  3 et « DockPanel.Dock » Ă  « Top » Glissez un « DatePicker » en dessous du deuxiĂšme « Label » avec les propriĂ©tĂ©s « Name » Ă  « dpDate », « Margin » Ă  3 et « DockPanel.Dock » Ă  « Top » Ajoutez un bouton en dessous du « Picker » avec « Name », « Margin », « Content » Ă  « btnSoumettre », 3 et « Soumettre » Glissez un « TextBox » en dessous du bouton  avec la propriĂ©tĂ© « Name » Ă  « txtConsole », « Margin » Ă  3, « AcceptReturn » Ă  « True » Le code XAML de la fenĂȘtre principale doit ĂȘtre comme ceci : <Window x:Class="CandidatApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" LastChildFill="True" > <Label Content="Nom :" Margin="3" DockPanel.Dock="Top"/> <TextBox Name="txtNom" Margin="3" DockPanel.Dock="Top"/> <Label Content="Date de Naissance :" Margin="3" DockPanel.Dock="Top"/> <DatePicker Name="dpDate" Margin="3" DockPanel.Dock="Top"/> <Button Content="Soumettre" DockPanel.Dock="Top" Margin="3" Name="btnSoumettre"/> <TextBox Name="txtConsole" Margin="3" AcceptsReturn="True" TextWrapping="Wrap" VerticalAlignment="Stretch"/> </DockPanel></Window> Lancez la boĂźte de dialogue d’ajout de nouvel Ă©lĂ©ment (au projet CandidatApp) Ă  partir de l’explorateur de solutions Dans la zone de recherche, tapez « WCF » SĂ©lectionnez « Service WCF » Dans la zone « Nom », entrez « NotificationService » Remarquez que VS crĂ©e une interface appelĂ©e « INotificationService » et son implĂ©mentation « NotificationService Ouvrez le fichier « INotificationService.cs » Supprimez la dĂ©claration de mĂ©thode « DoWork » Ajoutez une mĂ©thode de type « void » appelĂ©e « Notifier » et qui a deux paramĂštres « Nom » et « Acceptation » de type « String » et « Boolean » DĂ©corez la mĂ©thode par l’attribut « OperationContract » pour la rendre comme service Le listing de l’interface INotificationService devrait ĂȘtre comme ceci : [ServiceContract] public interface INotificationService { [OperationContract] void Notifier(string Nom, bool Acceptation); } Ouvrez le fichiez « NotificationService.cs » Supprimez le code de « DoWork » ImplĂ©mentez la mĂ©thode « Notifier » comme ceci : public void Notifier(string Nom, bool Acceptation) { string etat; if (Acceptation) etat = "acceptĂ©"; else etat = "rejetĂ©"; Console.WriteLine("Le candidat {0} a Ă©tĂ© {1} ", Nom, etat);  } Compilez l’application. Ajoutez au projet la classe « TextBoxTextWriter » crĂ©Ă© lors des tutoriaux prĂ©cĂ©dents pour rediriger les sorties de la console sur la « TextBox » txtConsole Ouvrez « MainWindow.xaml.cs » Ajoutez un using sur l’esapce de nom de la classe « TextBoxTextWriter » Ajoutez un using sur « System.ServiceModel » Ajoutez un Ă©vĂšnement de chargement « Loaded » sur le « DockPanel » Redirigez la sortie de la console// rediriger la sortie de la console Console.SetOut(new TextBoxTextWriter(txtConsole)); CrĂ©ez ensuite le hĂŽte qui permettra d’herbeger le service WCF comme ceci : // crĂ©er le hote wcf var host = new ServiceHost(typeof(NotificationService)); // lancer le hote wcf host.Open(); Console.WriteLine("service de notification dĂ©marrĂ©"); Nous allons enfin affectez une valeur par dĂ©faut Ă  la date de naissance qui est de 20 ans avant aujourd’hui// date par dĂ©faut dpDate.SelectedDate = DateTime.Now.AddYears(-20); Le listing complet de la mĂ©thode devrait ĂȘtre comme ceci : private void DockPanel_Loaded(object sender, RoutedEventArgs e) { // rediriger la sortie de la console Console.SetOut(new TextBoxTextWriter(txtConsole)); // crĂ©er le hote wcf var host = new ServiceHost(typeof(NotificationService)); // lancer le hote wcf host.Open(); Console.WriteLine("service de notification dĂ©marrĂ©"); // date par dĂ©faut dpDate.SelectedDate = DateTime.Now.AddYears(-20); } Ouvrez le fichier « App.Config » Changez la propriĂ©tĂ© « baseAddress » de l’hĂŽte Ă  http://localhost:7766/Notification Le listing de « App.Config » devrait ĂȘtre comme ceci : <?xml version="1.0" encoding="utf-8" ?><configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="false" /> </behavior> </serviceBehaviors> </behaviors> <services> <service name="CandidatApp.NotificationService"> <endpoint address="" binding="basicHttpBinding" contract="CandidatApp.INotificationService"> <identity> <dns value="localhost" /> </identity> </endpoint> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> <host> <baseAddresses> <add baseAddress="http://localhost:7766/Notification" /> </baseAddresses> </host> </service> </services> </system.serviceModel></configuration> Lancez l’application pour tester le service Appelez la mĂ©thode « Notifier » Ă  partir du testeur « WCF » Remarquez que les messages de notifications sortent sur la console Etape 8 : CrĂ©ation du proxy vers le workflow Dans l’étape prĂ©cĂ©dente, nous avons crĂ©Ă© le serveur de notification qui permettra au workflow de notifier le candidat. Dans cette Ă©tape, nous allons nous connecter au service du workflow pour pouvoir soumettre le dossier du candidat. Revenez sur la fenĂȘtre VS qui contient le projet « Tutoriel51 » Lancez l’application « WorkflowConsoleHost » Revenez sur le projet « CandidatApp » Dans l’explorateur de solutions, cliquez sur le bouton droit sur les rĂ©fĂ©rences puis sur « ajouter une rĂ©fĂ©rence de service » Dans la zone « Adresse », entrez http://localhost:9988/EmbaucheSimple Cliquez sur « Go » Dans l’espace de noms, entrez « FormationWF » Dans les services, sĂ©lectionnez « EmbaucheSimpleService » puis « IEmbaucheService » Cliquez sur « OK » Ouvrez « MainWndow » en mode design Affectez un Ă©vĂšnement « Click » au bouton CrĂ©ons les informations Ă  transmettre Ă  partir du formulaire // crĂ©er les informations var info = new FormationWF.CandidatInfo() { Nom = txtNom.Text, DateNaissance = dpDate.SelectedDate.Value }; CrĂ©ons ensuite le proxy qui nous permettra de nous connecter au serveur// crĂ©er le proxy var client = new FormationWF.EmbaucheServiceClient(); Nous allons ensuite crĂ©er les donnĂ©es Ă  transmettre au serveur // donnĂ©es Ă  transmettre var data = new FormationWF.RecevoirCandidature(); data.pInfo = new FormationWF.CandidatInfo() { Nom = txtNom.Text, DateNaissance = dpDate.SelectedDate.Value }; Ensuite nous allons appeler le service et rĂ©cupĂ©rer le rĂ©sultat // appel du service var res = client.RecevoirCandidature(data); if (res.HasValue && !res.Value) Console.WriteLine("Candidature automatiquement rejetĂ©e"); Le listing complet du gestionnaire de clic est comme suit : private void btnSoumettre_Click(object sender, RoutedEventArgs e) { // crĂ©er les informations var info = new FormationWF.CandidatInfo() { Nom = txtNom.Text, DateNaissance = dpDate.SelectedDate.Value }; // crĂ©er le proxy var client = new FormationWF.EmbaucheServiceClient(); // donnĂ©es Ă  transmettre var data = new FormationWF.RecevoirCandidature(); data.pInfo = new FormationWF.CandidatInfo() { Nom = txtNom.Text, DateNaissance = dpDate.SelectedDate.Value }; // appel du service var res = client.RecevoirCandidature(data); // afficher rĂ©sultat si rejet auto if (res.HasValue && !res.Value) Console.WriteLine("Candidature automatiquement rejetĂ©e"); } ExĂ©cutez l’application puis remarquez comment des candidatures de moins de 30 ans sont automatiquement rejetĂ©es Etape 9 : CrĂ©ation de l’application de l’évaluateur L’objectif de cette Ă©tape est de crĂ©er une application qui va ĂȘtre utilisĂ©e par l’évaluateur pour Ă©valuer les candidatures. Ouvrez Visual Studio dans une nouvelle fenĂȘtre CrĂ©ez une nouvelle application WPF et appelez-la « EvaluateurApp » Ouvrez « MainWindow » en mode design Supprimez la grille Glissez un « StackPanel » sur la fenĂȘtre principale avec les propriĂ©tĂ©s « HorizontalAlignment » et « VerticalAlignment » Ă  « Stretch », supprimez les propriĂ©tĂ©s « Height » et « Width ». Glissez un « Label » sur le « StackPanel » avec la propriĂ©tĂ© « Content » Ă  « Nom : » et « Margin » Ă  3 Glissez un « TextBox » avec la propriĂ©tĂ© « Name » Ă  « txtNom » et « Margin » Ă  3 Glissez un deuxiĂšme « Label » en dessous de la « TextBox » avec la propriĂ©tĂ© « Content » Ă  « Note : » et « Margin » Ă  3 Glissez une « ComboBox » en dessous du deuxiĂšme « Label » avec les propriĂ©tĂ©s « Name » Ă  « cbNote », « Margin » Ă  3. La combobox contient 5 notes de 1 Ă  5 et la propriĂ©tĂ© « SelectedIndex » Ă  0 Ajoutez un bouton en dessous du « ComboBox » avec « Name », « Margin », « Content » Ă  « btnEvaluer », 3 et « Evaluer » Le XAML de la fenĂȘtre devrait ĂȘtre comme ceci : <Window x:Class="EvaluateurApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <StackPanel> <Label Content="Nom :" Margin="3"/> <TextBox TextWrapping="Wrap" Name="txtNom" Margin="3"/> <Label Content="Note :" Margin="3"/> <ComboBox Name="cbNote" Margin="3" SelectedIndex="0"> <ListBoxItem Content="1"/> <ListBoxItem Content="2"/> <ListBoxItem Content="3"/> <ListBoxItem Content="4"/> <ListBoxItem Content="5"/> </ComboBox> <Button Content="Evaluer" Name="btnEvaluer"/> </StackPanel>  </Grid></Window> Dans l’explorateur de solutions, cliquez sur le bouton droit sur les rĂ©fĂ©rences puis sur « ajouter une rĂ©fĂ©rence de service » Dans la zone « Adresse », entrez http://localhost:9988/EmbaucheSimple Cliquez sur « Go » Dans l’espace de noms, entrez « FormationWF » Dans les services, sĂ©lectionnez « EmbaucheSimpleService » puis « IEmbaucheService » Cliquez sur « OK » Ajoutez un gestionnaire de clic au bouton « Evaluer » qui permet d’appeler la mĂ©thode « Evaluer » du service private void btnEvaluer_Click(object sender, RoutedEventArgs e) { // crĂ©er le proxy var proxy = new FormationWF.EmbaucheServiceClient(); // appeler le service proxy.Evaluer(txtNom.Text, cbNote.SelectedIndex + 1); } Compilez l’application pour vĂ©rifier d’éventuelles erreurs. Etape 10 : Finalisation du workflow L’objectif de cette Ă©tape est de finaliser le workflow de façon Ă  ce qu’aprĂšs l’évaluation, il envoie une notification au candidat. Revenez Ă  la solution « Tutoriel51 » Si l’application console est en exĂ©cution, quittez-la Glissez une activitĂ© « Send » au workflow Connectez le dernier « Assign » avec l’activitĂ© « Send » Connectez le nouveau « Send » avec le cĂŽtĂ© « False » de l’activitĂ© de dĂ©cision Dans la zone « OperationName », entrez « Notifier » Cliquez sur le bouton « Contenu » Ajoutez un paramĂštre appelĂ© « Nom » de type « String » et dont la valeur est « info .Nom » Ajoutez un deuxiĂšme paramĂštre appelĂ©e « Acceptation », de type « Boolean » et dont la valeur est « Resultat »   Cliquez sur « OK » Dans la propriĂ©tĂ© « EndPoint.AddressUri » de l’activitĂ© « Send », entrez http://localhost:7766/Notification. C’est l’adresse du service de notification. Dans la propriĂ©tĂ© « EndPoint.Binding », de l’activitĂ© « Send », sĂ©lectionnez « basicHttpBinding » Dans la propriĂ©tĂ© « ServiceContractName » entrez « INotificationService » Le workflow devrait ĂȘtre comme ceci : Etape 11 : Mise en place de la corrĂ©lation Le workflow dans l’étape prĂ©cĂ©dente est fonctionnel mais il reste un problĂšme : lorsque l'Ăąge du candidat dĂ©passe 30 ans, l’évaluateur doit lui donner une note. Le problĂšme est qu’un instant donnĂ©, plusieurs instances peuvent s’exĂ©cuter en parallĂšle. Lorsqu’un Ă©valuateur donne une note, on doit affecter cette note au bon candidat. La solution pour trouver la bonne instance du workflow est la corrĂ©lation. Nous Ă©tablirons une corrĂ©lation sur le nom du candidat. Ouvrez le workflow « EmbaucheSimpleService.xamlx » en mode design Remarquez que notre workflow contient deux activitĂ©s « Receive ». Pour assurer une cohĂ©rence d’exĂ©cution, nous relierons les deux « Receive » par une corrĂ©lation. Le premier « Receive » doit initialiser une corrĂ©lation tandis que le deuxiĂšme doit s’appliquer Ă  cette corrĂ©lation. Cliquez sur l’activitĂ© organigramme (FlowChart) parente Examinez les variables Remarquez la prĂ©sence d’une variable appelĂ©e « __handle » de type « CorrlationHandle » Si cette variable n’existe pas, crĂ©ez-la Changez la portĂ©e de la variable (Scope) pour qu’elle soit sur tout l’organigramme Renommez cette variable en « dossier » Double-cliquez sur la premiĂšre sĂ©quence du workflow Cliquez sur la propriĂ©tĂ© « CorrelationInitializers » de l’activitĂ© « Receive » de la sĂ©quence Dans la zone « Add initializer », entrez « dossier » Dans la liste dĂ©roulante au dessus de la grille « XPath Queries », sĂ©lectionnez « Query Correlation Initializer » Dans la grille, dans la colonne « Query », sĂ©lectionnez la propriĂ©tĂ© « Nom » du paramĂštre « pInfo » Remarquez que VS gĂ©nĂšre automatiquement le chemin « XPath » relatif Ă  la propriĂ©tĂ© Laissez la colonne « Key » Ă  « Key1 » Cliquez sur « OK » Nous allons maintenant corrĂ©ler la deuxiĂšme activitĂ© « Receive » avec la premiĂšre Dans la deuxiĂšme activitĂ© « Receive » (Recevoir Evaluation), cliquez sur le bouton de la propriĂ©tĂ© « CorrelateOn » Dans la zone « Correlates With », entrez « dossier » Dans la « XPath Queries », sĂ©lectionnez le paramĂštre « Nom ». Remarquez que le chemin « XPath » est automatiquement gĂ©nĂ©rĂ©. Ce que nous avons fait c’est que nous utilierons le paramĂštre « Nom » fourni par l’évaluateur pour trouver la bonne instance du workflow. Cliquez sur « OK » Le workflow est maintenant prĂȘt. Compilez pour vĂ©rifier l’absence d’erreurs. Etape 12 : ExĂ©cution et Tests L’objectif de cette Ă©tape est de tester nos services. Nous validerons les trois cas de figures : Si le candidat a moins de 30 ans, sa candidature est automatiquement rejetĂ©e. Il reçoit immĂ©diatement une notification. Si le candidat a plus de 30 ans, l’évaluateur doit l’évaluer. Lorsque l’évaluateur lui attribue une note supĂ©rieure ou Ă©gale Ă  3, il est acceptĂ©, sinon il est rejetĂ©. ProcĂ©dure : Lancez les trois applications Dans l’application « CandidatApp », entrez « Kamel » dans le nom et « 01/05/1986 » dans la date de naissance Cliquez sur « Soumettre » Remarquez que « Kamel » a Ă©tĂ© automatiquement rejetĂ© CrĂ©ez deux candidatures pour « Yazid » et pour « Racha » nĂ©s respectivement le « 25/03/1971 » et « 26/08/1978 » Remarquez que les candidatures n’ont pas Ă©tĂ© automatiquement rejetĂ©es Allez sur l’application « EvaluateurApp » Dans la zone « Nom » entrez « Racha » et sĂ©lectionnez « 4 » dans la note puis cliquez sur « Evaluer » Revenez Ă  l’application « CandidatApp », remarquez qu’une notification indiquant que « Racha » a Ă©tĂ© acceptĂ©e. La corrĂ©lation a fait qu’on trouve le bon « Workflow » malgrĂ© que « Racha » a un dossier ultĂ©rieur Ă  celui de « Yazid » Revenez Ă  l’application « EvaluateurApp » Dans la zone « Nom » entrez « Yazid », dans la zone « Note » entrez « 2 » puis cliquez sur le bouton « Evaluer » Revenez sur l’application « CandidatApp » Remarquez une notification stipulant que « Yazid » a Ă©tĂ© rejetĂ©e       Le code source complet des applications de ce tutoriel est accessible ici. Enjoy !

Tutoriel 5.1 - Création d’un service de recrutement–Partie 1

L’objectif de ce tutoriel (cours 5) est de mettre en place un workflow de recrutement basĂ© sur les services WCF. Pour ce, le workflow est hĂ©bergĂ© dans une application console. Le candidat dispose d’une application qui lui permet de postuler Ă  un poste et l’évaluateur a une application lui permettant d’évaluer une candidature. A la fin de l’évaluation, le candidat reçoit une notification de la note s’il a Ă©tĂ© acceptĂ© ou pas. Au dĂ©but, si l’ñge du candidat est infĂ©rieur Ă  30, sa candidature est systĂ©matiquement rejetĂ©e sans passer par l’évaluation. Etape 1 – CrĂ©ation de la solution. L’objectif est de crĂ©er une solution qui va contenir les diffĂ©rents modules du tutoriel. Lancez Visual Studio en tant qu’administrateur CrĂ©ez une nouvelle solution vide appelĂ©e « Tutoriel51 » Ajoutez Ă  la solution un projet de type « Application de service WCF Workflow » et appelez-le « EmbaucheLibrary » Remarquez que le projet contient un fichier dont l’extension est « xamlx » appelĂ©e « Service1.xamlx » Supprimez ce fichier Ajoutez au projet un nouvel Ă©lĂ©ment de type « Service Workflow WCF » et appelez-le « EmbaucheSimpleService » Remarquez que par dĂ©faut, VS 2012 crĂ©e une sĂ©quence avec une activitĂ© « Receive » et une activitĂ© « Send » Supprimez la sĂ©quence et toutes les activitĂ©s avec Etape 2 : CrĂ©ation d’un contrat de donnĂ©es L’objectif de cette Ă©tape est de crĂ©er un contrat de donnĂ©es (DataContract) qui reprĂ©sente des donnĂ©es qui vont ĂȘtre sĂ©rialisĂ©es et envoyĂ©es sur le rĂ©seau Ă  travers les services WCF. Ajoutez une nouvelle classe au projet « EmbaucheSimpleService » appelĂ©e « CandidatInfo » Ajoutez deux propriĂ©tĂ©s publiques appelĂ©es «Nom » et « DateNaissance » de type « String » et « DateTime » public DateTime DateNaissance { get; set; } public string Nom { get; set; }   Ajoutez un « using » vers l’espace de nom « System.Runtime.Serialization » DĂ©corez les deux propriĂ©tĂ©s avec l’attribut « DataMember » DĂ©core la classe avec l’attriobut « DataContract » Compilez la solution pour vĂ©rifier l’absence d’erreurs. Le listing complet devrait ĂȘtre comme suit : [DataContract] public class CandidatInfo { [DataMember] public DateTime DateNaissance { get; set; } [DataMember] public string Nom { get; set; } }   Etape 3 : CrĂ©ation du service de candidature L’objectif de cette Ă©tape est de crĂ©er un service workflow qui permettra au candidat de prĂ©senter ses informations : nom et date de naissance. Ouvrez le workflow « EmbaucheSimpleService.xamlx » Glissez un organigramme sur le workflow Glissez une activitĂ© « ReceiveAndSendReply » et connectez-la au nƓud de dĂ©marrage Cliquez sur l’organigramme parent CrĂ©ez une variable « info » dont le type est « CandidatInfo » CrĂ©ez une deuxiĂšme variable appelĂ©e « Resultat » de type « Boolean » CrĂ©ez une troisiĂšme variable appelĂ©e « Note » de type « Int32 » Double-cliquez sur la sĂ©quence Renommez la premiĂšre activitĂ© « Receive » en « Recevoir Candidature » Entrez « RecevoirCandidature » dans la zone « Nom de l’opĂ©ration » Dans la propriĂ©tĂ© « ServiceContractName », entrez « IEmbaucheService » Cochez la propriĂ©tĂ© « CanCreateInstance » Cliquez sur « Contenu », la boĂźte de dialogue ci-dessous devrait apparaĂźtre Cliquez sur le bouton radio « ParamĂštres » Cliquez sur « Ajoutez nouveau paramĂštre » Dans la zone « Nom », entrez « pInfo » Pour le type, sĂ©lectionnez « CandidatInfo » Dans la zone « Assign To », entrez « info » Cliquez sur OK Glissez une activitĂ© de type « Assign » entre l’activitĂ© « Receive » et l’activitĂ© « Send » Dans la zone « To », entrez « Resultat » Dans la zone « Value » entrez l’expression suivante :info.DateNaissance < DateTime.Today.AddYears(-30) Ajoutez une activitĂ© « WriteLine » aprĂšs « Assign » et avant « SendReplyToReceive » Affectez l’expression suivante Ă  la propriĂ©tĂ© « Text »string.Format("Candidature reçue, nom : {0}, dn : {1}",info.Nom,info.DateNaissance) Dans l’activitĂ© « SendReplyToReceive », cliquez sur « Contenu » Dans la liste dĂ©roulante « Message Type », sĂ©lectionnez « Boolean » Dans la zone « Message Data », entrez « Resultat » La sĂ©quence devrait ĂȘtre comme suit : Compilez pour vĂ©rifier la prĂ©sence d’erreurs. Etape 4 : Test du service L’objectif de cette Ă©tape est de tester le service en utilisant l’outil « WCFTestClient » fourni avec Visual Studio. Dans Visual Studio, dans l’explorateur de solution, cliquez avec le bouton droit sur le fichier « EmbaucheSimpleService.xamlx » Cliquez sur « Afficher sur le navigateur » Remarquez la dĂ©finition du service s’affichant dans le navigateur.   Copiez l’adresse du service Ă  partir de la barre d’adresse Lancez l’explorateur Windows Allez sur le rĂ©pertoire d’installation de Visual Studio Allez sur le rĂ©pertoire « Common7 Ă  IDE » ExĂ©cutez l’outil « WCFTestClient.exe » Cliquez « Fichier Ă  Ajouter Service » Dans la boĂźte de dialogue qui apparait, dans la zone de texte, collez l’URL copiĂ©e lors de l’étape 4 Remarquez la prĂ©sence du service « IEmbaucheService » avec la mĂ©thode « RecevoirCandidature » Double-cliquez sur « RecevoirCandidature » Affectez des valeurs au paramĂštre « pInfo » (utiliser l’assistant qui propose des valeurs par dĂ©faut) Cliquez sur « Invoke » Remarquez que le service renvoie « True » pour les dates de naissance de plus de 30 ans et false sinon Etape 5 : CrĂ©ation de l’application d’hĂ©bergement en mode console Une des nombreuses façons d’hĂ©berger un workflow de service est une application de console. L’objectif de cette Ă©tape est d’utiliser la classe « WorkflowServiceHost » pour hĂ©berger un workflow. Ajoutez Ă  la solution un nouveau projet de type « Application Console Workflow » et appelez-la « ConsoleWorkflowHost » A partir de l’explorateur de solution, faites de ce projet le projet de dĂ©marrage A partir de l’explorateur de solution, supprimer le workflow « Workflow1.xaml » crĂ©Ă© avec le projet Ajoutez une rĂ©fĂ©rence vers « EmbaucheLibrary » Ouvrez le fichier « Program.cs » Supprimez le contenu de la mĂ©thode « Main » Ajoutez un using vers « System.ServiceModel.Activities » Ajoutez un using vers « System.Xaml » Ajoutez un using vers « System.ServiceModel.Description » La premiĂšre Ă©tape consiste Ă  charger le service Ă  partir du fichier :// charger le service Ă  partir du fichier WorkflowService service = XamlServices.Load(@"F:\FormationWF\Tutoriaux\Chapitre 5\Tutoriel51\EmbaucheLibrary\EmbaucheSimpleService.xamlx") as WorkflowService; Remplacez le chemin indiquĂ© par le chemin dans lequel se trouve votre code source La deuxiĂšme Ă©tape consiste Ă  compiler le service :// compiler le service Compile(service); Nous fournirons l’implĂ©mentation de la mĂ©thode « Compile » dans une prochaine Ă©tape La prochaine Ă©tape consiste Ă  dĂ©finir l’adresse du service :// dĂ©finir l'adresse du service Uri address = new Uri("http://localhost:9988/EmbaucheSimple"); Ensuite, nous lancerons l’hĂŽte en utilisant le workflow et l’adresse :// crĂ©er le hĂŽte WorkflowServiceHost host = new WorkflowServiceHost(service, address); Nous paramĂ©trerons le service de façon Ă  ce qu’il communique ses mĂ©tadonnĂ©es via HTTP :// communiquer mĂ©tadonnĂ©es via HTTP host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true }); Nous supprimerons ensuite le comportement de dĂ©bogage du service// supprimer le comportement de dĂ©bogage host.Description.Behaviors.Remove(typeof(ServiceDebugBehavior)); Nous ajouterons ensuite le comportement qui inclut les informations des exceptions avec les messages. Ce comportement est trĂšs utile pour le dĂ©bogage.// inclure les dĂ©tails des exception dans les messages host.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true }); Maintenant que l’hĂŽte est configuĂ©, il faut le lancer// dĂ©marrer le service host.Open(); Ajoutons une instruction qui lit une touche Ă  partir du clavier (Console.ReadKey()) et afficher un message avantConsole.WriteLine("Service dĂ©marrĂ©"); Console.ReadKey(); Le listing complet de la mĂ©thode « Main » devraĂźt ĂȘtre comme suit : static void Main(string[] args) { // charger le service Ă  partir du fichier WorkflowService service = XamlServices.Load(@"F:\FormationWF\Tutoriaux\Chapitre 5\Tutoriel51\EmbaucheLibrary\EmbaucheSimpleService.xamlx") as WorkflowService; // compiler le service Compile(service); // dĂ©finir l'adresse du service Uri address = new Uri("http://localhost:9988/EmbaucheSimple"); // crĂ©er le hĂŽte WorkflowServiceHost host = new WorkflowServiceHost(service, address); // communiquer mĂ©tadonnĂ©es via HTTP host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true }); // supprimer le comportement de dĂ©bogage host.Description.Behaviors.Remove(typeof(ServiceDebugBehavior)); // inclure les dĂ©tails des exception dans les messages host.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true }); // dĂ©marrer le service host.Open(); Console.WriteLine("Service dĂ©marrĂ©"); Console.ReadKey(); } Ajoutez un using vers « System.Activities.XamlIntegration » Ajoutez un using vers « System.Activities.Expressions » Ajoutez la mĂ©thode « Compile » comme suit : static void Compile(WorkflowService workflowService) { TextExpressionCompilerSettings settings = new TextExpressionCompilerSettings { Activity = workflowService.Body, Language = "C#", ActivityName = workflowService.Name + "_CompiledExpressionRoot", ActivityNamespace = string.Join(".", "CompiledExpressions"), RootNamespace = null, GenerateAsPartialClass = false, ForImplementation = false, // Important tip - ForImplementation should only be true when compiling an ActivityBuilder, it must be false when compiling an Activity such as a WorfkflowService body. };  TextExpressionCompilerResults results = new TextExpressionCompiler(settings).Compile();  if (results.HasErrors) { throw new Exception("Compilation failed."); }  ICompiledExpressionRoot compiledExpressionRoot = Activator.CreateInstance(results.ResultType, new object[] { workflowService.Body }) as ICompiledExpressionRoot;  CompiledExpressionInvoker.SetCompiledExpressionRoot( workflowService.Body, compiledExpressionRoot); }   L’implĂ©mentation de « Compile a Ă©tĂ© tirĂ©e du service de « Microsoft » (https://connect.microsoft.com/VisualStudio/feedback/details/743870/workflow-foundation-4-5-workflowservicehost-xamlx-expression-activity-type-csharpvalue1-requires-compilation-in-order-to-run ) Etape 6 : CrĂ©ation du service d’évaluation L’objectif de cette Ă©tape est d’ajouter le service permettant Ă  la commission technique d’évaluer un candidat. Ouvrez le workflow « EmbaucheSimpleService.xaml » Ouvrez l’organigramme principal qui contient la sĂ©quence Ajoutez une activitĂ© « FlowDecision » et connectez-la Ă  la sĂ©quence Entrez « Resultat » dans l’expression de la condition de la dĂ©cision Ajoutez une activitĂ© « Receive » et appelez-la « Recevoir Evaluation » Connectez l’activitĂ© au label « True » de la dĂ©cision Entrez « IEmbaucheService » dans la propriĂ©tĂ© « ServiceContractName » Entrez « Evaluer » dans la zone « Nom de l’opĂ©ration » Cliquez sur le bouton « Contenu » de l’activitĂ© « Receive » Cliquez sur le bouton radio « ParamĂštres » Ajoutez un paramĂštre appelĂ© « nom » de type « String » Ajoutez un paramĂštre appelĂ© « note » de type « Int32 » affectĂ©e Ă  la variable « Note » Cliquez sur « OK » Ajoutez une activitĂ© « Assign » et connectez-la au dernier « Receive » Dans la zone « To » entrez « Resultat » Dans la zone expression, entrez « Note >=3) Le workflow devrait ĂȘtre comme suit : Compilez pour vĂ©rifier les erreurs. Vous pouvez accĂ©der Ă  la suite du tutoriel ici.

Workflow Foundation Cours 4–Tutoriel 4.2 Suivi

L’objectif de ce tutoriel est de mettre en place le suivi des workflows. Pour ce, nous utiliserons l’implĂ©mentation standard « ETWTrackingParticipant » qui Ă©crit les Ă©vĂšnements sur le journal des Ă©vĂšnements. Nous ajoutons aussi une implĂ©mentation personnalisĂ©e qui Ă©crit les Ă©vĂšnements sur la console. Etape 1 – Ouverture de la solution. L’objectif de dupliquer la solution crĂ©Ă©e dans le tutoriel 4.1. AccĂ©dez au rĂ©pertoire contenant la solution du tutoriel 4.1 Dupliquez le rĂ©pertoire pour ne pas perdre les rĂ©sultats du tutoriel prĂ©cĂ©dent. Renommez le nouveau rĂ©pertoire « Tutoriel42 » Ouvrez la solution dans Visual Studio Etape 2 : PrĂ©paration de l’observateur d’évĂšnements L’objectif de cette Ă©tape est de prĂ©parer l’observateur d’évĂšnements de façon Ă  ce qu’il reçoive les enregistrements Ă©mis par les trackers. A partir du menu dĂ©marrer, lancez l’observateur d’évĂšnements. Dans le menu « Affichage », cliquez sur « Afficher les journaux de dĂ©bogage et d’analyse » Ouvrez le nƓud « Journaux des applications et des services » Ă  « Microsoft » Ă  « Windows » « Application Server-Applications » Remarquez la prĂ©sence du journal « Analytique » Cliquez avec le bouton droit sur le journal puis cliquez sur « Activer le journal » Dans Visual Studio, ouvrez le fichier « MainWindow.xaml.cs » Modifiez la mĂ©thode « ConfigurerApplication » de façon Ă  crĂ©er un « Tracker » qui suit les Ă©tats des activitĂ©s. La crĂ©ation du tracker doit se faire comme ceci : // crĂ©er le tracker var tracker = new EtwTrackingParticipant() { TrackingProfile = new TrackingProfile() { Name = "Formation WF", Queries = { new ActivityStateQuery() { States = {"*"} } } } }; Le tracker crĂ©Ă© avec un profil appelĂ© « Formation WF » et une requĂȘte sur tous les Ă©tats L’étape suivante doit ĂȘtre d’ajouter le tracker aux extensions // ajouter le tracker aux extensionsapp.Extensions.Add(tracker); Le listing complet de la mĂ©thode « ConfigurerApplication » doit ĂȘtre comme ceci : /// <summary> /// configure l'application /// </summary> /// <param name="app"></param> /// <param name="NomCandidat"></param> private void ConfigurerApplication(WorkflowApplication app, string NomCandidat = null) { // affecter le store app.InstanceStore = _store; // ajouter l'extension pour pouvoir persister le dossier du candidat app.Extensions.Add(new CandidatParticipant() { Nom = NomCandidat }); // crĂ©er le tracker var tracker = new EtwTrackingParticipant() { TrackingProfile = new TrackingProfile() { Name = "Formation WF", Queries = { new ActivityStateQuery() { States = {"*"} } } } };  // ajouter le tracker aux extensions app.Extensions.Add(tracker); // Ă©vĂšnement Ă  dĂ©clencher lorsque le workflow est en mode veille app.Idle = delegate(WorkflowApplicationIdleEventArgs args) { var signet = args.Bookmarks.FirstOrDefault(); if (signet == null) return; DesactiverBoutons(); switch (signet.BookmarkName) { case "EvaluationTechnique": ActiverBouton(btnTechnique, true); break; case "EvaluationOrale": ActiverBouton(btnOral, true); break; } };  // Ă©vĂšnement se dĂ©clenchant avant la persistance app.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs args) { Console.WriteLine("Workflow {0} va ĂȘtre persistĂ©", args.InstanceId); return PersistableIdleAction.Unload; };  // affiche un message lorsque le workflow est dĂ©chargĂ© app.Unloaded += delegate(WorkflowApplicationEventArgs args) { Console.WriteLine("Workflow {0} dĂ©chargĂ©", args.InstanceId); };  // se dĂ©clenche lorsque le workflow se termine app.Completed += delegate(WorkflowApplicationCompletedEventArgs args) { var extensions = args.GetInstanceExtensions<CandidatParticipant>(); var dossier = extensions.First(); Console.WriteLine("Workflow {0} terminĂ© avec statut {1}", args.InstanceId, args.CompletionState); Console.WriteLine("L'opĂ©ration d'embauche du dossier {0} a Ă©tĂ© terminĂ©e avec une moyenne de {1}", dossier.Nom, args.Outputs["moyenne"]); DesactiverBoutons(); // supprimer le workflow de la liste cbWorkflows.Dispatcher.Invoke(new Action(() => _liste.Remove(args.InstanceId)));  }; } ExĂ©cutez l’application et crĂ©ez quelques candidatures Revenez Ă  l’observateur d’évĂšnements et accĂ©dez aux Ă©vĂšnements Ă©mis par le workflow   Etape 2 : CrĂ©ation d’un tracker personnalisĂ© L’objectif de cette Ă©tape est de crĂ©er un tracker personnalisĂ© qui affiche les enregistrements en mode console. Ajoutez un using sur « System.Activities.Tracking » Ajoutez une nouvelle classe publique Ă  la bibliothĂšque « EmbaucheLibrary » appelĂ©e « ConsoleTrackingParticipant » et qui hĂ©rite de la classe « TrackingParticipant » RedĂ©finissez la mĂ©thode « Track » de façon Ă  ce qu’elle affiche l’enregistrement sur la console : if (record != null) Console.WriteLine(record); Dans la mĂ©thode « ConfigurerApplication », changez le type de la variable « tracker » de « EtwTrackingParticipant » vers « ConsoleTrackingParticipant » Le listing complet de la classe « ConsoleTrackingParticipant » est comme ceci : public class ConsoleTrackingParticipant : TrackingParticipant { protected override void Track(TrackingRecord record, TimeSpan timeout) { if (record != null) Console.WriteLine(record); } } ExĂ©cutez l’application pour voir les enregistrements en mode console Code Source Le code source est accessible ici.

WF Cours 4–Services Avancés. Tutoriel 4.1 Persistance–Partie 2

Ce tutoriel est la suite de la premiĂšre partie qui consiste Ă  mettre en place un workflow utilisant le service de persistance persistance. Etape 6 : PrĂ©paration du rĂ©fĂ©rentiel (Store) L’objectif de cette Ă©tape est de prĂ©parer le rĂ©fĂ©rentiel qui permettra de persister les workflows sur la BDD SQL Server crĂ©Ă©e durant les Ă©tapes prĂ©cĂ©dentes. Le rĂ©fĂ©rentiel utilise une BDD SQL Server. Pour ce, nous avons besoin d’une chaĂźne de connexion. DĂ©clarez une variable de type « String » appelĂ©e « connectionString » comme ceci : /// <summary> /// la chaine de connexion de la base /// </summary> const string connectionString = "Server=.;Initial Catalog=FormationWF;Integrated Security=SSPI"; Utiliser un serveur autre que “.” Si vous avez une installation ou une configuration diffĂ©rente de SQL Server. Au projet « Tutoriel41UI », ajoutez deux rĂ©fĂ©ences sur « System.Runtime.DurableInstancing » et « System.Activities.DurableInstancing » Dans le fichier « MainWindow.xaml.cs » ajoutez deux « using » « System.Activities » et « System.Activities.DurableInstancing » DĂ©clarez une variable privĂ©e de type « SqlWorkflowInstanceStore » comme ceci : /// <summary> /// le rĂ©fĂ©rentiel SQL Server /// </summary> private SqlWorkflowInstanceStore _store; Dans la mĂ©thode « ConfigurerStore », instanciez le store en utilisant la chaĂźne de connexion : // crĂ©er le rĂ©fĂ©rentiel en utilisant la chaine de connexion _store = new SqlWorkflowInstanceStore(connectionString); Nous allons ensuite dĂ©clarer les propriĂ©tĂ©s additionnelles Ă  persister : // dĂ©clarer les propriĂ©tĂ©s Ă  intĂ©grer avec la persistance List<XName> variantProperties = new List<XName>(); variantProperties.Add(CandidatParticipant.nomns); _store.Promote("Candidature", variantProperties, null); Nous allons ensuite configurer le store de façon Ă  ce qu’il soit le store par dĂ©faut pour les workflows // dĂ©finir le rĂ©fĂ©rentiel par dĂ©faut WorkflowApplication.CreateDefaultInstanceOwner(_store, null, WorkflowIdentityFilter.Any); Le listing complet de la mĂ©thode “ConfigurerStore » est comme ceci : /// <summary> /// configure le store SQL Server /// </summary> private void ConfigurerStore() { // crĂ©er le rĂ©fĂ©rentiel en utilisant la chaine de connexion _store = new SqlWorkflowInstanceStore(connectionString); // dĂ©clarer les propriĂ©tĂ©s Ă  intĂ©grer avec la persistance List<XName> variantProperties = new List<XName>(); variantProperties.Add(CandidatParticipant.nomns); _store.Promote("Candidature", variantProperties, null); // dĂ©finir le rĂ©fĂ©rentiel par dĂ©faut WorkflowApplication.CreateDefaultInstanceOwner(_store, null, WorkflowIdentityFilter.Any); } Compilez la solution pour vĂ©rifier la prĂ©sence d’erreurs. Etape 7 : ExĂ©cution du workflow L’objectif de cette Ă©tape est crĂ©er un workflow pour le dossier d’une nouvelle candidature. Nous allons voir comment dĂšs que le , il est dĂ©chargĂ© de la mĂ©moire et persistĂ© sur une base de donnĂ©es. Ouvrez la fenĂȘtre principale « MainWindow » en mode design Affectez un Ă©vĂšnement « Click » au bouton « dĂ©marrer » /// <summary> /// dĂ©marrer une nouvelle candidature /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnDemarrer_Click(object sender, RoutedEventArgs e) { } Ajoutez un « using » sur « EmbaucheLibrary » et ajoutez cette bibliothĂšque aux rĂ©fĂ©rences si le using n’est pas reonnu. A l’intĂ©rieur du bouton de clic, crĂ©ez une nouvelle instance du workflow // crĂ©er le workflow var activity = new EmbaucheWorkflow(); DĂ©clarez une variable de type « WorkflowIdentity » comme suit : /// <summary> /// identitĂ© du workflow /// </summary> private WorkflowIdentity _identity = new WorkflowIdentity() { Name = "Workflow Embauche", Version = new Version(1, 0, 0, 0) }; Ajoutez une mĂ©thode « GetIdentity » qui renvoie la variable « _identity ». Nous nous conterons d’une seule version dans ce tutoriel.   /// <summary> /// renvoie l'identitĂ© du workflow /// </summary> /// <returns></returns> private WorkflowIdentity GetIdentity() { return _identity; } Dans le gestionnaire du click du bouton « demarrer », crĂ©er une nouvelle application workflow en utilisant l’identiĂ© // crĂ©er une nouvelle application var _app = new WorkflowApplication(activity, GetIdentity()); Nous allons maintenant ajouter l’Id dela nouvelle application Ă  la liste des workflows. // empĂȘche de dĂ©clencher l'Ă©vĂšnement de changement de combobox _demarrage = true; _liste.Add(_app.Id); cbWorkflows.SelectedIndex = _liste.IndexOf(_app.Id); _demarrage = false; Nous allons maintenant configurer l’application // configure l'application ConfigurerApplication(_app, txtNom.Text); A la fin, nous devons lancer le workflow // dĂ©marrer le workflow _app.Run(); Console.WriteLine("Workflow {0} dĂ©marrĂ©", _app.Id); Le listing complet dui gestionnaire de clic devrait ĂȘtre comme ceci : /// <summary> /// dĂ©marrer une nouvelle candidature /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnDemarrer_Click(object sender, RoutedEventArgs e) { // crĂ©er le workflow var activity = new EmbaucheWorkflow(); // crĂ©er une nouvelle application var _app = new WorkflowApplication(activity, GetIdentity()); // empĂȘche de dĂ©clencher l'Ă©vĂšnement de changement de combobox _demarrage = true; _liste.Add(_app.Id); cbWorkflows.SelectedIndex = _liste.IndexOf(_app.Id); _demarrage = false; // configure l'application ConfigurerApplication(_app, txtNom.Text); // dĂ©marrer le workflow _app.Run(); Console.WriteLine("Workflow {0} dĂ©marrĂ©", _app.Id); } Nous allons maintenant passer Ă  la configuration de l’application dans la mĂ©thode « ConfigurerApplication » La premiĂšre chose Ă  faire est d’ associer le store crĂ©Ă© prĂ©cĂ©demment Ă  l’application // affecter le store app.InstanceStore = _store; Nous devons ensuite ajouter l’extension « CandidatParticipant » pour qu’il puisse ĂȘtre intĂ©grĂ© dans les traitements // ajouter l'extension pour pouvoir persister le dossier du candidat app.Extensions.Add(new CandidatParticipant() { Nom = NomCandidat }); Nous devons crĂ©er un Ă©vĂšnement qui se dĂ©clenche en mode veille. L’évĂšnement active un bouton selon le signet en cours (technique ou oral) // Ă©vĂšnement Ă  dĂ©clencher lorsque le workflow est en mode veille app.Idle = delegate(WorkflowApplicationIdleEventArgs args) { var signet = args.Bookmarks.FirstOrDefault(); if (signet == null) return; DesactiverBoutons(); switch (signet.BookmarkName) { case "EvaluationTechnique": ActiverBouton(btnTechnique, true); break; case "EvaluationOrale": ActiverBouton(btnOral, true); break; } }; Nous devons dĂ©finit l’évĂšnement « PersistIdle » pour indiquer que le worklfow doit ĂȘtre dĂ©chargĂ© et persistĂ© en mĂȘme temps // Ă©vĂšnement se dĂ©clenchant avant la persistance app.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs args) { Console.WriteLine("Workflow {0} va ĂȘtre persistĂ©", args.InstanceId); return PersistableIdleAction.Unload; }; Nous ajoutons ensuite l’évĂšnement « Unloaded » qui affiche un message lorsque le workflow est dĂ©chargĂ© // affiche un message lorsque le workflow est dĂ©chargĂ© app.Unloaded += delegate(WorkflowApplicationEventArgs args) { Console.WriteLine("Workflow {0} dĂ©chargĂ©", args.InstanceId); }; Nous ajouton ensuite un Ă©vĂšnement qui se dĂ©clenche lorsque le workflow se termine. L’évĂšnement affiche le statut de la candidature et de terminaison du workflow. // se dĂ©clenche lorsque le workflow se termine app.Completed += delegate(WorkflowApplicationCompletedEventArgs args) { var extensions = args.GetInstanceExtensions<CandidatParticipant>(); var dossier = extensions.First(); Console.WriteLine("Workflow {0} terminĂ© avec statut {1}", args.InstanceId, args.CompletionState); Console.WriteLine("L'opĂ©ration d'embauche du dossier {0} a Ă©tĂ© terminĂ©e avec une moyenne de {1}", dossier.Nom, args.Outputs["moyenne"]); DesactiverBoutons(); // supprimer le workflow de la liste cbWorkflows.Dispatcher.Invoke(new Action(() => _liste.Remove(args.InstanceId)));  }; Le listing complet de « ConfigurerApplication » devrait ĂȘtre comme ceci : /// <summary> /// configure l'application /// </summary> /// <param name="app"></param> /// <param name="NomCandidat"></param> private void ConfigurerApplication(WorkflowApplication app, string NomCandidat = null) { // affecter le store app.InstanceStore = _store; // ajouter l'extension pour pouvoir persister le dossier du candidat app.Extensions.Add(new CandidatParticipant() { Nom = NomCandidat }); // Ă©vĂšnement Ă  dĂ©clencher lorsque le workflow est en mode veille app.Idle = delegate(WorkflowApplicationIdleEventArgs args) { var signet = args.Bookmarks.FirstOrDefault(); if (signet == null) return; DesactiverBoutons(); switch (signet.BookmarkName) { case "EvaluationTechnique": ActiverBouton(btnTechnique, true); break; case "EvaluationOrale": ActiverBouton(btnOral, true); break; } };  // Ă©vĂšnement se dĂ©clenchant avant la persistance app.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs args) { Console.WriteLine("Workflow {0} va ĂȘtre persistĂ©", args.InstanceId); return PersistableIdleAction.Unload; };  // affiche un message lorsque le workflow est dĂ©chargĂ© app.Unloaded += delegate(WorkflowApplicationEventArgs args) { Console.WriteLine("Workflow {0} dĂ©chargĂ©", args.InstanceId); };  // se dĂ©clenche lorsque le workflow se termine app.Completed += delegate(WorkflowApplicationCompletedEventArgs args) { var extensions = args.GetInstanceExtensions<CandidatParticipant>(); var dossier = extensions.First(); Console.WriteLine("Workflow {0} terminĂ© avec statut {1}", args.InstanceId, args.CompletionState); Console.WriteLine("L'opĂ©ration d'embauche du dossier {0} a Ă©tĂ© terminĂ©e avec une moyenne de {1}", dossier.Nom, args.Outputs["moyenne"]); DesactiverBoutons(); // supprimer le workflow de la liste cbWorkflows.Dispatcher.Invoke(new Action(() => _liste.Remove(args.InstanceId)));  }; } Compilez pour vĂ©rifer l’absence d’erreurs ExĂ©cutez l’application, entrez un nom puis cliquez sur « Nouvelle Candidature »   Remarquez le message indiquant que le workflow a Ă©tĂ© dĂ©chargĂ© et que le bouton « Evaluer Technique » a Ă©tĂ© activĂ© ïżœïżœ cause du signet Allez dans SQL Server Management Studio Affichez le contenu de la table « InstancesTable » de la BDD « FormationWF » Remarquez la valeur du champ « Id » qui reprĂ©sente l’id du workflow Remarquez la valeur du champ « BlockingBookmarks » qui indique le signet sur lequel est bloquĂ© le workflow Affichez le contenu de la table « InstancePromotedPropertiesTable »   Remarquez la prĂ©sence du nom du dossier entrĂ© prĂ©cĂ©demment Etape 8 : Chargement du Workflow L’étape prĂ©cĂ©dente a mis en place la persistance. L’objectif de cette Ă©tape est de mettre en place le mĂ©canisme inverse permettant de charger un workflow dĂ©chargĂ© et persistĂ©. Quittez l’application pour revenir vers Visual Studio Ouvrez « MainWindow.xaml.cs » Nous allons maintenant crĂ©er un contexte Enbtity Framework quii nous permettra de nous connecter sur l base de donnĂ©es de persistance. CrĂ©ez un contexte EntityFramework pointant sur la base de donnĂ©es « FormationWF » et qui inclut une table unique « InstancesTable » . Appelez le contexte « FormationWFEntities » L’option de pluralisation doit ĂȘtre cochĂ©e. Le modĂšle devrait ĂȘtre comme ceci :   Nous allons maintenant changer l’implĂ©mentation de la mĂ©thode « ChargerListeWorkflows » de façon Ă  ramener cette liste Ă  partir de la BDD /// <summary> /// charge la liste des workflows Ă  partir de la base de donnĂ©es /// </summary> private void ChargerListeWorkflows() { using (var ctx = new FormationWFEntities()) { _liste = new ObservableCollection<Guid>(ctx.InstancesTables.Select(et => et.Id).ToList()); } cbWorkflows.ItemsSource = _liste; } Nous allons maintenant ajouter la mĂ©thode « ChargerApplication » qui permettra de charger un workflow persistĂ© ultĂ©rieurement. Ajoutez une mĂ©thode privĂ©e appelĂ© « ChargerApplication » et dont le type de retour est « WorkflowApplication » La premiĂšre instruction nous permettra de rĂ©cupĂ©rer l’instance du workflow Ă  partir du rĂ©fĂ©rentiel // rĂ©cupĂ©rer l'instance Ă  partir du store var instance = WorkflowApplication.GetInstance((Guid)cbWorkflows.SelectedItem, _store); La deuxiĂšme Ă©tape est de crĂ©er une instance en mĂ©moire du workflow // crĂ©er le workflow en mĂ©moirevar activity = new EmbaucheWorkflow(); Ensuite nous crĂ©erons une application sur la dĂ©finition de l’instance et du workflow en mĂ©moire. Ensuite l’application doit ĂȘtre configurĂ©e pour qu’elle s’exĂ©cute correctement. // crĂ©er l'applicationvar app = new WorkflowApplication(activity, instance.DefinitionIdentity);ConfigurerApplication(app); Ensuite, l’étape la plus importante est de charger le workflow // chager le workflow app.Load(instance); Enfin,on retourne l’application crĂ©Ă©e. /// <summary> /// change le workflow persistĂ© /// </summary> /// <returns></returns> private WorkflowApplication ChargerApplication() { // rĂ©cupĂ©rer l'instance Ă  partir du store var instance = WorkflowApplication.GetInstance((Guid)cbWorkflows.SelectedItem, _store); // crĂ©er le workflow en mĂ©moire var activity = new EmbaucheWorkflow(); // crĂ©er l'application var app = new WorkflowApplication(activity, instance.DefinitionIdentity); ConfigurerApplication(app); // chager le workflow app.Load(instance); return app; } La mĂ©thode « ChargerApplication » va ĂȘtre utilisĂ©e par les deux boutons et la combobox. Un changement de la combobox devrait charger un workflow, afficher les signets bloquĂ©s et le relancer, pour ce, implĂ©mentez l’évĂšnement « SelectionChanged » comme suit : /// <summary> /// changement de la combo box /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void cbWorkflows_SelectionChanged(object sender, SelectionChangedEventArgs e) { // si dĂ©marrage, ne rien faire if (_demarrage || (cbWorkflows.SelectedItem == null)) return; // charge l'application var app = ChargerApplication(); Console.WriteLine("Workflow {0} chargĂ©", app.Id); // affiche les signets foreach (var signet in app.GetBookmarks()) { Console.WriteLine("Workflow {0}, signet {1} en attente", app.Id, signet.BookmarkName); } // exĂ©cute l'application, app.Run(); } Un clic sur le bouton technique devrait dĂ©clencher le signet correspondant. Mais avant, il faut d’abord charger l’application Ă  partir du rĂ©fĂ©rentiel. Ajoutez un gestionnaire de clic au bouton d’évaluation technique comme ceci : private void btnTechnique_Click(object sender, RoutedEventArgs e) { // charger l'application var app = ChargerApplication(); txtConsole.AppendText(string.Format("La commission technique a donnĂ© une note de {0}\n", cbEval.SelectedIndex + 1)); // dĂ©clencher le signet technique app.ResumeBookmark("EvaluationTechnique", cbEval.SelectedIndex + 1); } De la mĂȘme façon, affectez un gestionnaire de clic au bouton oral et implĂ©mentez-le comme suit : private void btnOral_Click(object sender, RoutedEventArgs e) { var app = ChargerApplication(); txtConsole.AppendText(string.Format("La commission orale a donnĂ© une note de {0}\n", cbEval.SelectedIndex + 1)); app.ResumeBookmark("EvaluationOrale", cbEval.SelectedIndex + 1);  }  Le listing complet de la classe « MainWindow » devrait ĂȘtre comme ceci : /// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{ /// <summary> /// liste des ids des workflows /// </summary> ObservableCollection<Guid> _liste;  /// <summary> /// la chaine de connexion de la base /// </summary> const string connectionString = "Server=.;Initial Catalog=FormationWF;Integrated Security=SSPI";  /// <summary> /// le rĂ©fĂ©rentiel SQL Server /// </summary> private SqlWorkflowInstanceStore _store;  /// <summary> /// indique si on est en dĂ©marrage /// </summary> private bool _demarrage;  /// <summary> /// identitĂ© du workflow /// </summary> private WorkflowIdentity _identity = new WorkflowIdentity() { Name = "Workflow Embauche", Version = new Version(1, 0, 0, 0) };  /// <summary> /// renvoie l'identitĂ© du workflow /// </summary> /// <returns></returns> private WorkflowIdentity GetIdentity() { return _identity; }  private void DesactiverBoutons() { ActiverBouton(btnOral, false); ActiverBouton(btnTechnique, false); }  private void ActiverBouton(Button bouton, bool valeur) { bouton.Dispatcher.BeginInvoke(new Action(() => bouton.IsEnabled = valeur)); }  public MainWindow() { InitializeComponent(); }  private void Grid_Loaded(object sender, RoutedEventArgs e) { Console.SetOut(new TextBoxTextWriter(txtConsole)); DesactiverBoutons(); // configurer le store ConfigurerStore(); // charger les workflows en cours ChargerListeWorkflows(); }  /// <summary> /// charge la liste des workflows Ă  partir de la base de donnĂ©es /// </summary> private void ChargerListeWorkflows() { using (var ctx = new FormationWFEntities()) { _liste = new ObservableCollection<Guid>(ctx.InstancesTables.Select(et => et.Id).ToList()); } cbWorkflows.ItemsSource = _liste; }  /// <summary> /// configure le store SQL Server /// </summary> private void ConfigurerStore() { // crĂ©er le rĂ©fĂ©rentiel en utilisant la chaine de connexion _store = new SqlWorkflowInstanceStore(connectionString); // dĂ©clarer les propriĂ©tĂ©s Ă  intĂ©grer avec la persistance List<XName> variantProperties = new List<XName>(); variantProperties.Add(CandidatParticipant.nomns); _store.Promote("Candidature", variantProperties, null); // dĂ©finir le rĂ©fĂ©rentiel par dĂ©faut WorkflowApplication.CreateDefaultInstanceOwner(_store, null, WorkflowIdentityFilter.Any); }   /// <summary> /// configure l'application /// </summary> /// <param name="app"></param> /// <param name="NomCandidat"></param> private void ConfigurerApplication(WorkflowApplication app, string NomCandidat = null) { // affecter le store app.InstanceStore = _store; // ajouter l'extension pour pouvoir persister le dossier du candidat app.Extensions.Add(new CandidatParticipant() { Nom = NomCandidat }); // Ă©vĂšnement Ă  dĂ©clencher lorsque le workflow est en mode veille app.Idle = delegate(WorkflowApplicationIdleEventArgs args) { var signet = args.Bookmarks.FirstOrDefault(); if (signet == null) return; DesactiverBoutons(); switch (signet.BookmarkName) { case "EvaluationTechnique": ActiverBouton(btnTechnique, true); break; case "EvaluationOrale": ActiverBouton(btnOral, true); break; } };  // Ă©vĂšnement se dĂ©clenchant avant la persistance app.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs args) { Console.WriteLine("Workflow {0} va ĂȘtre persistĂ©", args.InstanceId); return PersistableIdleAction.Unload; };  // affiche un message lorsque le workflow est dĂ©chargĂ© app.Unloaded += delegate(WorkflowApplicationEventArgs args) { Console.WriteLine("Workflow {0} dĂ©chargĂ©", args.InstanceId); };  // se dĂ©clenche lorsque le workflow se termine app.Completed += delegate(WorkflowApplicationCompletedEventArgs args) { var extensions = args.GetInstanceExtensions<CandidatParticipant>(); var dossier = extensions.First(); Console.WriteLine("Workflow {0} terminĂ© avec statut {1}", args.InstanceId, args.CompletionState); Console.WriteLine("L'opĂ©ration d'embauche du dossier {0} a Ă©tĂ© terminĂ©e avec une moyenne de {1}", dossier.Nom, args.Outputs["moyenne"]); DesactiverBoutons(); // supprimer le workflow de la liste cbWorkflows.Dispatcher.Invoke(new Action(() => _liste.Remove(args.InstanceId)));  }; }  /// <summary> /// dĂ©marrer une nouvelle candidature /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnDemarrer_Click(object sender, RoutedEventArgs e) { // crĂ©er le workflow var activity = new EmbaucheWorkflow(); // crĂ©er une nouvelle application var _app = new WorkflowApplication(activity, GetIdentity()); // empĂȘche de dĂ©clencher l'Ă©vĂšnement de changement de combobox _demarrage = true; _liste.Add(_app.Id); cbWorkflows.SelectedIndex = _liste.IndexOf(_app.Id); _demarrage = false; // configure l'application ConfigurerApplication(_app, txtNom.Text); // dĂ©marrer le workflow _app.Run(); Console.WriteLine("Workflow {0} dĂ©marrĂ©", _app.Id); }  private void btnTechnique_Click(object sender, RoutedEventArgs e) { // charger l'application var app = ChargerApplication(); txtConsole.AppendText(string.Format("La commission technique a donnĂ© une note de {0}\n", cbEval.SelectedIndex + 1)); // dĂ©clencher le signet technique app.ResumeBookmark("EvaluationTechnique", cbEval.SelectedIndex + 1); }  private void btnOral_Click(object sender, RoutedEventArgs e) { var app = ChargerApplication(); txtConsole.AppendText(string.Format("La commission orale a donnĂ© une note de {0}\n", cbEval.SelectedIndex + 1)); app.ResumeBookmark("EvaluationOrale", cbEval.SelectedIndex + 1);  }  /// <summary> /// changement de la combo box /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void cbWorkflows_SelectionChanged(object sender, SelectionChangedEventArgs e) { // si dĂ©marrage, ne rien faire if (_demarrage || (cbWorkflows.SelectedItem == null)) return; // charge l'application var app = ChargerApplication(); Console.WriteLine("Workflow {0} chargĂ©", app.Id); // affiche les signets foreach (var signet in app.GetBookmarks()) { Console.WriteLine("Workflow {0}, signet {1} en attente", app.Id, signet.BookmarkName); } // exĂ©cute l'application, app.Run(); }  /// <summary> /// change le workflow persistĂ© /// </summary> /// <returns></returns> private WorkflowApplication ChargerApplication() { // rĂ©cupĂ©rer l'instance Ă  partir du store var instance = WorkflowApplication.GetInstance((Guid)cbWorkflows.SelectedItem, _store); // crĂ©er le workflow en mĂ©moire var activity = new EmbaucheWorkflow(); // crĂ©er l'application var app = new WorkflowApplication(activity, instance.DefinitionIdentity); ConfigurerApplication(app); // chager le workflow app.Load(instance); return app; }}  ExĂ©cutez l’application VĂ©rifiez qu’un workflow reprend mĂȘme aprĂšs avoir quittĂ© l’application VĂ©rifiez le comportement avec plusieurs candidats en mĂȘme temps TĂ©lĂ©chargement Le code du tutoriel peut ĂȘtre obtenu ici. Enjoy !