Partager via


Custom Message Encoder: Compression Encoder

Cet exemple montre comment implémenter un codeur personnalisé à l'aide de la plateforme Windows Communication Foundation (WCF). Cet exemple se compose d'un programme de console cliente (.exe), d'un programme de console de service auto-hébergé (.exe) et d'une bibliothèque de codeurs de message de compression (.dll). Le service implémente un contrat qui définit un modèle de communication demande-réponse. Le contrat est défini par l'interface ISampleServer, laquelle expose la chaîne de base qui reproduit en écho les opérations (Echo et BigEcho). Le client adresse des demandes synchrones à une opération donnée et le service répond en répétant de nouveau le message au client. L'activité du client et du service est visible dans les fenêtres de console. Cet exemple montre comment écrire un codeur personnalisé et présente l'impact de la compression d'un message sur le câble. Vous pouvez ajouter l'instrumentation au codeur de message de compression afin de calculer la taille de message, le temps de traitement ou les deux.

L'exemple montre comment créer et intégrer un codeur de message personnalisé dans une application WCF. La bibliothèque GZipEncoder.dll est déployée avec le client et le service. Cet exemple montre également l'impact de la compression des messages. Le code dans GZipEncoder.dll présente les procédures suivantes :

  • Création d'un codeur personnalisé et d'une fabrique de codeur.
  • Développement d'un élément de liaison pour un codeur personnalisé.
  • Utilisation de la configuration de liaison personnalisée permettant d'intégrer des éléments de liaison personnalisés.
  • Développement d'un gestionnaire de configuration personnalisé afin de permettre la configuration de fichier d'un élément de liaison personnalisé.

Comme indiqué précédemment, plusieurs couches sont implémentées dans un codeur personnalisé. Pour mieux illustrer la relation entre chacune d'elles, la liste suivante présente un ordre simplifié des événements de démarrage du service :

  1. Le serveur démarre.
  2. Les informations de configuration sont lues.
    1. La configuration du service enregistre le gestionnaire de configuration personnalisé.
    2. L'hôte de service est créé et ouvert.
    3. L'élément de configuration personnalisé crée et retourne l'élément de liaison personnalisé.
    4. L'élément de liaison personnalisé crée et retourne une fabrique de codeur de message.
  3. Un message est reçu.
  4. La fabrique retourne un codeur de message permettant de lire dans le message et d'écrire la réponse.
  5. La couche de codeur est implémentée sous forme de fabrique de classe. Seule la fabrique de classe de codeur doit être exposée publiquement pour le codeur personnalisé. L'objet de fabrique est retourné par l'élément de liaison lorsque l'objet ServiceHost ou ChannelFactory est créé. Les codeurs de message peuvent fonctionner en mode de mise en mémoire tampon ou en mode de diffusion en continu. Cet exemple présente ces deux modes.

Chaque mode est associée à une méthode ReadMessage et WriteMessage sur la classe abstraite MessageEncoder. La majeure partie du codage a lieu dans ces méthodes. L'exemple encapsule les codeurs de message texte et binaire existants. Cela permet à l'exemple de déléguer la lecture et l'écriture de la représentation de câble des messages au codeur interne, et au codeur de compression de compresser ou de décompresser les résultats. Comme il n'existe aucun pipeline pour le codage de message, c'est le seul modèle permettant d'utiliser plusieurs codeurs dans WCF. Une fois que le message a été décompressé, le message résultant est passé en haut de la pile pour être géré par la pile de canaux. Pendant la compression, le message compressé résultant est écrit directement dans le flux fourni.

Cet exemple utilise des méthodes d'assistance (CompressBuffer et DecompressBuffer) permettant d'effectuer la conversion des mémoires tampon en flux afin d'utiliser la classe GZipStream.

Les classes ReadMessage et WriteMessage mises en mémoire tampon utilisent la classe BufferManager. Le codeur est uniquement accessible via la fabrique de codeur. La classe abstraite MessageEncoderFactory fournit la propriété Encoder permettant d'accéder au codeur actuel et à la méthode CreateSessionEncoder permettant de créer un codeur qui prend les sessions en charge. Un codeur de ce type peut être utilisé dans le scénario où le canal prend des sessions en charge, est ordonné et est fiable. Ce scénario permet d'optimiser dans chaque session les données écrites sur le câble. Si cet objectif n'est pas souhaité, la méthode de base ne doit pas être surchargée. La propriété Encoder fournit un mécanisme permettant accéder au codeur sans session, et l'implémentation par défaut de la méthode CreateSessionEncoder retourne la valeur de la propriété. Cet exemple encapsulant un codeur existant pour fournir la compression, l'implémentation MessageEncoderFactory accepte un MessageEncoderFactory qui représente la fabrique de codeur interne.

Le codeur et la fabrique de codeur étant maintenant définis, ils peuvent être utilisés avec un client et un service WCF. Toutefois, ces codeurs doivent être ajoutés à la pile de canaux. Vous pouvez dériver des classes des classes ServiceHost et ChannelFactory, et substituer les méthodes OnInitialize pour ajouter cette fabrique de codeur manuellement. Vous pouvez également exposer la fabrique de codeur via un élément de liaison personnalisé.

Pour créer un élément de liaison personnalisé, dérivez une classe de la classe BindingElement. Cependant, il existe plusieurs types d'éléments de liaison. Pour s'assurer que l'élément de liaison personnalisé est reconnu en tant qu'élément de liaison de codage de message, vous devez également implémenter MessageEncodingBindingElement. MessageEncodingBindingElement expose une méthode permettant de créer une nouvelle fabrique de codeur de message (CreateMessageEncoderFactory), laquelle est implémentée pour retourner une instance de la fabrique de codeur de message correspondante. En outre, MessageEncodingBindingElement possède une propriété permettant d'indiquer la version d'adressage. Cet exemple encapsulant les codeurs existants, l'implémentation encapsule également les éléments de liaison de codeur existants et fournit un élément de liaison de codeur interne comme paramètre au constructeur, puis l'expose via une propriété. L'exemple de code suivant présente l'implémentation de la classe GZipMessageEncodingBindingElement.

public sealed class GZipMessageEncodingBindingElement 
                        : MessageEncodingBindingElement //BindingElement
                        , IPolicyExportExtension
{

    //We use an inner binding element to store information 
    //required for the inner encoder.
    MessageEncodingBindingElement innerBindingElement;

        //By default, use the default text encoder as the inner encoder.
        public GZipMessageEncodingBindingElement()
            : this(new TextMessageEncodingBindingElement()) { }

    public GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement)
    {
        this.innerBindingElement = messageEncoderBindingElement;
    }

    public MessageEncodingBindingElement InnerMessageEncodingBindingElement
    {
        get { return innerBindingElement; }
        set { innerBindingElement = value; }
    }

    //Main entry point into the encoder binding element. 
    // Called by WCF to get the factory that creates the
    //message encoder.
    public override MessageEncoderFactory CreateMessageEncoderFactory()
    {
        return new 
GZipMessageEncoderFactory(innerBindingElement.CreateMessageEncoderFactory());
    }
    
    public override MessageVersion MessageVersion
    {
        get { return innerBindingElement.MessageVersion; }
        set { innerBindingElement.MessageVersion = value; }
    }

    public override BindingElement Clone()
    {
        return new 
        GZipMessageEncodingBindingElement(this.innerBindingElement);
    }

    public override T GetProperty<T>(BindingContext context)
    {
        if (typeof(T) == typeof(XmlDictionaryReaderQuotas))
        {
            return innerBindingElement.GetProperty<T>(context);
        }
        else 
        {
            return base.GetProperty<T>(context);
        }
    }

    public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BindingParameters.Add(this);
        return context.BuildInnerChannelFactory<TChannel>();
    }

    public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BindingParameters.Add(this);
        return context.BuildInnerChannelListener<TChannel>();
    }

    public override bool CanBuildChannelListener<TChannel>(BindingContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BindingParameters.Add(this);
        return context.CanBuildInnerChannelListener<TChannel>();
    }

    void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext policyContext)
    {
        if (policyContext == null)
        {
            throw new ArgumentNullException("policyContext");
        }
       XmlDocument document = new XmlDocument();
       policyContext.GetBindingAssertions().Add(document.CreateElement(
            GZipMessageEncodingPolicyConstants.GZipEncodingPrefix,
            GZipMessageEncodingPolicyConstants.GZipEncodingName,
            GZipMessageEncodingPolicyConstants.GZipEncodingNamespace));
    }
}

Notez que la classe GZipMessageEncodingBindingElement implémente l'interface IPolicyExportExtension, afin que cet élément de liaison puisse être exporté en tant que stratégie dans les métadonnées, tel qu'indiqué dans l'exemple suivant.

<wsp:Policy wsu:Id="BufferedHttpSampleServer_ISampleServer_policy">
    <wsp:ExactlyOne>
      <wsp:All>
        <gzip:text xmlns:gzip=
        "https://schemas.microsoft.com/ws/06/2004/mspolicy/netgzip1" /> 
       <wsaw:UsingAddressing /> 
     </wsp:All>
   </wsp:ExactlyOne>
</wsp:Policy>

La classe GZipMessageEncodingBindingElementImporter implémente l'interface IPolicyImportExtension et importe la stratégie pour GZipMessageEncodingBindingElement. L'outil Svcutil.exe permet d'importer des stratégies dans le fichier de configuration et de gérer GZipMessageEncodingBindingElement ; les éléments suivants doivent être ajoutés à Svcutil.exe.config.

<configuration>
  <system.serviceModel>
    <extensions>
      <bindingElementExtensions>
        <add name="gzipMessageEncoding" 
          type=
            "Microsoft.ServiceModel.Samples.GZipMessageEncodingElement, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </bindingElementExtensions>
    </extensions>
    <client>
      <metadata>
        <policyImporters>
          <remove type=
"System.ServiceModel.Channels.MessageEncodingBindingElementImporter, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
          <extension type=
"Microsoft.ServiceModel.Samples.GZipMessageEncodingBindingElementImporter, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </policyImporters>
      </metadata>
    </client>
  </system.serviceModel>
</configuration>

Maintenant qu'un élément de liaison correspondant existe pour le codeur de compression, il peut être raccordé par programme au service ou au client en construisant un nouvel objet de liaison personnalisé et en lui ajoutant l'élément de liaison personnalisé, tel qu'indiqué dans l'exemple de code suivant.

ICollection<BindingElement> bindingElements = new List<BindingElement>();
HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement();
GZipMessageEncodingBindingElement compBindingElement = new GZipMessageEncodingBindingElement ();
bindingElements.Add(compBindingElement);
bindingElements.Add(httpBindingElement);
CustomBinding binding = new CustomBinding(bindingElements);
binding.Name = "SampleBinding";
binding.Namespace = "http://tempuri.org/bindings";

Même si cela peut s'avérer suffisant pour la majorité de scénarios utilisateur, la prise en charge d'une configuration de fichier est critique si un service doit être hébergé sur le Web. Pour prendre en charge le scénario hébergé sur le Web, vous devez développer un gestionnaire de configuration personnalisé afin de permettre la configuration d'un élément de liaison personnalisé dans un fichier.

Vous pouvez créer un gestionnaire de configuration pour l'élément de liaison sur le système de configuration fourni par le .NET Framework 2.0. Ce gestionnaire de configuration doit dériver de la classe BindingElementExtensionElement. La propriété BindingElementType permet d'indiquer au système de configuration le type d'élément de liaison à créer pour cette section. Tous les aspects de BindingElement qui peuvent être définis doivent être exposés en tant que propriétés dans la classe dérivée BindingElementExtensionElement. ConfigurationPropertyAttribute permet de mapper les attributs d'élément de configuration aux propriétés et d'affecter des valeurs par défaut si les attributs ne sont pas définis. Après avoir chargé et appliqué les valeurs de la configuration aux propriétés, la méthode CreateBindingElement est appelée et convertit les propriétés en instance concrète d'un élément de liaison. La méthode ApplyConfiguration permet de convertir les propriétés sur la classe dérivée BindingElementExtensionElement en valeurs à définir sur l'élément de liaison récemment créé.

L'exemple de code suivant présente l'implémentation de GZipMessageEncodingElement.

public class GZipMessageEncodingElement : BindingElementExtensionElement
{
    public GZipMessageEncodingElement()
    {
    }

//Called by the WCF to discover the type of binding element this 
//config section enables
    public override Type BindingElementType
    {
        get { return typeof(GZipMessageEncodingBindingElement); }
    }

    //The only property we need to configure for our binding element is 
    //the type of inner encoder to use. Here, we support text and
    //binary.
    [ConfigurationProperty("innerMessageEncoding", 
                         DefaultValue = "textMessageEncoding")]
    public string InnerMessageEncoding
    {
        get { return (string)base["innerMessageEncoding"]; }
        set { base["innerMessageEncoding"] = value; }
    }

    //Called by the WCF to apply the configuration settings (the 
    //property above) to the binding element
    public override void ApplyConfiguration(BindingElement bindingElement)
    {
        GZipMessageEncodingBindingElement binding = 
                (GZipMessageEncodingBindingElement)bindingElement;
        PropertyInformationCollection propertyInfo = 
                    this.ElementInformation.Properties;
        if (propertyInfo["innerMessageEncoding"].ValueOrigin != 
                                     PropertyValueOrigin.Default)
        {
            switch (this.InnerMessageEncoding)
            {
                case "textMessageEncoding":
                    binding.InnerMessageEncodingBindingElement = 
                      new TextMessageEncodingBindingElement();
                    break;
                case "binaryMessageEncoding":
                    binding.InnerMessageEncodingBindingElement = 
                         new BinaryMessageEncodingBindingElement();
                    break;
            }
        }
    }

    //Called by the WCF to create the binding element
    protected override BindingElement CreateBindingElement()
    {
        GZipMessageEncodingBindingElement bindingElement = 
                new GZipMessageEncodingBindingElement();
        this.ApplyConfiguration(bindingElement);
        return bindingElement;
    }
} 

Ce gestionnaire de configuration mappe à la représentation suivante dans le fichier App.config ou Web.config du service ou du client :

<gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />

Pour utiliser ce gestionnaire de configuration, il doit être enregistré dans l'élément <system.ServiceModel>, tel qu'indiqué dans l'exemple de configuration suivant.

<extensions>
    <bindingElementExtensions>
       <add 
           name="gzipMessageEncoding" 
           type=
           "Microsoft.ServiceModel.Samples.GZipMessageEncodingElement,
           GZipEncoder, Version=1.0.0.0, Culture=neutral, 
           PublicKeyToken=null" />
      </bindingElementExtensions>
</extensions>

Lorsque vous exécutez le serveur, les demandes et réponses d'opération s'affichent dans la fenêtre de console. Appuyez sur ENTER dans la fenêtre pour arrêter le serveur.

Press Enter key to Exit.

        Server Echo(string input) called:
        Client message: Simple hello


        Server BigEcho(string[] input) called:
        64 client messages

Lorsque vous exécutez le client, les demandes et réponses d'opération s'affichent dans la fenêtre de console. Appuyez sur ENTER dans la fenêtre du client pour l'arrêter.

Calling Echo(string):
Server responds: Simple hello Simple hello

Calling BigEcho(string[]):
Server responds: Hello 0

Press <ENTER> to terminate client.

Pour configurer, générer et exécuter l'exemple

  1. Assurez-vous d'avoir effectué la procédure indiquée dans la section Procédure d'installation unique pour les exemples Windows Communication Foundation.

  2. Pour générer la solution, suivez les instructions indiquées dans Génération des exemples Windows Communication Foundation.

  3. Pour exécuter l'exemple dans une configuration à un ou plusieurs ordinateurs, suivez les instructions indiquées dans Exécution des exemples Windows Communication Foundation.

Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.