Niestandardowy koder komunikatów: Koder kompresji
Przykład kompresji pokazuje, jak zaimplementować koder niestandardowy przy użyciu platformy Windows Communication Foundation (WCF).
Przykładowe szczegóły
Ten przykład składa się z programu konsolowego klienta (.exe), programu konsoli samoobsługi (.exe) i biblioteki kodera komunikatów kompresji (.dll). Usługa implementuje kontrakt, który definiuje wzorzec komunikacji typu żądanie-odpowiedź. Kontrakt jest definiowany przez ISampleServer
interfejs, który uwidacznia podstawowe operacje echa ciągu (Echo
i BigEcho
). Klient wysyła synchroniczne żądania do danej operacji, a usługa odpowiada, powtarzając komunikat z powrotem do klienta. Działanie klienta i usługi jest widoczne w oknach konsoli. Celem tego przykładu jest pokazanie, jak napisać koder niestandardowy i zademonstrować wpływ kompresji komunikatu na przewody. Instrumentację można dodać do kodera komunikatów kompresji, aby obliczyć rozmiar komunikatu, czas przetwarzania lub oba te elementy.
Uwaga
W programie .NET Framework 4 automatyczne dekompresja została włączona na kliencie WCF, jeśli serwer wysyła skompresowaną odpowiedź (utworzoną przy użyciu algorytmu takiego jak GZip lub Deflate). Jeśli usługa jest hostowana w sieci Web na serwerze informacji internetowych (IIS), usługi IIS można skonfigurować pod kątem wysyłania skompresowanej odpowiedzi przez usługę. Ten przykład można użyć, jeśli wymagane jest wykonanie kompresji i dekompresji zarówno na kliencie, jak i w usłudze, lub jeśli usługa jest hostowana samodzielnie.
W przykładzie pokazano, jak skompilować i zintegrować niestandardowy koder komunikatów z aplikacją WCF. Biblioteka GZipEncoder.dll jest wdrażana zarówno z klientem, jak i usługą. W tym przykładzie pokazano również wpływ kompresowania komunikatów. Kod w GZipEncoder.dll pokazuje następujące elementy:
Tworzenie niestandardowego kodera i fabryki kodera.
Tworzenie elementu powiązania dla kodera niestandardowego.
Używanie niestandardowej konfiguracji powiązania do integrowania niestandardowych elementów powiązania.
Tworzenie niestandardowej procedury obsługi konfiguracji w celu umożliwienia konfiguracji pliku niestandardowego elementu powiązania.
Jak wskazano wcześniej, istnieje kilka warstw, które są implementowane w koderze niestandardowym. Aby lepiej zilustrować relację między poszczególnymi warstwami, uproszczona kolejność zdarzeń uruchamiania usługi znajduje się na poniższej liście:
Serwer zostanie uruchomiony.
Informacje o konfiguracji są odczytywane.
Konfiguracja usługi rejestruje niestandardową procedurę obsługi konfiguracji.
Host usługi jest tworzony i otwierany.
Niestandardowy element konfiguracji tworzy i zwraca niestandardowy element powiązania.
Niestandardowy element powiązania tworzy i zwraca fabrykę kodera komunikatów.
Zostanie odebrany komunikat.
Fabryka kodera komunikatów zwraca koder komunikatów do odczytu w komunikacie i zapisywania odpowiedzi.
Warstwa kodera jest implementowana jako fabryka klas. Tylko fabryka klas kodera musi być publicznie uwidoczniona dla kodera niestandardowego. Obiekt fabryki jest zwracany przez element powiązania podczas ServiceHost tworzenia obiektu lub ChannelFactory<TChannel> . Kodery komunikatów mogą działać w trybie buforowania lub przesyłania strumieniowego. W tym przykładzie pokazano zarówno tryb buforowany, jak i tryb przesyłania strumieniowego.
Dla każdego trybu występuje towarzyszący ReadMessage
element i WriteMessage
metoda w klasie abstrakcyjnej MessageEncoder
. Większość pracy kodowania odbywa się w tych metodach. Przykład opakowuje istniejące kodery komunikatów tekstowych i binarnych. Dzięki temu przykład może delegować odczyt i zapis komunikatów do kodera wewnętrznego i umożliwia koderowi kompresji kompresowanie lub dekompresowanie wyników. Ponieważ nie ma potoku kodowania komunikatów, jest to jedyny model używania wielu koderów w programie WCF. Gdy komunikat zostanie zdekompresowany, wynikowy komunikat zostanie przekazany do obsługi stosu dla stosu kanału. Podczas kompresji wynikowy skompresowany komunikat jest zapisywany bezpośrednio w udostępnionym strumieniu.
W tym przykładzie użyto metod pomocnika (CompressBuffer
i DecompressBuffer
) do przeprowadzenia konwersji z buforów na strumienie w celu użycia GZipStream
klasy .
Buforowane ReadMessage
klasy i WriteMessage
używają BufferManager
klasy . Koder jest dostępny tylko za pośrednictwem fabryki kodera. Klasa abstrakcyjna MessageEncoderFactory
udostępnia właściwość o nazwie Encoder
na potrzeby uzyskiwania dostępu do bieżącego kodera i metody o nazwie CreateSessionEncoder
do tworzenia kodera obsługującego sesje. Taki koder może być używany w scenariuszu, w którym kanał obsługuje sesje, jest uporządkowany i niezawodny. Ten scenariusz umożliwia optymalizację w każdej sesji danych zapisanych w sieci. Jeśli nie jest to pożądane, metoda podstawowa nie powinna być przeciążona. Właściwość Encoder
udostępnia mechanizm uzyskiwania dostępu do kodera bez sesji, a domyślna implementacja CreateSessionEncoder
metody zwraca wartość właściwości. Ponieważ przykład opakowuje istniejący koder w celu zapewnienia kompresji, MessageEncoderFactory
implementacja akceptuje fabrykę MessageEncoderFactory
kodera wewnętrznego.
Po zdefiniowaniu fabryki kodera i kodera można ich używać z klientem i usługą WCF. Jednak te kodery należy dodać do stosu kanału. Klasy i można utworzyć z ServiceHost klas i ChannelFactory<TChannel> i i zastąpić OnInitialize
metody, aby ręcznie dodać tę fabrykę kodera. Fabrykę kodera można również uwidocznić za pomocą niestandardowego elementu powiązania.
Aby utworzyć nowy niestandardowy element powiązania, utwórz klasę z BindingElement klasy . Istnieje jednak kilka typów elementów powiązania. Aby upewnić się, że niestandardowy element powiązania jest rozpoznawany jako element powiązania kodowania komunikatów, należy również zaimplementować element MessageEncodingBindingElement. Metoda MessageEncodingBindingElement uwidacznia metodę tworzenia nowej fabryki kodera komunikatów (CreateMessageEncoderFactory
), która jest implementowana w celu zwrócenia wystąpienia zgodnej fabryki kodera komunikatów. Ponadto właściwość MessageEncodingBindingElement ma właściwość wskazującą wersję adresowania. Ponieważ ten przykład opakowuje istniejące kodery, przykładowa implementacja również opakowuje istniejące elementy powiązania kodera i przyjmuje element powiązania kodera wewnętrznego jako parametr konstruktora i uwidacznia go za pośrednictwem właściwości. Poniższy przykładowy kod przedstawia implementację GZipMessageEncodingBindingElement
klasy.
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));
}
}
Należy pamiętać, że GZipMessageEncodingBindingElement
klasa implementuje IPolicyExportExtension
interfejs, dzięki czemu ten element powiązania można wyeksportować jako zasady w metadanych, jak pokazano w poniższym przykładzie.
<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>
Klasa GZipMessageEncodingBindingElementImporter
implementuje interfejs, a ta klasa importuje IPolicyImportExtension
zasady dla klasy GZipMessageEncodingBindingElement
. Svcutil.exe narzędzie może służyć do importowania zasad do pliku konfiguracji w celu obsługi GZipMessageEncodingBindingElement
polecenia , należy dodać następujące polecenie do pliku 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>
Teraz, gdy istnieje pasujący element powiązania dla kodera kompresji, można go programowo podłączyć do usługi lub klienta, tworząc nowy obiekt powiązania niestandardowego i dodając do niego niestandardowy element powiązania, jak pokazano w poniższym przykładowym kodzie.
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";
Chociaż może to być wystarczające w przypadku większości scenariuszy użytkownika, obsługa konfiguracji plików ma kluczowe znaczenie, jeśli usługa ma być hostowana w sieci Web. Aby obsługiwać scenariusz hostowany w sieci Web, należy opracować niestandardową procedurę obsługi konfiguracji, aby umożliwić skonfigurowanie niestandardowego elementu powiązania w pliku.
Można utworzyć procedurę obsługi konfiguracji dla elementu powiązania w oparciu o system konfiguracji. Procedura obsługi konfiguracji elementu powiązania musi pochodzić z BindingElementExtensionElement klasy . Element BindingElementExtensionElement.BindingElementType informuje system konfiguracji o typie elementu powiązania, który ma zostać utworzony dla tej sekcji. Wszystkie aspekty BindingElement
, które można ustawić, powinny być uwidocznione jako właściwości w klasie pochodnej BindingElementExtensionElement . Pomoc ConfigurationPropertyAttribute w mapowaniu atrybutów elementu konfiguracji na właściwości i ustawianie wartości domyślnych, jeśli brakuje atrybutów. Po załadowaniu i zastosowaniu wartości z konfiguracji do właściwości BindingElementExtensionElement.CreateBindingElement wywoływana jest metoda, która konwertuje właściwości na konkretne wystąpienie elementu powiązania. Metoda BindingElementExtensionElement.ApplyConfiguration służy do konwertowania właściwości klasy pochodnej BindingElementExtensionElement na wartości, które mają zostać ustawione na nowo utworzony element powiązania.
Poniższy przykładowy kod przedstawia implementację pliku 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;
}
}
Ta procedura obsługi konfiguracji mapuje następującą reprezentację w pliku App.config lub Web.config dla usługi lub klienta.
<gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />
Aby użyć tej procedury obsługi konfiguracji, należy ją zarejestrować w elemecie <system.serviceModel> , jak pokazano w poniższej przykładowej konfiguracji.
<extensions>
<bindingElementExtensions>
<add
name="gzipMessageEncoding"
type=
"Microsoft.ServiceModel.Samples.GZipMessageEncodingElement,
GZipEncoder, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null" />
</bindingElementExtensions>
</extensions>
Po uruchomieniu serwera żądania operacji i odpowiedzi są wyświetlane w oknie konsoli. Naciśnij klawisz ENTER w oknie, aby zamknąć serwer.
Press Enter key to Exit.
Server Echo(string input) called:
Client message: Simple hello
Server BigEcho(string[] input) called:
64 client messages
Po uruchomieniu klienta żądania operacji i odpowiedzi są wyświetlane w oknie konsoli. Naciśnij klawisz ENTER w oknie klienta, aby zamknąć klienta.
Calling Echo(string):
Server responds: Simple hello Simple hello
Calling BigEcho(string[]):
Server responds: Hello 0
Press <ENTER> to terminate client.
Aby skonfigurować, skompilować i uruchomić przykład
Zainstaluj ASP.NET 4.0 przy użyciu następującego polecenia:
%windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
Upewnij się, że wykonano procedurę instalacji jednorazowej dla przykładów programu Windows Communication Foundation.
Aby skompilować rozwiązanie, postępuj zgodnie z instrukcjami w temacie Building the Windows Communication Foundation Samples (Tworzenie przykładów programu Windows Communication Foundation).
Aby uruchomić przykład w konfiguracji pojedynczej lub między maszynami, postępuj zgodnie z instrukcjami w temacie Uruchamianie przykładów programu Windows Communication Foundation.