Utiliser un contrat WCF avec WF4? Oui mais alors Non, pas comme ça!!!

Il y a une fausse (mais alors vraiment fausse) information qui cours au sujet de WF4 sur l’utilisation des contrats de service et des contrats de données provenant de WCF.

En donnant à la propriété ServiceContractName de l’activité Receive le nom du contrat de service WCF, le service va respecter le contrat.

Quand le lit ou que j’entend ça … “disons que je m’interroge”… sur de nombreux sujet, mais je contiens mon envie de hurler!…

Ce n’est pas le support qui fait la légitimité d’une information (blogs reconnus, livres, vidéos, conférence réputés… etc...), mais la manière dont c’est documenté son rédacteur et l’esprit critique dont il  a fait preuve

(ne cherchez pas, ce n’est pas une citation, mais mon avis personnel)

WF c’est WF! WCF c’est WCF! Exposer un workflow via le WorkflowServiceHost, c’est une manière particulière d’utiliser WCF pour exposer un Workflow. Ce qui s’applique à WCF ne s’applique donc pas forcement à WF, mais WF à accès à presque toutes les fonctionnalités de WCF. Ce n’est donc pas en supposant qu’une propriété fait une chose, qu’elle le fait. Ceci s’applique surtout à WF.

C’est qu’elle est taquine cette technologie ;)

Je sors un peu de mon mode "coup de gueule" pour passer au mode j’explique :

ServciceContractName n’est en fait que le nom de contrat qui serra écrit dans les échanges SOAP qui surviendront entre le client et le serveur. Cela n’a aucun impact sur l’aspect fonctionnel du service.

Afin de démontrer les possibilités de paramétrage d’un service WF/WCF, j’ai codé un petit exemple simple. Dans mon exemple, l’objectif est d’exposer un service disposant d’une méthode retournant un utilisateur. Côté serveur j’ai donc réalisé le code commun qui suit (ce code serra utilisé dans les différentes implémentation de mon service) :

using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace Demo.WCF.Abc
{
    /// <summary>
    /// Mon contrat de service
    /// </summary>
    [ServiceContract]
    public interface IMyService
    {
        [OperationContract]
        User GetUser(Int32 id);
    }

    /// <summary>
    /// Mon contrat de donnée
    /// </summary>
    [DataContract]
    public class User
    {
        [DataMember]
        public Int32 Id { get; set; }
        [DataMember]
        public String FirstName { get; set; }
        [DataMember]
        public String Name { get; set; }
    }

    /// <summary>
    /// Ma source de données
    /// </summary>
    public static class MyData
    {
        private static User[] s_Users;

        /// <summary>
        /// Constructeur static
        /// </summary>
        static MyData()
        {
            s_Users = new User[] {
                        new User{ Id =1, FirstName ="Donald", Name ="Duc" },
                        new User{ Id =2, FirstName ="John", Name ="Doe" }
                    };
        }

        /// <summary>
        /// Recherche d'un utilisateur par son id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static User GetUser(Int32 id)
        {
            return s_Users.FirstOrDefault(u => u.Id == id);
        }
    }
}

Pas grand chose à dire sur ce code, c’est l’usage que je vais en faire qui aura sont importance ;)

1 – Création d’un Service WCF classique :

J’ai donc codé un service basé sur le contrat précédent :

public class ServiceWcf : IMyService
{
    public User GetUser(Int32 id)
    {
        return MyData.GetUser(id);
    }
}

Celui-ci est disponible via le endpoint créé par défaut avec ma classeWcf (ServiceWcf.svc) classique (je n’ai pas touché à la configuration du binding). Quand on fait un appel au service via le client de test WCf on a un appercu tel que celui-ci.

wf_wcf_contract01


Tout est bon, il s’agit du comportement attendu ;)

2 – Création d’un Service WF/WCF :

J’ai utilisé le Template de base fourni par Visual Studio 2010 pour créé un service WF. Par défaut ce workflow contient une méthode qui attend un entier et retourne ce même entier convertie en String. Ma seule modification à consister à changer la propriété ServciceContractName par le nom de l’interface censées représenter mon contact:

wf_wcf_contract02


Quand je compile, tout se passe bien. Pourtant comme vous pouvez le constater, mon service ne cadre pas avec le contact. Pour les plus “incrédules”, j’ai fait une capture du comportement de ce service si on le consomme :

wf_wcf_contract03


Je crois que la démonstration est sans appel. On voit clairement que la propriété ServciceContractName n’a permit que de donner un nom au contact déduit lors de la génération du proxy ;)

3 – Déjouer les travers du Wizard d’édition des messages

Les habitués du client de test WCF se sont certainement rendu compte que la saisie de l’argument différait dans le cas d’un service Workflow Foundation. Ceci est normal, cette différence vient du fait que l’on utilise le type Message pour passer nos donnée à WCF. Oui, Message : ce truc pas drôle à utiliser qui nous viens du merveilleux monde POX (Plain Old XML).

Afin de vous présenter les incidences de l’utilisation du wizard WF qui vous pousse par défaut à utiliser Message, j’ai réaliser 3 services dans lesquelles je n’ai fait que changer la manière dont j’échangeais mes données avec WCF. La base du service à  la suivante :

wf_wcf_contract04


Dans le cas du ServiceWF1.xamlx j’ai utilisé les messages suivant :

ReceiveRequest :

wf_wcf_contract05


SendResponse :

wf_wcf_contract06


Dans le cas du ServiceWF2.xamlx j’ai utilisé les messages suivant :

ReceiveRequest :

wf_wcf_contract07


SendResponse :

wf_wcf_contract08


Dans le cas du ServiceWF3.xamlx j’ai utilisé les messages suivant :

ReceiveRequest : la même chose que ServiceWF2.xamlx

SendResponse :

wf_wcf_contract09


Ces 3 services m’on données chaque fois des comportement différents :

ServiceWF1.xamlx

wf_wcf_contract10


ServiceWF2.xamlx

wf_wcf_contract11


ServiceWF3.xamlx

wf_wcf_contract12


Seule le dernier service permet d’avoir une définition du service identique à celle du même service ServiceWcf.svc. Mais là encore il s’agit d’une petite subtilité : afin que le message SOAP de réponse soit identique, j’ai changé le nom de mon argument de sortie par GetUserResult. Facile à reproduire, mais entièrement obtenu par déduction ;)

Conclusion

Si vous voulez cadrer avec l’usage des contrats, tels qu’ils sont dans l’ABC WCF, utiliser des arguments en entrée et sortie de vos activité de services ;)

PS : Ron Jacobs aillant publié un article sur l’astuce du nommage d’arguments des activités SendResponse ce vendredi, je suis un peu confus. Aillant préparé ce post il y a déjà quelques semaines pour une série spéciale WF+WCF, je ne voulais pas jeter mon travail à la corbeille, donc je vous le livre aujourd’hui. A noter que malgré sont titre : How to make a WorkflowService implement a contract, on reste dans le même esprit.

Actuellement, on n’implémente pas un contrat, on fait en sorte d’y coller!

Jérémy Jeanson

Comments

You have to be logged in to comment this post.