Comment loguer les flux soap d'un client WCF ?

Tags: WCF

Imaginons que vous ayez un client qui doit consommer un web service. Avec Visual Studio, rien de plus simple, il suffit d’ajouter une référence de service pour que tout le code soit généré pour vous.

Mais si vous êtes dans un environnement “professionnel”, il est nécessaire, obligatoire, de pouvoir loguer les échanges entre le client et le serveur.

Or par définition, comme svcutil.exe vous a généré les classes/objets qu’il faut, vous ne manipuler que des objets, jamais les flux Xml qui sont sérialisés et désérialisés par les classes du framework.

Or si vous avez un problème d’échange avec le serveur, il y a de forte chance que le responsable du serveur vous demande :

“Tu m’as envoyé quoi comme message xml ? Et tu as reçu quoi ?”

Et là : c’est le drame ! Clignement d'œil

Car il n’y a rien qui vous permet d’accéder à cette information, du moins de façon trivial.

Avec les WebServices de l’ancien temps (ie pas sous WCF, les WebServices qui sont apparus avec le framework 1.0 quoi), vous deviez créer une classe dérivant  de SoapExtension et sa méthode ProcessMessage était appelée à chaque étape du process (ie avant/après envoi du message et avant/après réception du message. (voir un article que j’ai trouvé qui est très bien fait).

Mais ce n’était franchement pas très pratique car vous ne pouviez, dans le code utilisant le client du webservice, modifier/accéder à l’objet dérivant de SoapExtension.

Pour nous, avec WCF, c’est plus simple (enfin presque).

IClientMessageInspector

Le principe est dans un premier temps de créer une classe qui hérite de System.ServiceModel.Dispatcher.IClientMessageInspector (dans System.ServiceModel). On doit juste définir 2 méthodes qui sont appelées à l’envoi et à la réception du message :

public class MyMessageInspector : IClientMessageInspector
{
    #region Implementation of IClientMessageInspector

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        XmlRequest = request.ToString();
        return null;
    }

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        XmlResponse = reply.ToString();
    }

    #endregion

    /// <summary>
    /// Xml envoyé
    /// </summary>
    public string XmlRequest { get; set; }

    /// <summary>
    /// Xml reçu
    /// </summary>
    public string XmlResponse { get; set; }
}

 

Rien de compliqué vous l’avouerez. Attention à une chose importante : si vous voulez vous amuser avec le message (le modifier par exemple), ne travaillez jamais avec le message lui même mais avec une copie. Heureusement, il y a des outils pour cela :

var buffer = request.CreateBufferedCopy(int.MaxValue);
var message = buffer.CreateMessage();
// on s'amuse ici
buffer.Close();

 

Notre IClientMessageInspector permet donc de stocker les flux entrant sortant. Il faut maintenant le lier avec notre client.

Pour cela, on a la possibilité d’ajouter un behavior (comportement) à notre service. Ce behavior fera le lien entre notre classe IClientMessageInspector et notre client du service.

IEndpointBehavior

Nous allons donc créer une classe implémentant l’interface IEndpointBehavior. Sa seule fonctionnalité sera d’ajouter notre IClientMessageInspector à la liste des Inspector de notre service. Son code est donc très simple :

public class MyEndpointBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {}

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        // instanciation de l'inspector
        Inspector = new MyMessageInspector();
        // ajout de l'inspector pour notre service
        clientRuntime.MessageInspectors.Add(Inspector);
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {}

    public void Validate(ServiceEndpoint endpoint)
    {}

    /// <summary>
    /// L'inspector
    /// </summary>
    public MyMessageInspector Inspector { get; set; }
}

 

Il ne nous reste plus qu’à ajouter le comportement (behavior) aux comportements de notre service.

Paramétrage du service

Pour paramétrer un service, il y a 2 options :

  • Soit vous vous amusez à écrire des centaines de lignes dans le fichier de configuration de votre application,
  • Soit vous ajoutez des centaines de lignes de codes au moment de l’instanciation de votre service.

Personnellement, je préfère la seconde solution (mais cela n’engage que moi). Donc vous aurez un code du genre :

var binding = new BasicHttpBinding();

binding.ReceiveTimeout = new TimeSpan(0, 5, 0);
binding.SendTimeout = new TimeSpan(0, 5, 0);

binding.CloseTimeout = new TimeSpan(0, 5, 0);
binding.OpenTimeout = new TimeSpan(0, 5, 0);

binding.MaxReceivedMessageSize = Int32.MaxValue;
binding.MaxBufferSize = Int32.MaxValue;

binding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
binding.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
binding.ReaderQuotas.MaxNameTableCharCount = Int32.MaxValue;

binding.Security.Mode = BasicHttpSecurityMode.None;
// ServiceUrl est une propriété qui indique l'adresse du service
_client = new MyServiceClient(binding, new EndpointAddress(ServiceUrl));
_client.Endpoint.Behaviors.Add(new MyEndpointBehavior());

 

Il suffit maintenant, juste après l’utilisation de la méthode du service, de récupérer les flux xml envoyés/réceptionnés en retrouvant notre comportement :

var response = _client.MethodeDuService(argumentsDuService);

var myEndpointBehavior = _client.Endpoint.Behaviors.OfType<MyEndpointBehavior>().FirstOrDefault();
if (myEndpointBehavior != null)
{
    XmlRequest = myEndpointBehavior.Inspector.XmlRequest;
    XmlResponse = myEndpointBehavior.Inspector.XmlResponse;
}

 

Et voila.

Si vous avez des commentaires à faire, n’hésitez pas.

blog comments powered by Disqus