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 !

Module 9 : Sécurisation des applications ASP.NET

C’est fait, c’est le dernier module de ma formation sur ASP.NET et j’espĂšre que ça a Ă©tĂ© utile. En attendant trĂšs prochainement les cours et les tutoriaux Workflow Foundation, voyons Ă  travers ce modules et les tutoriaux associĂ©s comment sĂ©curiser une application ASP.NET. SĂ©curisation des applications ASP.NET from Mohammed Amine Mostefai

Tutoriel 8.3 : Gestion de l’état

L’objectif de ce tutoriel (module 8) est d’apprendre Ă  utiliser trois mĂ©canismes de gestion de l’état : le cache, les cookies et l’état d’application. Etape 1 : CrĂ©ation d’un compteur de visiteurs en utilisant l’état de l’application L’objectif de cette Ă©tape est d’utiliser l’état de l’application pour implĂ©menter un compteur de visiteurs. L’objectif est aussi de faire la diffĂ©rence entre l’état d’une application et l’état d’une session. Ouvrez VS2012 CrĂ©ez une application ASP.NET vide appelĂ©e « TestEtat » Ajoutez une forme web appelĂ©e « Default.aspx » Glissez deux composants de type « Literal » dans la page et sĂ©parez-les par une balise « <br/> » Ajoutez une classe globale d’application appelĂ©e « Global.asax » Dans l’évĂšnement de dĂ©marrage « Application_Start », ajoutez le code suivant : 1: Application["visiteurs"] = 0; .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Dans le code de dĂ©marrage d’une session « Session_Start », ajoutez le code suivant : 1: int visiteurs = (int)Application["visiteurs"]; 2: ++visiteurs; 3: Application["visiteurs"] = visiteurs; 4: Session["code"] = new Random().Next(); Ouvrez la page « Default.aspx » en mode code behind Ajoutez le code suivant dans l’évĂšnement « Page_Load » : int visiteurs = (int)Application["visiteurs"]; ++visiteurs; Application["visiteurs"] = visiteurs; Session["code"] = new Random().Next(); .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } ExĂ©cutez en appuyant sur « F5 » Remarquez le code de la session et le nombre de visiteurs Ouvrez l’application dans un autre navigateur Remarquez le code de la session et le nombre de visiteurs Revenez sur le navigateur oĂč s’est exĂ©cutĂ©e l’application en premier RafraĂźchissez la page en appuyant sur « F5 » Remarquez que le nombre de visiteurs a changĂ© et que le code de la session est restĂ© le mĂȘme   Etape 2 : Utilisation du cache L’objectif de cette Ă©tape est d’utiliser le cache avec expiration pour cacher une date. Une page simple rĂ©cupĂšre la date d’aujourd’hui qui est cachĂ©e pendant 30 secondes. On remarquera que grĂące au cache, la date ne change que chaque 30 secondes. Ajoutez une forme web appelĂ©e « CachePage.aspx » Glissez un composant « Literal » sur la page Ouvrez la page en mode code behind Dans l’évĂšnement « Page_Load » de la page, ajoutez ceci : 1: Literal1.Text = GetDate().ToString(); .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Dans la classe « CachePage », ajoutez la mĂ©thode suivante : 1: DateTime GetDate() 2: { 3: if (Cache["maintenant"] == null) 4: { 5: Cache.Add("maintenant", DateTime.Now, null, DateTime.Now.AddSeconds(30), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, null); 6: } 7: return (DateTime)Cache["maintenant"]; 8: } ExĂ©cutez en appuyant sur « F5 » Remarquez que la date affichĂ©e ne change qu’aprĂšs 30 secondes en faisant des rafraichissements de la page dans le navigateur Etape 3 : Gestion des cookies L’objectif de cette Ă©tape est d’utiliser les cookies pour mĂ©moriser la premiĂšre visite d’un utilisateur. Ayant une expiration de 30 secondes, cette premiĂšre visite est rĂ©initialisĂ©e chaque 30 secondes. Ajoutez une page appelĂ©e « CookiePage.aspx » Glissez un composant de type « Literal » sur la page Ouvrez la page en mode code behind InsĂ©rez le code suivant dans l’évĂšnement Page_Load 1: var cookie = Request.Cookies["visite"]; 2: if (cookie != null) 3: { 4: DateTime d = DateTime.Parse(cookie["visite"]); 5: Literal1.Text = string.Format("Heure actuelle : {0}, premiĂšre visite : {1}", DateTime.Now, d); 6: } 7: else 8: { 9: Literal1.Text = "Bienvenue"; 10: var ck = new HttpCookie("visite"); 11: ck["visite"] = DateTime.Now.ToString(); 12: ck.Expires = DateTime.Now.AddSeconds(30); 13: Response.Cookies.Add(ck); 14: } Dans la premiĂšre ligne on teste l’existence du cookie. Si il n’existe pas alors il est crĂ©Ă© puis rattachĂ© Ă  la rĂ©ponse. ExĂ©cutez en appuyant sur « F5 » RafraĂźchissez plusieurs fois en appuyant sur « F5 » dans le navigateur VĂ©rifiez que ça se rĂ©initialise aprĂšs 30 secondes Pour tĂ©lĂ©charger le code, cliquez ici