Benutzerdefinierter Diensthost

Das Beispiel CustomServiceHost zeigt, wie mit einer benutzerdefinierten Ableitung der ServiceHost-Klasse das Laufzeitverhalten eines Diensts geändert werden kann. Dieser Ansatz stellt eine wiederverwendbare Alternative zum Konfigurieren einer großen Anzahl von Diensten auf die übliche Weise war. Außerdem zeigt das Beispiel, wie mithilfe der ServiceHostFactory-Klasse ein benutzerdefinierter ServiceHost in der IIS-(Internet Information Services, Internetinformationsdienste-) oder WAS-(Windows Process Activation Service-)Hostumgebung verwendet wird.

Informationen über das Szenario

Um ein unbeabsichtigtes Veröffentlichen von möglicherweise vertraulichen Dienstmetadaten zu vermeiden, wird mit der Standardkonfiguration für Windows Communication Foundation (WCF)-Dienste die Metadatenveröffentlichung deaktiviert. Dieses Verhalten ist standardmäßig sicher, bedeutet aber auch, dass der zum Aufrufen des Diensts erforderliche Clientcode nicht mithilfe eines Tools zum Importieren von Metadaten (wie „Svcutil.exe“) generiert werden kann. Dies ist nur dann möglich, wenn das Verhalten des Diensts zum Veröffentlichen von Metadaten in der Konfiguration explizit aktiviert ist.

Wenn die Metadatenveröffentlichung für eine große Anzahl von Diensten aktiviert wird, werden jedem einzelnen Dienst die gleichen Konfigurationselemente hinzugefügt. Dies führt zu einer großen Anzahl von Konfigurationsinformationen, die weitgehend identisch sind. Anstatt jeden Dienst einzeln zu konfigurieren, ist es möglich, imperativen Code zu schreiben, der eine einmalige Veröffentlichung der Metadaten ermöglicht. Anschließend kann dieser Code für mehrere unterschiedliche Dienste verwendet werden. Zu diesem Zweck wird eine neue Klasse erstellt, die von ServiceHost abgeleitet wird und die ApplyConfiguration()-Methode überschreibt, um das Metadatenveröffentlichungsverhalten imperativ hinzuzufügen.

Wichtig

Beachten Sie, dass in diesem Beispiel die Erstellung eines ungesicherten Metadaten-Veröffentlichungsendpunkts veranschaulicht wird. Solche Endpunkte können für anonyme, nicht authentifizierte Benutzer verfügbar sein, allerdings muss vor der Bereitstellung dieser Endpunkte sorgfältig darauf geachtet werden, dass die Offenlegung der Metadaten eines Diensts angemessen ist.

Implementieren eines benutzerdefinierten Diensthosts

Die ServiceHost-Klasse macht mehrere hilfreiche virtuelle Methoden verfügbar, die von Erben überschrieben werden können, um das Laufzeitverhalten eines Diensts zu ändern. Beispielsweise liest die ApplyConfiguration()-Methode die Dienstkonfigurationsinformationen aus dem Konfigurationsspeicher und ändert die ServiceDescription des Diensts entsprechend. Die Standardimplementierung liest die Konfiguration aus der Konfigurationsdatei der Anwendung. Die benutzerdefinierte Implementierung kann ApplyConfiguration() überschreiben, um die ServiceDescription mithilfe von imperativem Code zu ändern oder den Standardkonfigurationsspeicher vollständig zu ersetzen, Beispielsweise, um die Endpunktkonfiguration eines Diensts nicht aus der Konfigurationsdatei der Anwendung zu lesen, sondern aus einer Datenbank.

In diesem Beispiel soll ein benutzerdefinierter ServiceHost erstellt werden, der das ServiceMetadataBehavior (das die Veröffentlichung von Metadaten ermöglicht) hinzufügt, selbst wenn dieses Verhalten der Konfigurationsdatei des Diensts nicht explizit hinzugefügt wurde. Zu diesem Zweck erstellen wir eine neue Klasse, die von ServiceHost erbt und ApplyConfiguration() überschreibt.

class SelfDescribingServiceHost : ServiceHost
{
    public SelfDescribingServiceHost(Type serviceType, params Uri[] baseAddresses)
        : base(serviceType, baseAddresses) { }

    //Overriding ApplyConfiguration() allows us to
    //alter the ServiceDescription prior to opening
    //the service host.
    protected override void ApplyConfiguration()
    {
        //First, we call base.ApplyConfiguration()
        //to read any configuration that was provided for
        //the service we're hosting. After this call,
        //this.Description describes the service
        //as it was configured.
        base.ApplyConfiguration();

        //(rest of implementation elided for clarity)
    }
}

Da die Konfigurationen in der Konfigurationsdatei der Anwendung nicht ignoriert werden sollen, wird beim Überschreiben von ApplyConfiguration() zuerst die Basisimplementierung aufgerufen. Nach der Fertigstellung dieser Methode kann das ServiceMetadataBehavior der Beschreibung imperativ mithilfe des folgenden imperativen Codes hinzugefügt werden.

ServiceMetadataBehavior mexBehavior = this.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (mexBehavior == null)
{
    mexBehavior = new ServiceMetadataBehavior();
    this.Description.Behaviors.Add(mexBehavior);
}
else
{
    //Metadata behavior has already been configured,
    //so we do not have any work to do.
    return;
}

Die letzte Aufgabe, die beim Überschreiben von ApplyConfiguration() ausgeführt werden muss, ist das Hinzufügen des Standardmetadatenendpunkts. Entsprechend der Konvention wird für jeden URI in der BaseAddresses-Auflistung des Diensthosts ein Metadatenendpunkt erstellt.

//Add a metadata endpoint at each base address
//using the "/mex" addressing convention
foreach (Uri baseAddress in this.BaseAddresses)
{
    if (baseAddress.Scheme == Uri.UriSchemeHttp)
    {
        mexBehavior.HttpGetEnabled = true;
        this.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
                                MetadataExchangeBindings.CreateMexHttpBinding(),
                                "mex");
    }
    else if (baseAddress.Scheme == Uri.UriSchemeHttps)
    {
        mexBehavior.HttpsGetEnabled = true;
        this.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
                                MetadataExchangeBindings.CreateMexHttpsBinding(),
                                "mex");
    }
    else if (baseAddress.Scheme == Uri.UriSchemeNetPipe)
    {
        this.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
                                MetadataExchangeBindings.CreateMexNamedPipeBinding(),
                                "mex");
    }
    else if (baseAddress.Scheme == Uri.UriSchemeNetTcp)
    {
        this.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
                                MetadataExchangeBindings.CreateMexTcpBinding(),
                                "mex");
    }
}

Verwenden eines benutzerdefinierten Diensthosts bei Selbsthost

Nachdem wir nun die Implementierung des benutzerdefinierten Diensthosts fertig gestellt haben, können wird diese verwenden, um einem beliebigen Dienst Metadatenveröffentlichungsverhalten hinzuzufügen, indem dieser Dienst innerhalb einer Instanz von SelfDescribingServiceHost gehostet wird. Im folgenden Code wird gezeigt, wie diese im Selbsthostszenario verwendet wird.

SelfDescribingServiceHost host =
         new SelfDescribingServiceHost( typeof( Calculator ) );
host.Open();

Der benutzerdefinierte Host liest die Endpunktkonfiguration des Diensts nach wie vor aus der Konfigurationsdatei der Anwendung, so als wäre die ServiceHost-Standardklasse zum Hosten des Diensts verwendet worden. Da jedoch in dem benutzerdefinierten Host die Logik für die Aktivierung der Metadatenveröffentlichung hinzugefügt wurde, muss das Metadatenveröffentlichungsverhalten nicht mehr explizit in der Konfiguration aktiviert werden. Dieser Ansatz bietet vor allem dann Vorteile, wenn Sie eine Anwendung erstellen, die mehrere Dienste enthält, und Sie die Metadatenveröffentlichung auf allen diesen Diensten aktivieren möchten, ohne jedes Mal die gleichen Konfigurationselemente schreiben zu müssen.

Verwenden eines benutzerdefinierten Diensthosts in IIS oder WAS

Die Verwendung eines benutzerdefinierten Diensthosts in einem Selbsthostszenario ist einfach, da ausschließlich Ihr eigener Anwendungscode für die Erstellung und Öffnung der Diensthostinstanz verantwortlich ist. In der IIS- oder WAS-Hosting-Umgebung hingegen instanziiert die WCF-Infrastruktur den Host Ihres Diensts dynamisch als Reaktion auf eingehende Nachrichten. Es können in dieser Hostingumgebung auch benutzerdefinierte Diensthosts verwendet werden, für diese ist jedoch zusätzlicher Code in Form einer ServiceHostFactory erforderlich. Im folgenden Code wird eine Ableitung von ServiceHostFactory dargestellt, die Instanzen des benutzerdefinierten SelfDescribingServiceHost zurückgibt.

public class SelfDescribingServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType,
     Uri[] baseAddresses)
    {
        //All the custom factory does is return a new instance
        //of our custom host class. The bulk of the custom logic should
        //live in the custom host (as opposed to the factory)
        //for maximum
        //reuse value outside of the IIS/WAS hosting environment.
        return new SelfDescribingServiceHost(serviceType,
                                             baseAddresses);
    }
}

Wie Sie sehen können, ist die Implementierung einer benutzerdefinierten ServiceHostFactory ganz einfach. Die gesamte benutzerdefinierte Logik befindet sich in der Implementierung des Diensthosts, und die Factory gibt eine Instanz der abgeleiteten Klasse zurück.

Um eine benutzerdefinierte Factory mit einer Dienstimplementierung zu verwenden, müssen der SVC-Datei des Diensts einige zusätzliche Metadaten hinzugefügt werden.

<% @ServiceHost Service="Microsoft.ServiceModel.Samples.CalculatorService"
               Factory="Microsoft.ServiceModel.Samples.SelfDescribingServiceHostFactory"
               language=c# Debug="true" %>

Hier wurde der @ServiceHost-Anweisung ein zusätzliches Attribut Factory hinzugefügt, und der CLR-Typname der benutzerdefinierten Factory wurde als Attributwert übergeben. Wenn IIS oder WAS eine Nachricht für diesen Dienst empfängt, erstellt die WCF-Hosting-Infrastruktur zunächst eine Instanz der ServiceHostFactory und instanziiert anschließend den Diensthost selbst durch einen Aufruf von ServiceHostFactory.CreateServiceHost().

Ausführen des Beispiels

Obwohl dieses Beispiel eine voll funktionsfähige Client- und Dienstimplementierung bereitstellt, soll es veranschaulichen, wie Sie das Laufzeitverhalten eines Diensts mithilfe eines benutzerdefinierten Hosts ändern können. Führen Sie die folgenden Schritte aus:

Beobachten der Auswirkungen des benutzerdefinierten Hosts

  1. Öffnen Sie die Datei „Web.config“ des Diensts. Sie werden feststellen, dass es keine Konfiguration gibt, die explizit Metadaten für den Dienst aktiviert.

  2. Wenn Sie die SVC-Datei des Diensts öffnen, sehen Sie, dass die @ServiceHost-Anweisung ein Factory-Attribut enthält, das den Namen einer benutzerdefinierten ServiceHostFactory angibt.

Einrichten, Erstellen und Ausführen des Beispiels

  1. Stellen Sie sicher, dass Sie die Beispiele zum einmaligen Setupverfahren für Windows Communication Foundation ausgeführt haben.

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

  3. Nachdem die Projektmappe erstellt wurde, führen Sie Setup.bat aus, um die ServiceModelSamples-Anwendung in IIS 7.0 einzurichten. Das Verzeichnis ServiceModelSamples sollte jetzt als IIS 7.0-Anwendung angezeigt werden.

  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.

  5. Führen Sie Cleanup.bat aus, um die IIS 7.0-Anwendung zu entfernen.

Siehe auch