Après avoir parcouru les samples WF4 je croyais que le Bookmark n'avait pas besoin de plus d'explication que cela... Il faut dire que ce sample est vraiment bien fait. Mais après avoir lu les déboires de certain sur le sujet je me rend compte qu'il est utile de tourner le projecteur vers le Bookmark.
Le Bookmark, c'est LA fonctionnalité la plus géniale de Workflow Foundation 4 à mon sens (juste avant les services xamlx et la persistance... qui eux même les utilisent beaucoup). Pour faire simple le Bookmark est une marque (littéralement un signet) ajoutée au workflow à un moment T. Quand cette marque est ajoutée à l'instance de workflow, celui-ci ce met en attente d'un évènement extérieur. Plus précisément, ce n'est pas le workflow qui est en attente, mais la branche du workflow qui contient le Bookmark.
La notion qui semble être trop souvent omise est que lorsque l'hôte du workflow relance la branche en attente, il est possible de passer des données au workflow.
Alors : Comment se code un boorkmark? comment peut on l'utiliser?.. et dans quel type de scénario?
Une fois n'est pas coutume, je vais répondre à mes question à l'envers :
- Dans quel type de scénario peut on utiliser un Bookmark?Le bookmark est le composant idéal pour un scénario de validation. Dans ce type de scénario, on a souvent besoin qu'une personne réponde à une question par Oui ou Non (voir même peut être).
ex: Un workflow passe par une activité qui contient un bookmark :
- Quand ce bookmark est activé l'utilisateur reçoit une message et le workflow persiste en base de donnée (ex :boite de dialogue... mais ce peut aussi être un mail).
- L'utilisateur décide de donner une réponse à ce message (ex: oui ou non dans la boite de dialogue).
- Le workflow est ravivé et le bookmark utilisé pour passer la réponse.
- Le workflow poursuit son exécution après avoir analysé la réponse de l'utilisateur.
Le bookmark s'utilise avec un WorkflowApplication ou un WorkflowServiceHost et non pas avec le WorkflowInvoker. La raison en est toute simple : une instance de WorkflowApplication ou WorkflowServiceHost peut rendre accessible la collection de Bookmarks du workflow en cours d'exécution et non le WorkflowInvoker.
Pour connaitre la liste des Bookmarks d'un workflow il suffit alors d'utiliser la méthode GetBookMarks() de l'instance de WorkflowApplication.
Pour raviver un bookmark on dispose d'une méthode ResumeBookmark(). Cette méthode a plusieurs surcharges afin de permettre d'identifier le Bookmark ciblé. Cependant, le plus intéressant pour chacune d'entre elles, reste le second argument. Cet argument de type Object représente l'information à remonter au workflow (ex: un boolean pour une validation).
Imaginons que l'on veuille utiliser un Bookmark nommé "ValideMyWorkflow" et lui passer un Boolean vrai :
myWorkflowApplication.ResumeBookmark("ValideMyWorkflow",true);
Voici donc les bases de l'utilisation ;-).
- Comment ce code le BookMark?Vous l'avez certainement déjà compris, on ne peut pas trouver de Bookmark comme cela dans la nature. Il faut les coder dans une activité. L'activité qui accueil un Bookmark est un peu particulière dans la masure où elle doit avoir le droit de mettre en attente un workflow (IDLE).
Petit retour en arrière dans le temps : Pour éviter les scénarios de workflows de WF3 qui une fois en attente pouvaient faire monter le CPU de nos "chères" machine à 100%, Microsoft a mise a disposition un nouveau dispositif d'IDLE. Par défaut une activité n'a pas le droit de faire passer une instance de workflow en IDLE. Pour autoriser ce comportement il faut que la propriété CanInduceIdle retourne toujours vrai.
Notre activité doit donc impérativement contenir le code suivant et hérité de NativeActivity (CodeActivity ne suffit pas pour l'IDLE):
protected override Boolean CanInduceIdle { get { return true; } }
Ensuite arrive l'insertion de notre Bookmark et de son CallBack. Contrairement à une grande légende urbaine, ce CallBack est très important. Ce Callback est "le point de retour" du Bookmark quand on le ravive. C'est donc cette méthode qui va servir à traiter les éventuelles données à introduire dans le workflow.
Rappelez vous le scénario de validation présenté un peu plus haut, si on retourne un Boolean, c'est par ce callback qu'il entrera dans le workflow.
Imaginons donc que notre activité attende un Boolean et qu'elle ait un argument de sortie chargé de mettre à disposition le retour formulé par l'utilisateur. Voici le callback correspondant :
private void MyCallBack(NativeActivityContext context, Bookmark bookmark, Object o) { Boolean value = (Boolean)o; this.Result.Set(context, value); }
Après tout cela arrive donc le code que tout le monde attend : le moment où le Bookmark est ajouté au workflow. Cela ce produit durant l'exécution. Il est donc logique de trouve ce code dans la méthode Execute(). Si on reste dans l'idée du scénario de validation présenté jusqu'ici :
protected override void Execute(NativeActivityContext context) { context.CreateBookmark("ValideMyWorkflow", new BookmarkCallback(this.MyCallBack)); }
Comme on peut le voir, introduire un Bookmark n'est pas si compliqué que cela.
Pour reprendre le code dans sa totalité : une activité permettant une validation par échange d'un Boolean entre le workflow et le monde extérieur pourrait se présenter comme ceci :
public sealed class MyValidationActivity : NativeActivity<Boolean> { protected override void Execute(NativeActivityContext context) { context.CreateBookmark("ValideMyWorkflow", new BookmarkCallback(this.MyCallBack)); } protected override Boolean CanInduceIdle { get { return true; } } private void MyCallBack(NativeActivityContext context, Bookmark bookmark, Object o) { Boolean value = (Boolean)o; this.Result.Set(context, value); } }
La pluparts des erreurs d'utilisation des bookmarks sont dues au fait que le développeur à l'origine du projet a oublié que le callback sert à remonter une variable. C'est ainsi que dernièrement j'ai assisté une personne qui avait introduit deux bookmarks dans une activité de validation : un pour indiquer que l'utilisateur était d'accord et l'autre pour indiquer le contraire. Grosse erreur, car pour que le workflow se poursuive il faut que les deux bookmarks soient relancés par des événements extérieurs... inconcevable dans ce scénario... vous vous voyez dire oui et non en même temps?! Dans ce cadre, un seul bookmark est utile.
Ne rêvez surtout pas à un code tel que celui ci-dessous qui comme je l'ai présenté plus haut est anti-Bookmark (ou anti-workflow car il ne pourra jamais se poursuivre) :
public sealed class MyValidationActivity: NativeActivity<Boolean> { protected override void Execute(NativeActivityContext context) { context.CreateBookmark("True", new BookmarkCallback(this.CallBackTrue)); context.CreateBookmark("False", new BookmarkCallback(this.CallBackFalse)); } protected override Boolean CanInduceIdle { get { return true; } } private void CallBackTrue(NativeActivityContext context, Bookmark bookmark, Object o) { this.Result.Set(context, true); } private void CallBackFalse(NativeActivityContext context, Bookmark bookmark, Object o) { this.Result.Set(context, false); } }