Partager via


Encodeur de message personnalisé : Encodeur de compression

L’exemple compression montre comment implémenter un encodeur personnalisé à l’aide de la plateforme Windows Communication Foundation (WCF).

Détails de l'exemple

Cet exemple se compose d’un programme de console client (.exe), d’un programme de console de service auto-hébergé (.exe) et d’une bibliothèque d’encodeur de message de compression (.dll). Le service implémente un contrat qui définit un modèle de communication de demande-réponse. Le contrat est défini par l’interface ISampleServer , qui expose les opérations d’écho de chaîne de base (Echo et BigEcho). Le client effectue des requêtes synchrones à une opération donnée et le service répond en répétant le message au client. L’activité client et service est visible dans les fenêtres de console. L’objectif de cet exemple est de montrer comment écrire un encodeur personnalisé et illustrer l’impact de la compression d’un message sur le câble. Vous pouvez ajouter de l’instrumentation à l’encodeur de message de compression pour calculer la taille du message, le temps de traitement ou les deux.

Remarque

Dans .NET Framework 4, la décompression automatique a été activée sur un client WCF si le serveur envoie une réponse compressée (créée avec un algorithme tel que GZip ou Deflate). Si le service est hébergé sur le web dans Internet Information Server (IIS), IIS peut être configuré pour que le service envoie une réponse compressée. Cet exemple peut être utilisé si l’exigence est d’effectuer la compression et la décompression sur le client et le service ou si le service est auto-hébergé.

L’exemple montre comment générer et intégrer un encodeur de message personnalisé dans une application WCF. La bibliothèque GZipEncoder.dll est déployée avec le client et le service. Cet exemple illustre également l’impact de la compression des messages. Le code de GZipEncoder.dll illustre les éléments suivants :

  • Création d’un encodeur personnalisé et d’une fabrique d’encodeur.

  • Développement d’un élément de liaison pour un encodeur personnalisé.

  • Utilisation de la configuration de liaison personnalisée pour l’intégration d’éléments de liaison personnalisés.

  • Développement d’un gestionnaire de configuration personnalisé pour autoriser la configuration de fichier d’un élément de liaison personnalisé.

Comme indiqué précédemment, plusieurs couches sont implémentées dans un encodeur personnalisé. Pour mieux illustrer la relation entre chacune de ces couches, un ordre simplifié d’événements pour le démarrage du service se trouve dans la liste suivante :

  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 d’encodeur de message.

  3. Un message est reçu.

  4. La fabrique retourne un encodeur de message permettant de lire dans le message et d'écrire la réponse.

  5. La couche d’encodeur est implémentée en tant que fabrique de classes. Seule la fabrique de classe d'encodeur doit être exposée publiquement pour l'encodeur personnalisé. L’objet factory est retourné par l’élément de liaison lorsque l’objet ServiceHost ou l’objet ChannelFactory<TChannel> est créé. Les encodeurs de messages peuvent fonctionner en mode mis en mémoire tampon ou en streaming. Cet exemple illustre à la fois le mode mis en mémoire tampon et le mode de diffusion en continu.

Chaque mode est associée à une méthode ReadMessage et WriteMessage sur la classe abstraite MessageEncoder. Une majorité du travail d’encodage a lieu dans ces méthodes. L’exemple enveloppe les encodeurs de messages texte et binaires existants. Cela permet à l’exemple de déléguer la lecture et l’écriture de la représentation filaire des messages à l’encodeur interne et permet à l’encodeur de compression de compresser ou de décompresser les résultats. Étant donné qu’il n’existe aucun pipeline pour l’encodage de message, il s’agit du seul modèle d’utilisation de plusieurs encodeurs dans WCF. Une fois le message décompressé, le message résultant est transmis à la pile de canaux pour être traité. 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) pour effectuer la conversion de mémoires tampons vers des flux pour utiliser la GZipStream classe.

Les classes tampon ReadMessage et WriteMessage utilisent la classe BufferManager. L’encodeur est accessible uniquement via la fabrique d’encodeur. La classe abstraite MessageEncoderFactory fournit une propriété nommée Encoder pour accéder à l’encodeur actuel et une méthode nommée CreateSessionEncoder pour créer un encodeur prenant en charge les sessions. Un tel encodeur peut être utilisé dans le scénario où le canal prend en charge les sessions, est ordonné et fiable. Ce scénario permet l’optimisation dans chaque session des données écrites dans le câble. Si ce n’est pas souhaité, la méthode de base ne doit pas être surchargée. La Encoder propriété fournit un mécanisme permettant d’accéder à l’encodeur sans session et l’implémentation par défaut de la CreateSessionEncoder méthode retourne la valeur de la propriété. Étant donné que l’exemple encapsule un encodeur existant pour fournir la compression, l’implémentation MessageEncoderFactory accepte un MessageEncoderFactory qui représente la fabrique d’encodeur interne.

Maintenant que l’encodeur et la fabrique d’encodeur sont définis, ils peuvent être utilisés avec un client et un service WCF. Toutefois, ces encodeurs doivent être ajoutés à la pile de canaux. Vous pouvez dériver des classes des classes ServiceHost et ChannelFactory<TChannel>, et substituer les méthodes OnInitialize pour ajouter cette fabrique d'encodeur manuellement. Vous pouvez également exposer la fabrique d’encodeur via un élément de liaison personnalisé.

Pour créer un élément de liaison personnalisé, dérivez une classe de la BindingElement classe. Toutefois, il existe plusieurs types d’éléments de liaison. Pour vous assurer que l’élément de liaison personnalisé est reconnu comme un élément de liaison d’encodage de message, vous devez également implémenter le MessageEncodingBindingElement. MessageEncodingBindingElement expose une méthode permettant de créer une nouvelle fabrique d'encodeur de message (CreateMessageEncoderFactory), laquelle est implémentée pour retourner une instance de la fabrique d'encodeur de message correspondante. En outre, la MessageEncodingBindingElement propriété contient une propriété pour indiquer la version d’adressage. Étant donné que cet exemple encapsule les encodeurs existants, l’exemple d’implémentation encapsule également les éléments de liaison d’encodeur existants et prend un élément de liaison d’encodeur interne en tant que paramètre au constructeur et l’expose via une propriété. L’exemple de code suivant montre l’implémentation de la GZipMessageEncodingBindingElement classe.

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 GZipMessageEncodingBindingElement classe 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, comme illustré dans l’exemple suivant.

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

La GZipMessageEncodingBindingElementImporter classe implémente l’interface IPolicyImportExtension , cette classe importe la stratégie pour GZipMessageEncodingBindingElement. Svcutil.exe outil peut être utilisé pour importer des stratégies dans le fichier de configuration, pour 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’il existe un élément de liaison correspondant pour l’encodeur de compression, il peut être connecté par programmation au service ou au client en construisant un nouvel objet de liaison personnalisé et en y ajoutant l’élément de liaison personnalisé, comme illustré 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";

Bien que cela soit suffisant pour la majorité des scénarios utilisateur, la prise en charge d’une configuration de fichier est essentielle 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é pour permettre à un élément de liaison personnalisé d’être configurable dans un fichier.

Vous pouvez créer un gestionnaire de configuration pour l’élément de liaison au-dessus du système de configuration. Le gestionnaire de configuration de l’élément de liaison doit dériver de la BindingElementExtensionElement classe. Le BindingElementExtensionElement.BindingElementType système de configuration indique le type d’élément de liaison à créer pour cette section. Tous les aspects de ce BindingElement qui peut être défini doivent être exposés en tant que propriétés dans la BindingElementExtensionElement classe dérivée. ConfigurationPropertyAttribute aide à associer les attributs de l’élément de configuration aux propriétés et à définir des valeurs par défaut si certains attributs sont absents. Une fois les valeurs de configuration chargées et appliquées aux propriétés, la BindingElementExtensionElement.CreateBindingElement méthode est appelée, qui convertit les propriétés en une instance concrète d’un élément de liaison. La BindingElementExtensionElement.ApplyConfiguration méthode est utilisée pour convertir les propriétés de la BindingElementExtensionElement classe dérivée en valeurs à définir sur l’élément de liaison nouvellement créé.

L’exemple de code suivant montre l’implémentation du 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 correspond à la représentation suivante dans les App.config ou Web.config pour le service ou le client.

<gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />

Pour utiliser ce gestionnaire de configuration, il doit être inscrit dans l’élément <system.serviceModel> , comme 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 Entrée 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 Entrée dans la fenêtre du client pour arrêter le client.

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. Installez ASP.NET 4.0 à l’aide de la commande suivante :

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Assurez-vous d’avoir effectué la Procédure d’installation unique pour les exemples Windows Communication Foundation.

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

  4. Pour exécuter l’exemple dans une configuration à un ou plusieurs ordinateurs, conformez-vous aux instructions figurant dans la rubrique Exécution des exemples Windows Communication Foundation.