Workflow Foundation et les webservices

Louis-Guillaume MORAND

Dans notre série des articles de démonstration des différentes utilisations de Workflow Foundation, nous allons cette fois nous intéresser à l'utilisation de Workflow Foundation avec les web Services ainsi que l'utilisation de Workflow Foundation en tant que web Service.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Workflow Foundation et les webservices

- Auteur : Louis-Guillaume MORAND

- Parution : 18 février 2009

- Licence : Copyright ® 2009 Louis-Guillaume Morand. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Introduction

- I. L'interaction avec des web Services

- I-A. Consommation de web Service

- I-B. Préparation du web Service

- I-C. Utilisation de l'activité InvokeWebService

- I-D. Mise en place du workflow

- II. Workflow Foundation en tant que web Service

- II-A. Le contrat

- II-B. Préparation du workflow

- II-C. Préparer le web Service

- II-D. Consommation de notre Workflow

- III. Conclusion

- IV. Téléchargements et liens complémentaires

- V. Remerciements

Introduction

Les applications d'aujourd'hui évoluent tous les jours et ces dernières années, les architectures d'applications sont souvent passées d'une architecture client-serveur ou n-tiers à une architecture SOA (Service Oriented Application).
Nous sommes aujourd'hui dans l'ère du web Service omniprésent. Workflow Foundation est bien conscient de cette évolution et c'est pourquoi il permet d'interagir très finement avec des web Services et est même capable d'en devenir un.

Les web Services sont l'un des moyens de communication et d'échanges de données entre applications qui présentent bien souvent le plus d'avantages. De façon non exhaustive, un WS permet :
- d'avoir un moyen de communication multiplateforme (le remoting ne le permet pas) ;
- d'avoir un moyen de communication aisé (pas besoin d'ouvrir de port spécifique, permet de passer à travers les pare-feux) ;
- d'avoir un moyen de communication entre applications n'utilisant pas la même technologie (interopérabilité) ;
- d'utiliser des standards comme moyen de communication (SOAP par exemple) ;
- création facile, et mise à disposition (publication) rapide grâce aux outils de développements qui permettent de les déployer rapidement ;
- et bien d'autres.

C'est à cause de cela que les applications d'aujourd'hui, de plus souvent en n-tiers, utilisent quasi systématiquement les web Services, soit pour les appeler à distance, soit pour présenter une partie de ses fonctionnalités. Il était donc naturel que Workflow Foundation soit développé pour prendre en compte cela et puisse facilement utiliser le principe des web Services.

Nous allons donc voir, au cours de cet article, comment Workflow Foundation permet de profiter pleinement, au sein d'applications distribuées, des web Services.

I - L'interaction avec des web Services

I-A - Consommation de web Service

Pour cette première partie, nous allons voir le côté dit client de la chose, puisque nous serons dans le cas où c'est notre workflow qui consommera un web Service distant.
J'avais pris un exemple simple pour l'article sur la persistance avec Workflow Foundation et j'ai donc décidé que nous prendrions un exemple sensiblement identique et simple. Le fait d'avoir un exemple trivial et sans fioriture nous permettra de nous concentrer sur l'essentiel, à savoir l'appel d'un workflow et l'utilisation de sa réponse.

Ainsi, nous allons partir d'un workflow qui sera chargé de faire cuire un aliment de notre choix (de type Food), et qui interrogera un web Service pour obtenir le temps de cuisson en fonction du type de nourriture.

Classe Food

public class Food
{
public string Type { get; set; }
}

I-B - Préparation du web Service

Du côté « serveur » avec notre web Service, nous créons simplement un projet de type ASP.Net web Service pour y ajouter notre service ne contenant qu'une simple méthode : GetCookingTime

Notre web Service

using System.Web.Services;

namespace developpez.com.workflow.ws
{
[WebService(Namespace = "http://www.developpez.com/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class MonWebService : WebService
{

[WebMethod]
public int GetCookingTime(string food)
{
switch(food)
{
case "egg":
return 120; // en secondes
case "fish":
return 900;
default:
return 300;
}
}
}
}

Nous avons donc une méthode GetCookingTime prenant en paramètre une chaine correspondant au type de nourriture et qui nous renvoie le temps de cuisson en secondes. Il nous restera à le convertir en TimeSpan comme nous le verrons ensuite.

I-C - Utilisation de l'activité InvokeWebService

Maintenant que notre web Service est prêt, il nous faut l'utiliser. La première idée pourrait être de le faire via le code, comme nous le ferions avec une application Webform ou WinForms standard; il ne suffirait en effet que de quelques lignes de code.
Malheureusement, cela ne serait pas utiliser le principe de Workflow et surtout l'avantage du designer qui permet à tout moment de déplacer les activités le long du workflow sans à avoir à modifier une ligne de code. Il nous faut donc trouver un moyen d'utiliser uniquement des activités pour faire cela.

Une fois encore, les créateurs de Workflow Foundation ont pensé à cela et nous ont concocté une activité dédiée à la consommation de web Service : il s'agit de l'activité InvokeWebService (oui, le nom est assez évocateur pour une fois).

Créons dans la même solution, un projet de type Workflow Sequentiel via une application console. Puis, sur le designer, déposez une activité InvokeWebService.

Déposer l'activité aura pour effet d'ouvrir l'assistant d'ajout de référence web. Cliquez alors sur local WebService pour trouver le web Service que nous venons de créer :

Puis cliquez sur votre web Service

Il ne vous reste plus qu'à nommer votre référence web (sur la partie droite) en lui donnant le nom de votre choix.

Ensuite, dans les propriétés de votre activité (F4), choisissez la méthode de votre activité en définissant MethodName. À ce moment-là, grâce au WSDL que vient de charger votre référence web, la liste des paramètres de votre méthode, ainsi que le paramètre de retour, devrait apparaitre.

Nous allons alors pour chaque paramètre (celui d'entrée FoodType, le paramètre de retour ReturnValue), créer des propriétés de dépendance. Cliquez à droite de chaque propriété pour ouvrir un assistant via lequel, vous créerez deux nouvelles propriétés Food et CookingTime (pour la valeur de retour) :

Cela devrait avoir rajouté du code à votre classe de workflow

public static System.Workflow.ComponentModel.DependencyProperty FoodTypeProperty = DependencyProperty.Register("FoodType", typeof(System.String),
typeof(developpez.com.workflow.demo.MonWorkflow));

[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
[BrowsableAttribute(true)]
[CategoryAttribute("Parameters")]
public string FoodType
{
get
{
return ((string)(base.GetValue(developpez.com.workflow.demo.MonWorkflow.FoodTypeProperty)));
}
set
{
base.SetValue(developpez.com.workflow.demo.MonWorkflow.FoodTypeProperty, value);
}
}

public static DependencyProperty CookingTimeProperty = DependencyProperty.Register("CookingTime", typeof(System.Int32),
typeof(developpez.com.workflow.demo.MonWorkflow));

[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
[BrowsableAttribute(true)]
[CategoryAttribute("Parameters")]
public Int32 CookingTime
{
get
{
return ((int)(base.GetValue(developpez.com.workflow.demo.MonWorkflow.CookingTimeProperty)));
}
set
{
base.SetValue(developpez.com.workflow.demo.MonWorkflow.CookingTimeProperty, value);
}
}

L'activité est maintenant pleinement configurée et utilisera la valeur de FoodType et assignera la valeur retournée par le web Service à la propriété CookingTime. Voyons comment mettre en pratique cela.

I-D - Mise en place du workflow

Si vous avez bien suivi les étapes, vous avez dans votre Visual Studio, une solution comprenant au moins, un projet de type web Service, et un projet d'application console Workflow Foundation.

Et dans le fichier program.cs, vous avez le code suivant :

static void Main(string[] args)
{
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); };
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(developpez.com.workflow.demo.MonWorkflow));
instance.Start();

waitHandle.WaitOne();
}
}

Code que nous allons modifier pour passer un paramètre à notre workflow. Rajoutez le code suivant pour créer une instance de la classe Food

Food f = new Food { Type = "egg" };
Dictionary<string, object> dic = new Dictionary<string, object>();
dic.Add("food", f);

Puis modifiez la ligne de lancement du workflow

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(developpez.com.workflow.demo.MonWorkflow),dic);

Ensuite, modifiez la classe de votre workflow pour y ajouter une propriété food

private Food _food;
public Food Food
{
get{ return _food;}
set { _food = value;
FoodType = _food.Type;
}
}

En effet, nous avons passé notre objet à notre workflow, qui l'attribuera automatiquement à la propriété fausse, mais pour le passer à notre web Service, il faut que notre propriété FoodType puisse connaitre cette valeur. Le plus simple est alors de modifier son setter.

Posez ensuite sur votre workflow deux CodeActivity et un Delay.

Chaque CodeActivity affichera un message avec l'heure et l'état du workflow tandis que le delay représentera le temps de cuisson.

private void StartTheCook(object sender, System.EventArgs e)
{
Console.WriteLine("{0} - we start cooking {1}", DateTime.Now.ToShortTimeString(), Food.Type);
}

private void EndTheCook(object sender, System.EventArgs e)
{
Console.WriteLine("{0} - {1} is cooked", DateTime.Now.ToShortTimeString(), Food.Type);
}

Pour finir, sur votre activité d'invocation de web Service, abonnez-vous à l'événement Invoked, qui se déclenche une fois la réponse du web Service obtenue, et rajoutez-y le code qui mettra à jour l'activité de délai, en fonction du résultat obtenu :

private void invokeWebServiceActivity1_Invoked(object sender, InvokeWebServiceEventArgs e)
{
delayActivity1.TimeoutDuration = TimeSpan.FromSeconds(CookingTime);
}

Il ne nous reste plus qu'à lancer le projet et vérifier que la cuisson se passe comme convenu

II - Workflow Foundation en tant que web Service

Nous venons de voir que Workflow foundation pouvait consommer des web Services en envoyant et en recevant des informations. Maintenant, nous allons voir qu'il est capable d'agir en tant que web Service. Facile pourriez vous penser, il suffit de faire un web Service lambda qui appelle une assembly contenant notre workflow.
Oui, je pense que ce serait très facilement faisable, mais niveau fiabilité, il y a sûrement mieux. Néanmoins, il arrivera peut-être un jour où vous aurez besoin de ce cas de figure. Cela ne signifie pas que vous avez fait de mauvais choix, j'indique seulement que si le seul intérêt de votre web Service est d'utiliser un workflow, alors vous pouvez utiliser la solution qui suit.

Que penseriez-vous de l'idée d'un Workflow qui serait directement publié en tant que web Service et sans à avoir à s'occuper de développer nous même la partie publique et toutes les WebMethods qu'il contient ?
Et bien, c'est possible ! Le meilleur de tout cela, c'est que c'est faisable très facilement simplement à l'aide de votre souris et de deux trois clics bien placés.

II-A - Le contrat

Comme tout « service » de communication, un contrat entre le client utilisant le service et le service lui-même est nécessaire. Ce contrat au niveau de votre workflow se fera via une interface. Nous prendrons un exemple simple en créant un workflow à qui vous passerez un objet (un Oeuf pour changer:)) et il sera chargé de le cuire avant de vous le rendre. J'interdis qui que ce soit de critiquer ce superbe exemple qui est totalement différent de celui du chapitre 1 ou encore de celui de l'article sur la persistance.
Passons donc à notre contrat. Créez un projet de type workflow séquentiel et créez à l'intérieur de celui-ci une nouvelle interface nommée ICooker.

Notre interface servant de contrat

interface ICooker
{
Oeuf Cuire(Oeuf monOeuf); // Cuit l'oeuf
}

Créons tout de suite notre classe Oeuf, qui doit être sérialisable puisqu'elle sera transmise au travers du protocole SOAP.

Classe Oeuf

[Serializable]
public class Oeuf
{
public bool Cuit
{
get;
set;
}
}

II-B - Préparation du workflow

La première chose à faire est de choisir dans la boite à outils d'activités de Worfklow Foundation, l'activité WebServiceInput et placez là au début de votre workflow séquentiel. Dans les propriétés de cette activité, commencez par configurer la propriété InterfaceType en allant choisir notre interface ICooker. Puis, configurez la propriété MethodName sur la méthode Cuire();

Vous pouvez remarquer la propriété IsActivating. Cette propriété, propre à l'activité WebServiceInput a un but bien précis, elle sert à démarrer un workflow lorsque l'activité WebServiceInput est appelée de l'extérieur. Cette propriété est censée être réglée sur True sur la première activité de type WebServiceInput de votre workflow. D'ailleurs, un custom validator vous le signal dans le designer grâce au petit point d'exclamation.

Vu que la méthode Cuire, prend un paramètre, Visual Studio nous a automatiquement indiqué qu'il attend un paramètre à lier à « monOeuf », le paramètre d'entrée de la méthode. Cliquez dans la fenêtre de propriété et créez un nouveau membre que vous pouvez appeler MonOeufCru.
Cela aura pour effet de générer le code suivant que vous commencez à comprendre puisqu'il s'agit d'une propriété liée à une propriété de dépendance :

public static DependencyProperty MonOeufCruProperty = DependencyProperty.Register("MonOeufCru", typeof(Oeuf),
typeof(Workflow1));

[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Browsable(true)]
[Category("Parameters")]
public Oeuf MonOeufCru
{
get { return ((Oeuf)(base.GetValue(MonOeufCruProperty))); }
set { base.SetValue(MonOeufCruProperty, value); }
}

Tout est bien configuré et pourtant nous avons une erreur à la compilation. Que se passe-t-il? Tout simplement, l'utilisation d'une activité WebServiceInput va de pair avec une activité WebServiceOutput.
Placez donc une activité WebServiceOutput.

Cette fois-ci, la propriété souhaite que vous précisiez l'activité d'Input. Positionnez la propriété sur le nom de votre activité WebServiceInput. Puis, créez un nouveau membre nommé OeufCuit en tant que paramètre de retour. C'est ce membre qui sera retourné au travers le web Service.

public static DependencyProperty MonOeufCuitProperty = DependencyProperty.Register("MonOeufCuit", typeof(Oeuf),
typeof(Workflow1));

[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Browsable(true)]
[Category("Parameters")]
public Oeuf MonOeufCuit
{
get { return ((Oeuf)(base.GetValue(MonOeufCuitProperty))); }
set { base.SetValue(MonOeufCuitProperty, value); }
}

À ce moment précis, nous avons placé un point d'entrée sur notre workflow et un point de sortie. Nous avons le membre qui récupèrera automatiquement l'objet passé au web Service, ainsi que le membre retourné au client consommant le web Service. Il reste une chose, le corps du workflow. En effet, on reçoit un Oeuf, on en retourne un autre, mais niveau cuisson, ce n'est pas encore ça.

Placez donc entre vos deux activités, une activité de type Code. Créez sur son événement Execute, une nouvelle méthode dans laquelle vous placerez le code se chargeant de cuire l'œuf :

Méthode de cuisson

private void Cuisson_ExecuteCode(object sender, EventArgs e)
{
Oeuf o = MonOeufCru;
o.Cuit = true;
MonOeufCuit = o;
}

La méthode prend l'objet reçu, le cuit, puis l'assigne au membre qui sera retourné. Aussi simple que cela. Dites-vous simplement que le workflow pourrait être un processus complexe, entrainant énormément d'opérations métier.

Voici à quoi ressemble le workflow final :

II-C - Préparer le web Service

« Quel web Service ? » vous demandez-vous. « Je croyais que c'était le workflow le web Service ». C'est presque cela. Le workflow va générer une interface sous forme de web Service qui permettra d'interagir directement avec lui. Pour se faire, cliquez à l'aide du bouton droit sur votre projet de workflow et choisissez Publier en tant que Service web :

II-D - Consommation de notre Workflow

Maintenant que tout est prêt, il ne nous reste plus qu'à appeler notre web Service. Pour cela, créez rapidement un projet console basique, dans lequel vous ajouterez une référence à votre web Service

Sur votre projet console, faites bouton droit > Ajouter web Référence et faites comme le chapitre précédent. Si vous n'avez pas web Référence, faites bouton droit, ajouter Service Référence. Dans la fenêtre qui s'ouvre, cliquez sur Avancé, puis en bas de la seconde fenêtre, sur Ajouter Référence web. Vous devriez alors pouvoir là encore, faire comme le premier chapitre et chercher une référence vers votre web Service autogénéré.

Une fois fait, dans la méthode Main(), rajoutez le code suivant, qui crée une instance d'Oeuf, et l'envoie au web Service pour le récupéré cuit.

Oeuf egg= new Oeuf();
MonWebService.Workflow1_WebService ws = new Workflow1_WebService();

Console.WriteLine("L'oeuf est cuit : {0}",egg.Cuit);
egg = ws.Cuire(egg); // appel au WebService
Console.WriteLine("L'oeuf est cuit : {0}",egg.Cuit);

L'éxécution du projet nous le prouve :

Nous venons donc de voir comment un workflow peut se voir exposé en tant que web Service très facilement et les avantages que cela apporte. Plus besoin de créer un projet de web Service spécifique, servant de wrapper autour de notre workflow, celui se charge lui même de générer l'interface entre le client et lui-même.

III - Conclusion

Workflow Foundation est apparu à un moment où les web Services étaient omniprésents dans les applications distribuées. Il a su montrer qu'il s'avait s'intégrer parfaitement dans ce milieu n-tiers, soit en sachant consommer des web Services distants, soit en se proposant soi-même en tant que web Service facilement consommable.
Ce dernier principe permet notamment au développeur de se focaliser sur le respect des processus métier via des workflows stricts, facilement utilisables par des applications clientes comme des clients légers ou lourds, des sites web ou encore d'autres services web. Ce découpage permet encore mieux de découper les couches des applications en ayant la possibilité de découper les processus métier et de le rendre utilisable à la demande.

IV - Téléchargements et liens complémentaires

Pour télécharger les sources de cet article, cliquez ici

V - Remerciements

Je tiens à remercier ram-0000 pour ses corrections et conseils apportés à l'article