Compartilhar via


Codificador de mensagem personalizado: Codificador de compactação

O exemplo de compactação demonstra como implementar um codificador personalizado usando a plataforma Windows Communication Foundation (WCF).

Detalhes de exemplo

Este exemplo consiste em um programa de console de cliente (.exe), um programa de console de serviço auto-hospedado (.exe) e uma biblioteca de codificador de mensagem de compactação (.dll). O serviço implementa um contrato que define um padrão de comunicação solicitação-resposta. O contrato é definido pela interface ISampleServer, que expõe operações básicas de eco de cadeia de caracteres (Echo e BigEcho). O cliente faz solicitações síncronas para uma determinada operação e o serviço responde repetindo a mensagem de volta ao cliente. A atividade do cliente e do serviço é visível nas janelas do console. A intenção deste exemplo é mostrar como escrever um codificador personalizado e demonstrar o impacto da compactação de uma mensagem na transmissão. Você pode adicionar instrumentação ao codificador de mensagem de compactação para calcular o tamanho da mensagem, o tempo de processamento ou ambos.

Observação

No .NET Framework 4, a descompactação automática será habilitada em um cliente WCF se o servidor estiver enviando uma resposta compactada (criada com um algoritmo como GZip ou Deflate). Se o serviço estiver hospedado na Web no Internet Information Server (IIS), o IIS poderá ser configurado para que o serviço envie uma resposta compactada. Esse exemplo pode ser usado se o requisito for fazer compactação e descompactação no cliente e no serviço ou se o serviço for auto-hospedado.

O exemplo demonstra como construir e integrar um codificador de mensagem personalizado em um aplicativo WCF. A biblioteca GZipEncoder.dll é implantada com o cliente e o serviço. Este exemplo também demonstra o impacto da compactação de mensagens. O código em GZipEncoder.dll demonstra o seguinte:

  • Criando um codificador personalizado e uma fábrica de codificadores.

  • Desenvolvendo um elemento de associação para um codificador personalizado.

  • Usando a configuração de associação personalizada para integrar elementos de associação personalizados.

  • Desenvolvendo um manipulador de configuração personalizado para permitir a configuração de arquivo de um elemento de associação personalizado.

Conforme indicado anteriormente, existem várias camadas que são implementadas em um codificador personalizado. Para melhor ilustrar a relação entre cada uma dessas camadas, uma ordem simplificada de eventos para inicialização do serviço está na lista a seguir:

  1. O servidor é iniciado.

  2. As informações de configuração são lidas.

    1. A configuração do serviço registra o manipulador de configuração personalizado.

    2. O host de serviço é criado e aberto.

    3. O elemento de configuração personalizado cria e retorna o elemento de associação personalizado.

    4. O elemento de associação personalizado cria e retorna uma fábrica de codificadores de mensagens.

  3. Uma mensagem é recebida.

  4. A fábrica do codificador de mensagem retorna um codificador de mensagem para ler a mensagem e escrever a resposta.

  5. A camada do codificador é implementada como uma fábrica de classes. Somente a fábrica da classe do codificador deve ser exposta publicamente para o codificador personalizado. O objeto de fábrica é retornado pelo elemento de associação quando o objeto ServiceHost ou ChannelFactory<TChannel> é criado. Os codificadores de mensagem podem operar em modo de buffer ou streaming. Este exemplo demonstra o modo em buffer e o modo de streaming.

Para cada modo há um método ReadMessage e WriteMessage de acompanhamento na classe abstrata MessageEncoder. A maioria do trabalho de codificação ocorre nesses métodos. A amostra encapsula os codificadores de mensagem binária e de texto existentes. Isso permite que a amostra delegue a leitura e a gravação da representação do fio de mensagens para o codificador interno e permite que o codificador de compactação comprima ou descompacte os resultados. Como não há pipeline para codificação de mensagens, esse é o único modelo para usar vários codificadores no WCF. Depois que a mensagem for descompactada, a mensagem resultante será passada para cima da pilha para a pilha do canal para tratá-la. Durante a compactação, a mensagem compactada resultante é gravada diretamente no fluxo fornecido.

Este exemplo usa métodos auxiliares (CompressBuffer e DecompressBuffer) para realizar a conversão de buffers em fluxos para usar a classe GZipStream.

As classes ReadMessage e WriteMessage em buffer fazem uso da classe BufferManager. O encoder é acessível somente através da fábrica do encoder. A classe abstrata MessageEncoderFactory fornece uma propriedade chamada Encoder para acessar o codificador atual e um método chamado CreateSessionEncoder para criar um codificador que suporte sessões. Esse codificador pode ser usado no cenário em que o canal suporta sessões, é ordenado e é confiável. Este cenário permite a otimização em cada sessão dos dados gravados na rede. Se isso não for desejado, o método base não deve ser sobrecarregado. A propriedade Encoder fornece um mecanismo para acessar o codificador sem sessão e a implementação padrão do método CreateSessionEncoder retorna o valor da propriedade. Como o exemplo envolve um codificador existente para fornecer compactação, a implementação MessageEncoderFactory aceita um MessageEncoderFactory que representa a fábrica de codificador interna.

Agora que o codificador e a fábrica do codificador estão definidos, eles podem ser usados com um cliente e serviço WCF. No entanto, esses codificadores devem ser adicionados à pilha de canais. Você pode derivar classes das classes ServiceHost e ChannelFactory<TChannel> e substituir os métodos OnInitialize para adicionar essa fábrica de codificadores manualmente. Você também pode expor a fábrica do codificador por meio de um elemento de associação personalizado.

Para criar um novo elemento de associação personalizado, derive uma classe da classe BindingElement. Há, no entanto, vários tipos de elementos de associação. Para garantir que o elemento deassociação personalizado seja reconhecido como um elemento de associação de codificação de mensagem, você também deve implementar o MessageEncodingBindingElement. O MessageEncodingBindingElement expõe um método para criar uma nova fábrica de codificadores de mensagens (CreateMessageEncoderFactory), que é implementada para retornar uma instância da fábrica de codificadores de mensagens correspondente. Além disso, o MessageEncodingBindingElement possui uma propriedade para indicar a versão de endereçamento. Como esse exemplo envolve os codificadores existentes, a implementação de exemplo também envolve os elementos de associação do codificador existentes e usa um elemento de associação do codificador interno como um parâmetro para o construtor e o expõe por meio de uma propriedade. O código de exemplo a seguir mostra a implementação da 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));
    }
}

Observe que a classe GZipMessageEncodingBindingElement implementa a interface IPolicyExportExtension, para que esse elemento de associação possa ser exportado como uma política nos metadados, conforme mostrado no exemplo a seguir.

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

A classe GZipMessageEncodingBindingElementImporter implementa a interface IPolicyImportExtension, esta classe importa a política para GZipMessageEncodingBindingElement. A ferramenta Svcutil.exe pode ser usada para importar políticas para o arquivo de configuração, para manipular GZipMessageEncodingBindingElement, o seguinte deve ser adicionado 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>

Agora que há um elemento de associação correspondente para o codificador de compactação, ele pode ser conectado programaticamente ao serviço ou cliente construindo um novo objeto de associação personalizado e adicionando o elemento de associação personalizado a ele, conforme mostrado no código de exemplo a seguir.

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

Embora isso possa ser suficiente para a maioria dos cenários de usuário, o suporte a uma configuração de arquivo é fundamental se um serviço for hospedado na Web. Para dar suporte ao cenário hospedado na Web, você deve desenvolver um manipulador de configuração personalizado para permitir que um elemento de associação personalizado seja configurável em um arquivo.

Você pode criar um manipulador de configuração para o elemento de associação na parte superior do sistema de configuração. O manipulador de configuração para o elemento de associação deve derivar da classe BindingElementExtensionElement. BindingElementExtensionElement.BindingElementType informa ao sistema de configuração o tipo de elemento de associação a ser criado para esta seção. Todos os aspectos de BindingElement que podem ser definidos devem ser expostos como propriedades na classe derivada BindingElementExtensionElement. ConfigurationPropertyAttribute auxilia no mapeamento dos atributos do elemento de configuração para as propriedades e na definição de valores padrão se os atributos estiverem ausentes. Depois que os valores da configuração são carregados e aplicados às propriedades, o método BindingElementExtensionElement.CreateBindingElement é chamado, que converte as propriedades em uma instância concreta de um elemento de associação. O método BindingElementExtensionElement.ApplyConfiguration é usado para converter as propriedades na classe derivada BindingElementExtensionElement nos valores a serem definidos no elemento de associação recém-criado.

O código de exemplo a seguir mostra a implementação do 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;
    }
}

Esse manipulador de configuração mapeia para a seguinte representação no App.config ou Web.config para o serviço ou cliente.

<gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />

Para usar esse gerenciador de configuração, ele deve ser registrado no elemento <system.serviceModel>, conforme mostrado na configuração de exemplo a seguir.

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

Ao executar o servidor, as solicitações e respostas da operação são exibidas na janela do console. Pressione ENTER na janela para desligar o servidor.

Press Enter key to Exit.

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

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

Ao executar o cliente, as solicitações e respostas da operação são exibidas na janela do console. Pressione ENTER na janela do cliente para desligar o 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, e executar o exemplo

  1. Instale o ASP.NET 4.0 usando o seguinte comando:

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Verifique se você executou o Procedimento de instalação única para os exemplos do Windows Communication Foundation.

  3. Para compilar a solução, siga as instruções contidas em Como compilar as amostras do Windows Communication Foundation.

  4. Para executar a amostra em uma configuração de computador único ou entre computadores, siga as instruções contidas em Como executar as amostras do Windows Communication Foundation.