Compartir vía


Codificador de mensajes personalizado: Codificador de compresión

En el ejemplo de compresión se muestra cómo implementar un codificador personalizado mediante la plataforma de Windows Communication Foundation (WCF).

Detalles del ejemplo

Este ejemplo consta de un programa de consola de cliente (.exe), un programa de consola de servicio autohospedado (.exe) y una biblioteca de codificadores de mensajes de compresión (.dll). El servicio implementa un contrato que define un patrón de comunicación de solicitud-respuesta. El contrato se define mediante la ISampleServer interfaz , que expone las operaciones básicas de eco de cadenas (Echo y BigEcho). El cliente realiza solicitudes sincrónicas a una operación determinada y el servicio responde repitiendo el mensaje al cliente. La actividad de cliente y servicio está visible en las ventanas de consola. La intención de este ejemplo es mostrar cómo escribir un codificador personalizado y demostrar el impacto de la compresión de un mensaje en la conexión. Puede agregar instrumentación al codificador de mensajes de compresión para calcular el tamaño del mensaje, el tiempo de procesamiento o ambos.

Nota:

En .NET Framework 4, la descompresión automática se ha habilitado en un cliente WCF si el servidor envía una respuesta comprimida (creada con un algoritmo como GZip o Deflate). Si el servicio está hospedado en Web en Internet Information Server (IIS), IIS se puede configurar para que el servicio envíe una respuesta comprimida. Este ejemplo se puede usar si el requisito es realizar compresión y descompresión tanto en el cliente como en el servicio o si el servicio está autohospedado.

En el ejemplo se muestra cómo compilar e integrar un codificador de mensajes personalizado en una aplicación WCF. La biblioteca GZipEncoder.dll se implementa con el cliente y el servicio. En este ejemplo también se muestra el impacto de comprimir mensajes. El código de GZipEncoder.dll muestra lo siguiente:

  • Creación de un codificador personalizado y un generador de codificadores.

  • Desarrollar un elemento de enlace para un codificador personalizado.

  • Uso de la configuración de enlace personalizada para integrar elementos de enlace personalizados.

  • Desarrollar un controlador de configuración personalizado para permitir la configuración de archivos de un elemento de enlace personalizado.

Como se indicó anteriormente, hay varias capas que se implementan en un codificador personalizado. Para ilustrar mejor la relación entre cada una de estas capas, un orden simplificado de eventos para el inicio del servicio se encuentra en la lista siguiente:

  1. Se inicia el servidor.

  2. Se lee la información de configuración.

    1. La configuración del servicio registra el controlador de configuración personalizado.

    2. El host del servicio se crea y se abre.

    3. El elemento de configuración personalizado crea y devuelve el elemento de enlace personalizado.

    4. El elemento de enlace personalizado crea y devuelve el generador de codificadores de mensajes.

  3. Se recibe un mensaje.

  4. El generador de codificadores de mensajes devuelve un codificador de mensaje para leer en el mensaje y escribir la respuesta.

  5. La capa del codificador se implementa como generador de clases. Sólo se debe exponer públicamente el generador de clases del codificador para el codificador personalizado. El elemento de enlace devuelve el objeto factory cuando se crea el objeto ServiceHost o el objeto ChannelFactory<TChannel>. Los codificadores de mensajes pueden funcionar en modo almacenado en búfer o streaming. En este ejemplo se muestra tanto el modo almacenado en búfer como el modo de streaming.

Para cada modo, hay un método ReadMessage y un método WriteMessage correspondiente en la clase abstracta MessageEncoder. La mayoría del trabajo de codificación tiene lugar en estos métodos. El ejemplo envuelve los codificadores de mensajes binarios y de texto existentes. Esto permite al ejemplo delegar la lectura y escritura de la representación de la conexión de mensajes en el codificador interno y permite al codificador de compresión comprimir o descomprimir los resultados. Dado que no hay ninguna canalización para la codificación de mensajes, este es el único modelo para usar varios codificadores en WCF. Una vez descomprimido el mensaje, el mensaje resultante se pasa a la pila para que lo gestione la pila de canales. Durante la compresión, el mensaje comprimido resultante se escribe directamente en la secuencia proporcionada.

Este ejemplo utiliza los métodos del asistente (CompressBuffer y DecompressBuffer) para realizar la conversión de los búferes a secuencias para utilizar la clase GZipStream.

Las clases ReadMessage y WriteMessage almacenadas en búfer hacen uso de la clase BufferManager. Solo se puede acceder al codificador a través del generador de codificadores. La clase abstracta MessageEncoderFactory proporciona una propiedad denominada Encoder para acceder al codificador actual y un método denominado CreateSessionEncoder para crear un codificador que admita sesiones. Este codificador se puede usar en un escenario donde el canal admite sesiones, está ordenado y es confiable. Este escenario permite la optimización en cada sesión de los datos transmitidos por la red. Si no se desea, no se debe sobrecargar el método base. La Encoder propiedad proporciona un mecanismo para acceder al codificador sin sesión y la implementación predeterminada del CreateSessionEncoder método devuelve el valor de la propiedad . Dado que el ejemplo encapsula un codificador existente para proporcionar compresión, la MessageEncoderFactory implementación acepta un MessageEncoderFactory que representa el generador del codificador interno.

Ahora que se definen el codificador y el generador de codificadores, se pueden usar con un cliente y servicio WCF. Sin embargo, estos codificadores deben ser agregados a la pila de canales. Puede derivar las clases ServiceHost y ChannelFactory<TChannel> e invalidar los métodos OnInitialize para agregar manualmente este generador de codificadores. También puede exponer el generador de codificadores a través de un elemento de enlace personalizado.

Para crear un nuevo elemento de enlace personalizado, derive una clase de la BindingElement clase . Sin embargo, hay varios tipos de elementos de enlace. Para asegurarse de que el elemento de enlace personalizado se reconoce como un elemento de enlace de codificación de mensajes, también debe implementar el MessageEncodingBindingElement. MessageEncodingBindingElement expone un método para crear una nueva factoría de codificador de mensajes (CreateMessageEncoderFactory), que se implementa para devolver una instancia de la factoría de codificador de mensajes coincidente. Además, MessageEncodingBindingElement tiene una propiedad para indicar la versión de direccionamiento. Dado que este ejemplo envuelve los codificadores existentes, la implementación del ejemplo también envuelve los componentes de enlace del codificador existentes y toma un elemento de enlace interno del codificador como parámetro para el constructor y lo expone a través de una propiedad. El código de ejemplo siguiente muestra la implementación de la GZipMessageEncodingBindingElement clase .

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));
    }
}

Tenga en cuenta que GZipMessageEncodingBindingElement la clase implementa la IPolicyExportExtension interfaz para que este elemento de enlace se pueda exportar como una directiva en los metadatos, como se muestra en el ejemplo siguiente.

<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 clase implementa la IPolicyImportExtension interfaz , esta clase importa la directiva para GZipMessageEncodingBindingElement. La herramienta Svcutil.exe se puede utilizar para importar las directivas al archivo de configuración. Para administrar GZipMessageEncodingBindingElement, se debería agregar lo siguiente a 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>

Ahora que hay un elemento de enlace coincidente para el codificador de compresión, se puede enlazar mediante programación al servicio o al cliente mediante la construcción de un nuevo objeto de enlace personalizado y agregarle el elemento de enlace personalizado, como se muestra en el código de ejemplo siguiente.

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";

Aunque esto puede ser suficiente para la mayoría de los escenarios de usuario, admitir una configuración de archivos es fundamental si un servicio se va a hospedar en web. Para admitir el escenario hospedado en web, debe desarrollar un controlador de configuración personalizado para permitir que un elemento de enlace personalizado se pueda configurar en un archivo.

Puede crear un controlador de configuración para el elemento de enlace sobre el sistema de configuración. El controlador de configuración del elemento de enlace debe derivar de la BindingElementExtensionElement clase . BindingElementExtensionElement.BindingElementType informa al sistema de configuración del tipo de elemento de enlace que se va a crear para esta sección. Todos los aspectos del BindingElement que se pueden establecer deben exponerse como propiedades en la clase derivada BindingElementExtensionElement. El ConfigurationPropertyAttribute ayuda a asignar los atributos del elemento de configuración a las propiedades y a establecer valores predeterminados si faltan atributos. Una vez cargados y aplicados los valores de la configuración a las propiedades, se llama al BindingElementExtensionElement.CreateBindingElement método , que convierte las propiedades en una instancia concreta de un elemento de enlace. El BindingElementExtensionElement.ApplyConfiguration método se usa para convertir las propiedades de la BindingElementExtensionElement clase derivada en los valores que se van a establecer en el elemento de enlace recién creado.

En el código de ejemplo siguiente se muestra la implementación 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;
    }
}

Este controlador de configuración asigna a la representación siguiente en App.config o Web.config para el servicio o cliente.

<gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />

Para usar este controlador de configuración, debe registrarse en el <elemento system.serviceModel> , como se muestra en la siguiente configuración de ejemplo.

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

Al ejecutar el servidor, las solicitudes de operación y las respuestas se muestran en la ventana de la consola. Presione ENTRAR en la ventana para apagar el servidor.

Press Enter key to Exit.

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

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

Al ejecutar el cliente, las solicitudes de operación y las respuestas se muestran en la ventana de la consola. Presione ENTRAR en la ventana del cliente para apagar el cliente.

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

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

Press <ENTER> to terminate client.

Para configurar, compilar y ejecutar el ejemplo

  1. Instale ASP.NET 4.0 con el comando siguiente:

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Asegúrese de que ha realizado el procedimiento de instalación única para los ejemplos de Windows Communication Foundation.

  3. Para compilar la solución, siga las instrucciones que se indican en Compilación de los ejemplos de Windows Communication Foundation.

  4. Para ejecutar el ejemplo en una configuración de una máquina única o entre máquinas, siga las instrucciones de Ejecución de los ejemplos de Windows Communication Foundation.