Partilhar via


Codificador de mensagem personalizado: Codificador de compressão

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

Detalhes da amostra

Este exemplo consiste em um programa de console cliente (.exe), um programa de console de serviço auto-hospedado (.exe) e uma biblioteca de codificadores de mensagens 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 ISampleServer interface, 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 para o 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 compressão de uma mensagem no fio. Você pode adicionar instrumentação ao codificador de mensagens de compactação para calcular o tamanho da mensagem, o tempo de processamento ou ambos.

Nota

No .NET Framework 4, a descompactação automática foi 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. Este 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 criar 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 no GZipEncoder.dll demonstra o seguinte:

  • Criação de uma fábrica de codificadores e codificadores personalizados.

  • Desenvolvimento de um elemento de ligação para um codificador personalizado.

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

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

Como indicado anteriormente, há várias camadas que são implementadas em um codificador personalizado. Para ilustrar melhor a relação entre cada uma dessas camadas, uma ordem simplificada de eventos para a 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 de serviço registra o manipulador de configuração personalizado.

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

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

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

  3. É recebida uma mensagem.

  4. A fábrica do codificador de mensagens retorna um codificador de mensagens 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 factory é retornado pelo elemento binding quando o ServiceHost objeto ou ChannelFactory<TChannel> é criado. Os codificadores de mensagens podem operar em buffer ou modo de streaming. Este exemplo demonstra o modo de buffer e o modo de streaming.

Para cada modo há um acompanhamento ReadMessage e WriteMessage método na classe abstrata MessageEncoder . A maioria do trabalho de codificação ocorre nesses métodos. O exemplo quebra os codificadores de texto e mensagens binárias existentes. Isso permite que a amostra delegue a leitura e a gravação da representação de fio de mensagens ao codificador interno e permite que o codificador de compressão compacte ou descompacte os resultados. Como não há pipeline para codificação de mensagens, este é o único modelo para usar vários codificadores no WCF. Uma vez que a mensagem tenha sido descompactada, a mensagem resultante é passada para cima da pilha para a pilha de canais para manipular. Durante a compressão, a mensagem compactada resultante é gravada diretamente no fluxo fornecido.

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

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

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 e ChannelFactory<TChannel> e substituir os OnInitialize métodos para adicionar essa fábrica de ServiceHost codificadores manualmente. Você também pode expor a fábrica do codificador por meio de um elemento de vinculação personalizado.

Para criar um novo elemento de vinculação personalizado, derive uma classe da BindingElement classe. Existem, no entanto, vários tipos de elementos vinculativos. Para garantir que o elemento de vinculação personalizado seja reconhecido como um elemento de vinculaçã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 codificador de mensagens (CreateMessageEncoderFactory), que é implementado para retornar uma instância da fábrica de codificador de mensagens correspondente. Além disso, o MessageEncodingBindingElement tem uma propriedade para indicar a versão de endereçamento. Como este exemplo encapsula os codificadores existentes, a implementação de exemplo também encapsula os elementos de ligação do codificador existentes e usa um elemento de ligaçã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 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));
    }
}

Observe que GZipMessageEncodingBindingElement a classe implementa a interface, para que esse elemento de vinculação possa ser exportado IPolicyExportExtension como uma política em 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 GZipMessageEncodingBindingElementImporter classe implementa a IPolicyImportExtension interface, esta classe importa a política para GZipMessageEncodingBindingElement. Svcutil.exe ferramenta pode ser usada para importar políticas para o arquivo de configuração, para manipular GZipMessageEncodingBindingElement, o seguinte deve ser adicionado ao 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 vinculação correspondente para o codificador de compactação, ele pode ser programaticamente conectado ao serviço ou cliente construindo um novo objeto de vinculação personalizado e adicionando o elemento de vinculaçã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 para que um serviço seja 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 vinculação personalizado seja configurável em um arquivo.

Você pode criar um manipulador de configuração para o elemento de ligação sobre o sistema de configuração. O manipulador de configuração para o elemento binding deve derivar da BindingElementExtensionElement classe. O BindingElementExtensionElement.BindingElementType informa o sistema de configuração do tipo de elemento de ligação a ser criado para esta seção. Todos os aspetos do BindingElement que pode ser definido devem ser expostos como propriedades na BindingElementExtensionElement classe derivada. O 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 faltando. Depois que os valores da configuração são carregados e aplicados às propriedades, o BindingElementExtensionElement.CreateBindingElement método é chamado, o que converte as propriedades em uma instância concreta de um elemento de ligação. O BindingElementExtensionElement.ApplyConfiguration método é usado para converter as propriedades na classe derivada BindingElementExtensionElement nos valores a serem definidos no elemento de vinculaçã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;
    }
}

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

Quando você executa o servidor, as solicitações de operação e respostas 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

Quando você executa o cliente, as solicitações de operação e respostas 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. Certifique-se de ter executado o procedimento de instalação única para os exemplos do Windows Communication Foundation.

  3. Para criar a solução, siga as instruções em Criando os exemplos do Windows Communication Foundation.

  4. Para executar o exemplo em uma configuração de máquina única ou cruzada, siga as instruções em Executando os exemplos do Windows Communication Foundation.