Extensibilité de WCF (1/3)

Une des forces de WCF est sa souplesse. Il est possible d'ajouter/supprimer/modifier des modules en quelques minutes. Ce sujet est abordé dans 3 de mes prochains articles. J'expliquerai comment vérifier les paramètres d'entrée, comment mettre en place un "traitement" spécial lors de l'invoke d'une méthode dans le service WCF et enfin comment ajouter ou modifier la sécurité dans WCF sans passer par une sécurité Transport (et donc par exemple par de l'https ...). Mais aujourd'hui, je parle de la validation de paramètres d'entrées ou encore les potentiels traitements spéciaux à effectuer lors de l'appel d'une méthode d'un service.

1) Cas concret

Prenons donc un service WCF retournant un "Country". Ci-dessous le contrat et l'implémentation:
[ServiceContract]
public interface ICountryService
{
    [OperationContract]
    Country GetCountry(string code);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyCountryService : ICountryService
{
    public Country GetCountry(string code)
    {
        return CountryDAL.GetCountry(code);
    }
}

2) Ajout de la validation des inputs

Il est possible assez facilement d'ajouter des validations/modifications des paramètres d'entée dans un service WCF au niveau du serveur (et/ou au niveau du client). Pour se faire nous allons implémenter l'interface IParameterInspector. Nous allons pouvoir vérifier que l'utilisateur a bien envoyé un bon code de pays connu par notre système.
public class CountryCodeValidation : IParameterInspector
{
    public void AfterCall(string operationName, object[] outputs, 
                        object returnValue, object correlationState)
    {
    }
    public object BeforeCall(string operationName, object[] inputs)
    {
        string code = inputs[0] as string;
        if (string.IsNullOrEmpty(code) 
            || !new[] { "BE""FR""UK""USA" }.Any(elt => elt == code))
            throw new FaultException("Code inexistant");
        return null;
    }
}

3) Liaison de notre validation à l'appel de la méthode

Nous devons maintenant "lier" notre classe de validation d'input avec notre service. Pour ce faire, nous avons en premier à implémenter un Attribute que l'on positionnera au dessus de la méthode. En réalité, nous rajoutons via cet attribut un nouveau "behavior" sur notre operation. Nous devons donc implémenter l'interface IOperationBehavior.
public class ServerCountryValidationAttribute : AttributeIOperationBehavior
{
    public void AddBindingParameters(OperationDescription operationDescription, 
                                        BindingParameterCollection bindingParameters)
    {
    }
    public void ApplyClientBehavior(OperationDescription operationDescription, 
                                        ClientOperation clientOperation)
    {
    }
    public void ApplyDispatchBehavior(OperationDescription operationDescription, 
                                        DispatchOperation dispatchOperation)
    {
        CountryCodeValidation validation = new CountryCodeValidation();
        dispatchOperation.ParameterInspectors.Add(validation);
    }
    public void Validate(OperationDescription operationDescription)
    {
    }
}
Nous lions donc bien notre méthode avec cet attribut:
[ServiceContract]
public interface ICountryService
{
    [OperationContract]
    [ServerCountryValidation]
    Country GetCountry(string code);
}
Nous pouvons également effectuer cet appel au niveau du client (si celui-ci est en .NET). Ceci évite un appel vers le service en cas d'échec. Pour ce faire, nous allons implémenter dans la classe ServerContryValidationAttribute  la méthode ApplyClientBehavior (ci-dessous et ci-dessus).
public void ApplyClientBehavior(OperationDescription operationDescription, 
                                                ClientOperation clientOperation)
{
    CountryCodeValidation validation = new CountryCodeValidation();
    clientOperation.ParameterInspectors.Add(validation);
}
Nous pouvons donc ajouter la validation dans le client comme montré ci-dessous:
CountryServiceClient client = new CountryServiceClient();
client.Endpoint.Contract
                .Operations
                .First(elt => elt.Name == "GetCountry")
                .Behaviors
                .Add(new ClientCountryCodeValidation());

4) Interception de l'appel de la méthode du service

Il est possible de "catcher" l'exécution d'une méthode et d'altérer les objets en entrée,etc. Pour ce faire, il faut implémenter l'interface IOperationInvoker. Dans l'exemple ci-dessous, nous allons mettre en mémoire les Country déjà appelés afin de réduire les appels DB.
public class CountryCaching : IOperationInvoker
{
    IOperationInvoker baseInvoker;
    Dictionary<stringobject> cache = new Dictionary<stringobject>();
    public CountryCaching(IOperationInvoker invoker)
    {
        this.baseInvoker = invoker;
    }
    public object Invoke(object instance, object[] inputs, out object[] outputs)
    {
        outputs = new object[0];
        string code = inputs[0] as string;
        object value;
        if (!this.cache.TryGetValue(code, out value))
        {
            value = this.baseInvoker.Invoke(instance, inputs, out outputs);
            this.cache.Add(code, value);
        }
        return value;
    }
    public object[] AllocateInputs()
    {
        return this.baseInvoker.AllocateInputs();
    }
    public IAsyncResult InvokeBegin(object instance, object[] inputs,
                                                AsyncCallback callback, object state)
    {
        return this.baseInvoker.InvokeBegin(instance, inputs, callback, state);
    }
    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
    {
        return this.baseInvoker.InvokeEnd(instance, out outputs, result);
    }
    public bool IsSynchronous
    {
        get { return this.baseInvoker.IsSynchronous; }
    }
}
Nous devons après modifier dans notre classe ServerContryValidationAttribute la méthode ApplyDispatchBehavior.
public void ApplyDispatchBehavior(OperationDescription operationDescription, 
                                                   DispatchOperation dispatchOperation)
{
    CountryCodeValidation validation = new CountryCodeValidation();
    dispatchOperation.ParameterInspectors.Add(validation);
    dispatchOperation.Invoker = new CountryCaching(dispatchOperation.Invoker);
}
Voici donc expliqué en quelques mots comment modifier des paramètres ou altérer l'invocation de la méthode dans le service. Le prochain article se portera sur la modification des couches d'identification.

Commentaires

1. Le mardi, mai 24 2011, 15:53 par raimond

slut j'ai trouvé ton tuto très bien ? Moi j'ai envi de modifier les informations envoyée par le serveur avant qu'il ne soit renvoyer. si tu peux aider avec un petite implementation cela me fera plaisir( par exemple modification d'un fichier qui est generé par le serveur avant son envoie au client

merci

2. Le vendredi, mai 27 2011, 10:54 par Thierry THOUA

Il faut utiliser l'interface : IDispatchMessageInspector. Il est possible de voir le message avant l'envoi de celui-ci au client.

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.

Fil des commentaires de ce billet

Page top