Freigeben über


Benutzerdefinierter Nachrichten-Encoder: Komprimierungs-Encoder

Das Komprimierungsbeispiel veranschaulicht die Implementierung eines benutzerdefinierten Encoders mithilfe der Windows Communication Foundation (WCF)-Plattform.

Beispieldetails

Dieses Beispiel besteht aus einem Clientkonsolenprogramm (.exe), einem selbst gehosteten Dienstkonsolenprogramm (.exe) und einer Komprimierungsnachrichten-Encoderbibliothek (.dll). Der Dienst implementiert einen Vertrag, der ein Kommunikationsmuster für die Anforderungsantwort definiert. Der Vertrag wird durch die ISampleServer Schnittstelle definiert, die grundlegende Zeichenfolgen-Echovorgänge (Echo und BigEcho) verfügbar macht. Der Client sendet synchrone Anforderungen an einen bestimmten Vorgang und der Dienst antwortet, indem er die Nachricht an den Client zurückschickt. Client- und Dienstaktivitäten sind in den Konsolenfenstern sichtbar. Die Absicht dieses Beispiels besteht darin, zu zeigen, wie ein benutzerdefinierter Encoder geschrieben und die Auswirkungen der Komprimierung einer Nachricht auf dem Draht veranschaulicht werden. Sie können dem Komprimierungsnachrichten-Encoder Instrumentierung hinzufügen, um die Nachrichtengröße, die Verarbeitungszeit oder beides zu berechnen.

Hinweis

In .NET Framework 4 wurde die automatische Dekomprimierung auf einem WCF-Client aktiviert, wenn der Server eine komprimierte Antwort sendet (erstellt mit einem Algorithmus wie GZip oder Deflate). Wenn der Dienst in Internetinformationsserver (INTERNET Information Server, IIS) gehostet wird, kann IIS für den Dienst konfiguriert werden, um eine komprimierte Antwort zu senden. Dieses Beispiel kann verwendet werden, wenn die Anforderung darin besteht, die Komprimierung und Dekomprimierung sowohl auf dem Client als auch auf dem Dienst zu erledigen oder wenn der Dienst selbst gehostet wird.

Das Beispiel veranschaulicht das Erstellen und Integrieren eines benutzerdefinierten Nachrichten-Encoders in eine WCF-Anwendung. Die Bibliothek GZipEncoder.dll wird sowohl mit dem Client als auch dem Dienst bereitgestellt. In diesem Beispiel wird auch die Auswirkung der Komprimierung von Nachrichten veranschaulicht. Der Code in GZipEncoder.dll veranschaulicht Folgendes:

  • Das Erstellen eines benutzerdefinierten Encoders und einer Encoder-Factory.

  • Entwickeln eines Bindungselements für einen benutzerdefinierten Encoder.

  • Verwenden der benutzerdefinierten Bindungskonfiguration für die Integration von benutzerdefinierten Bindungselementen.

  • Entwickeln eines benutzerdefinierten Konfigurationshandlers zum Zulassen der Dateikonfiguration eines benutzerdefinierten Bindungselements.

Wie bereits erwähnt, gibt es mehrere Ebenen, die in einem benutzerdefinierten Encoder implementiert werden. Um die Beziehung zwischen den einzelnen Ebenen besser zu veranschaulichen, befindet sich in der folgenden Liste eine vereinfachte Reihenfolge von Ereignissen für den Dienststart:

  1. Der Server wird gestartet.

  2. Die Konfigurationsinformationen werden gelesen.

    1. Die Dienstkonfiguration registriert den benutzerdefinierten Konfigurationshandler.

    2. Der Servicehost wird erstellt und geöffnet.

    3. Das benutzerdefinierte Konfigurationselement erstellt und gibt das benutzerdefinierte Bindungselement zurück.

    4. Das benutzerdefinierte Bindungselement erstellt eine Nachrichtenencoder-Factory und gibt sie zurück.

  3. Eine Nachricht wird empfangen.

  4. Die Nachrichtenencoder-Factory gibt einen Nachrichtenencoder zum Lesen der Nachricht und Schreiben der Antwort zurück.

  5. Die Encoderebene wird als Klassenfabrik implementiert. Nur die Encoderklassenfactory muss für den benutzerdefinierten Encoder öffentlich verfügbar gemacht werden. Das Factory-Objekt wird vom Bindungselement zurückgegeben, wenn entweder das ServiceHost-Objekt oder das ChannelFactory<TChannel>-Objekt erstellt wird. Nachrichten-Encoder können in einem gepufferten oder Streamingmodus ausgeführt werden. In diesem Beispiel werden der Puffermodus und der Streamingmodus veranschaulicht.

Zu jedem Modus gibt es eine begleitende ReadMessage, WriteMessage Methode auf der abstrakten MessageEncoder Klasse. Die meisten Codierungsarbeiten finden in diesen Methoden statt. Das Beispiel schließt die vorhandenen Text- und Binärnachrichtenencoder ein. Auf diese Weise kann es das Lesen und Schreiben der Übertragungsdarstellung von Nachrichten an den inneren Encoder delegieren, und die Ergebnisse können vom Komprimierungsencoder komprimiert oder dekomprimiert werden. Da keine Pipeline für die Nachrichtencodierung vorhanden ist, ist dies das einzige Modell für die Verwendung mehrerer Encoder in WCF. Sobald die Nachricht dekomprimiert wurde, wird die resultierende Nachricht an den Kanalstapel übergeben, um verarbeitet zu werden. Während der Komprimierung wird die resultierende komprimierte Nachricht direkt in den bereitgestellten Datenstrom geschrieben.

In diesem Beispiel werden Hilfsmethoden (CompressBuffer und DecompressBuffer) verwendet, um Konvertierungen von Puffern in Datenströme durchzuführen, um die GZipStream Klasse zu verwenden.

Die gepufferten ReadMessage und WriteMessage Klassen verwenden die BufferManager Klasse. Auf den Encoder kann nur über die Encoderfactory zugegriffen werden. Die abstrakte MessageEncoderFactory Klasse bietet eine Eigenschaft namens Encoder für den Zugriff auf den aktuellen Encoder und eine Methode namens CreateSessionEncoder zum Erstellen eines Encoders, der Sitzungen unterstützt. Ein solcher Encoder kann in dem Szenario verwendet werden, in dem der Kanal Sitzungen unterstützt, sortiert und zuverlässig ist. Dieses Szenario ermöglicht die Optimierung jeder Sitzung der Daten, die auf die Leitung geschrieben wurden. Wenn dies nicht gewünscht ist, sollte die Basismethode nicht überladen werden. Die Encoder Eigenschaft stellt einen Mechanismus für den Zugriff auf den Session-less-Encoder bereit, und die Standardimplementierung der CreateSessionEncoder Methode gibt den Wert der Eigenschaft zurück. Da das Beispiel einen vorhandenen Encoder umschließt, um die Komprimierung bereitzustellen, nimmt die MessageEncoderFactory Implementierung eine MessageEncoderFactory an, die die innere Encoder-Fabrik darstellt.

Da nun der Encoder und die Encoder-Fabrik definiert sind, können sie mit einem WCF-Client und -Dienst verwendet werden. Diese Encoder müssen jedoch dem Kanalstapel hinzugefügt werden. Sie können Klassen von den ServiceHost- und ChannelFactory<TChannel>-Klassen ableiten und die OnInitialize-Methoden überschreiben, um diese Encoder-Fabrik manuell hinzuzufügen. Sie können die Encoder-Factory auch über ein benutzerdefiniertes Bindungselement verfügbar machen.

Um ein neues benutzerdefiniertes Bindungselement zu erstellen, leiten Sie eine Klasse von der BindingElement Klasse ab. Es gibt jedoch mehrere Arten von Bindungselementen. Um sicherzustellen, dass das benutzerdefinierte Nachrichtencodierungsbindungs-Element als solches erkannt wird, müssen Sie auch das MessageEncodingBindingElement implementieren. Das MessageEncodingBindingElement macht eine Methode zum Erstellen einer neuen Nachrichtenencoder-Factory verfügbar (CreateMessageEncoderFactory), die so implementiert ist, dass sie eine Instanz der übereinstimmenden Nachrichtenencoder-Factory zurückgibt. Darüber hinaus hat die MessageEncodingBindingElement eine Eigenschaft, die auf die Adressierungsversion hinweist. Da dieses Beispiel die vorhandenen Encoder einschließt, schließt die Implementierung des Beispiels auch die vorhandenen Encoderbindungselemente ein und nimmt ein inneres Encoderbindungselement als Parameter für den Konstruktor entgegen, das es über eine Eigenschaft verfügbar macht. Der folgende Beispielcode zeigt die Implementierung der GZipMessageEncodingBindingElement Klasse.

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

Beachten Sie, dass GZipMessageEncodingBindingElement die Klasse die IPolicyExportExtension Schnittstelle implementiert, sodass dieses Bindungselement wie im folgenden Beispiel als Richtlinie in Metadaten exportiert werden kann.

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

Die GZipMessageEncodingBindingElementImporter Klasse implementiert die IPolicyImportExtension Schnittstelle. Diese Klasse importiert die Richtlinie für GZipMessageEncodingBindingElement. Svcutil.exe Tool kann verwendet werden, um Richtlinien in die Konfigurationsdatei zu importieren. Um GZipMessageEncodingBindingElement zu behandeln, sollte Folgendes zu Svcutil.exe.confighinzugefügt werden.

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

Da nun ein übereinstimmendes Bindungselement für den Komprimierungs-Encoder vorhanden ist, kann es programmgesteuert in den Dienst oder Client eingebunden werden, indem ein neues benutzerdefiniertes Bindungsobjekt erstellt und das benutzerdefinierte Bindungselement hinzugefügt wird, wie im folgenden Beispielcode gezeigt.

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

Obwohl dies für die meisten Benutzerszenarien ausreichend sein kann, ist die Unterstützung einer Dateikonfiguration wichtig, wenn ein Dienst webgehostt werden soll. Um das vom Web gehostete Szenario zu unterstützen, müssen Sie einen benutzerdefinierten Konfigurationshandler entwickeln, damit ein benutzerdefiniertes Bindungselement in einer Datei konfiguriert werden kann.

Sie können einen Konfigurationshandler für das Bindungselement über dem Konfigurationssystem erstellen. Der Konfigurationshandler für das Bindungselement muss von der BindingElementExtensionElement Klasse abgeleitet werden. Das BindingElementExtensionElement.BindingElementType Konfigurationssystem informiert über den Typ des Bindungselements, das für diesen Abschnitt erstellt werden soll. Alle Aspekte des BindingElement die festgelegt werden können, sollten als Eigenschaften in der BindingElementExtensionElement abgeleiteten Klasse bereitgestellt werden. Die ConfigurationPropertyAttribute hilft beim Zuordnen der Attribute von Konfigurationselementen zu den Eigenschaften und legt Standardwerte fest, wenn Attribute fehlen. Nachdem die Werte aus der Konfiguration geladen und auf die Eigenschaften angewendet wurden, wird die BindingElementExtensionElement.CreateBindingElement Methode aufgerufen, die die Eigenschaften in eine konkrete Instanz eines Bindungselements konvertiert. Die BindingElementExtensionElement.ApplyConfiguration Methode wird verwendet, um die Eigenschaften der BindingElementExtensionElement abgeleiteten Klasse in die Werte zu konvertieren, die für das neu erstellte Bindungselement festgelegt werden sollen.

Das folgenden Codebeispiel zeigt die Implementierung des 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;
    }
}

Dieser Konfigurationshandler ordnet die folgende Darstellung in der App.config oder Web.config für den Dienst oder Client zu.

<gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />

Um diesen Konfigurationshandler zu verwenden, muss er im <System.serviceModel-Element> registriert werden, wie in der folgenden Beispielkonfiguration gezeigt.

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

Wenn Sie den Server ausführen, werden die Vorgangsanforderungen und -antworten im Konsolenfenster angezeigt. Drücken Sie die EINGABETASTE im Fenster, um den Server herunterzufahren.

Press Enter key to Exit.

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

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

Wenn Sie den Client ausführen, werden die Vorgangsanforderungen und -antworten im Konsolenfenster angezeigt. Drücken Sie im Clientfenster die EINGABETASTE, um den Client zu schließen.

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

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

Press <ENTER> to terminate client.

So können Sie das Beispiel einrichten, erstellen und ausführen

  1. Installieren Sie ASP.NET 4.0 mit dem folgenden Befehl:

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Stellen Sie sicher, dass Sie das One-Time Setup-Verfahren für die Windows Communication Foundation-Beispieleausgeführt haben.

  3. Befolgen Sie zum Erstellen der Projektmappe die Anweisungen unter Erstellen der Windows Communication Foundation-Beispiele.

  4. Wenn Sie das Beispiel in einer Konfiguration mit einem Computer oder über Computer hinweg ausführen möchten, folgen Sie den Anweisungen unter Durchführen der Windows Communication Foundation-Beispiele.