Freigeben über


So migrieren Sie Managed-Code DCOM zu WCF

Windows Communication Foundation (WCF) ist die empfohlene und sichere Wahl über das DCOM (Distributed Component Object Model) für Verwaltete Codeaufrufe zwischen Servern und Clients in einer verteilten Umgebung. In diesem Artikel wird gezeigt, wie Sie Code aus DCOM zu WCF für die folgenden Szenarien migrieren.

  • Der Remotedienst gibt ein Objekt nach Wert an den Client zurück.

  • Der Client sendet ein Objekt nach Wert an den Remotedienst.

  • Der Remotedienst gibt ein Objekt nach Verweis auf den Client zurück.

Aus Sicherheitsgründen ist das Senden eines Objekts nach Verweis vom Client an den Dienst in WCF nicht zulässig. Ein Szenario, das eine Unterhaltung zwischen Client und Server erfordert, kann in WCF mithilfe eines Duplexdiensts erreicht werden. Weitere Informationen zu Duplexdiensten finden Sie unter Duplexdienste.

Weitere Informationen zum Erstellen von WCF-Diensten und -Clients für diese Dienste finden Sie unter "Grundlegende WCF-Programmierung", " Entwerfen und Implementieren von Diensten" und " Erstellen von Clients".

DCOM-Beispielcode

Für diese Szenarien weisen die DCOM-Schnittstellen, die mithilfe von WCF veranschaulicht werden, die folgende Struktur auf:

[ComVisible(true)]
[Guid("AA9C4CDB-55EA-4413-90D2-843F1A49E6E6")]
public interface IRemoteService
{
   Customer GetObjectByValue();
   IRemoteObject GetObjectByReference();
   void SendObjectByValue(Customer customer);
}

[ComVisible(true)]
[Guid("A12C98DE-B6A1-463D-8C24-81E4BBC4351B")]
public interface IRemoteObject
{
}

public class Customer
{
}

Der Dienst gibt ein Objekt nach Wert zurück.

In diesem Szenario rufen Sie einen Dienst auf und die Methode gibt ein Objekt zurück, das per Wert vom Server an den Client übergeben wird. Dieses Szenario stellt den folgenden COM-Aufruf dar:

public interface IRemoteService
{
    Customer GetObjectByValue();
}

In diesem Szenario empfängt der Client eine deserialisierte Kopie eines Objekts vom Remotedienst. Der Client kann mit dieser lokalen Kopie interagieren, ohne den Dienst zurückzurufen. Dem Client wird garantiert, dass sich der Dienst in keiner Weise einmischt, wenn Methoden auf der lokalen Kopie aufgerufen werden. WCF gibt immer Objekte aus dem Dienst nach Wert zurück, sodass die folgenden Schritte das Erstellen eines regulären WCF-Diensts beschreiben.

Schritt 1: Definieren der WCF-Dienstschnittstelle

Definieren Sie eine öffentliche Schnittstelle für den WCF-Dienst, und markieren Sie sie mit dem Attribut [ServiceContractAttribute]. Markieren Sie die Methoden, die Sie für Klienten mit dem [OperationContractAttribute]-Attribut verfügbar machen möchten. Das folgende Beispiel zeigt die Verwendung dieser Attribute, um die serverseitige Schnittstelle und Schnittstellenmethoden zu identifizieren, die ein Client aufrufen kann. Die für dieses Szenario verwendete Methode wird fett dargestellt.

using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
. . .
[ServiceContract]
public interface ICustomerManager
{
    [OperationContract]
    void StoreCustomer(Customer customer);

    [OperationContract]     Customer GetCustomer(string firstName, string lastName);

}

Schritt 2: Definieren des Datenvertrags

Als Nächstes sollten Sie einen Datenvertrag für den Dienst erstellen, der beschreibt, wie die Daten zwischen dem Dienst und seinen Kunden ausgetauscht werden. Im Datenvertrag beschriebene Klassen sollten mit dem Attribut [DataContractAttribute] gekennzeichnet werden. Die einzelnen Eigenschaften oder Felder, die für Client und Server sichtbar sein sollen, sollten mit dem Attribut [DataMemberAttribute] gekennzeichnet werden. Wenn Typen, die von einer Klasse im Datenvertrag abgeleitet werden sollen, zulässig sein sollen, müssen Sie sie mit dem Attribut [KnownTypeAttribute] identifizieren. WCF serialisiert oder deserialisiert Typen nur in der Dienstschnittstelle und Typen, die als bekannte Typen identifiziert werden. Wenn Sie versuchen, einen Typ zu verwenden, der kein bekannter Typ ist, tritt eine Ausnahme auf.

Weitere Informationen zu Datenverträgen finden Sie unter "Datenverträge".

[DataContract]
[KnownType(typeof(PremiumCustomer))]
public class Customer
{
    [DataMember]
    public string Firstname;
    [DataMember]
    public string Lastname;
    [DataMember]
    public Address DefaultDeliveryAddress;
    [DataMember]
    public Address DefaultBillingAddress;
}
 [DataContract]
public class PremiumCustomer : Customer
{
    [DataMember]
    public int AccountID;
}

 [DataContract]
public class Address
{
    [DataMember]
    public string Street;
    [DataMember]
    public string Zipcode;
    [DataMember]
    public string City;
    [DataMember]
    public string State;
    [DataMember]
    public string Country;
}

Schritt 3: Implementieren des WCF-Diensts

Als Nächstes sollten Sie die WCF-Dienstklasse implementieren, die die schnittstelle implementiert, die Sie im vorherigen Schritt definiert haben.

public class CustomerService: ICustomerManager
{
    public void StoreCustomer(Customer customer)
    {
        // write to a database
    }
    public Customer GetCustomer(string firstName, string lastName)
    {
        // read from a database
    }
}

Schritt 4: Konfigurieren des Diensts und des Clients

Zum Ausführen eines WCF-Diensts müssen Sie einen Endpunkt deklarieren, der diese Dienstschnittstelle mit einer bestimmten URL mithilfe einer bestimmten WCF-Bindung verfügbar macht. Eine Bindung gibt die Transport-, Codierungs- und Protokolldetails für die zu kommunizierenden Clients und server an. In der Regel fügen Sie Bindungen zur Konfigurationsdatei des Dienstprojekts (web.config) hinzu. Im Folgenden wird ein Bindungseintrag für den beispielhaften Dienst angezeigt:

<configuration>
  <system.serviceModel>
    <services>
      <service name="Server.CustomerService">
        <endpoint address="http://localhost:8083/CustomerManager"
                  binding="basicHttpBinding"
                  contract="Shared.ICustomerManager" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Als Nächstes müssen Sie den Client so konfigurieren, dass er mit den bindungsinformationen übereinstimmt, die vom Dienst angegeben werden. Fügen Sie dazu der Anwendungskonfiguration des Clients (app.config) Folgendes hinzu.

<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="customermanager"
                address="http://localhost:8083/CustomerManager"
                binding="basicHttpBinding"
                contract="Shared.ICustomerManager"/>
    </client>
  </system.serviceModel>
</configuration>

Schritt 5: Ausführen des Diensts

Schließlich können Sie sie in einer Konsolenanwendung selbst hosten, indem Sie der Dienst-App die folgenden Zeilen hinzufügen und die App starten. Weitere Informationen zu anderen Möglichkeiten zum Hosten einer WCF-Dienstanwendung, Hostingdienste.

ServiceHost customerServiceHost = new ServiceHost(typeof(CustomerService));
customerServiceHost.Open();

Schritt 6: Aufrufen des Diensts vom Client

Um den Dienst vom Client aufzurufen, müssen Sie eine Kanalfactory für den Dienst erstellen und einen Kanal anfordern, mit dem Sie die GetCustomer Methode direkt vom Client aufrufen können. Der Kanal implementiert die Schnittstelle des Diensts und behandelt die zugrunde liegende Anforderungs-/Antwortlogik für Sie. Der Rückgabewert dieses Methodenaufrufs ist die deserialisierte Kopie der Dienstantwort.

ChannelFactory<ICustomerManager> factory =
     new ChannelFactory<ICustomerManager>("customermanager");
ICustomerManager service = factory.CreateChannel();
Customer customer = service.GetCustomer("Mary", "Smith");

Der Client sendet ein By-Value-Objekt an den Server.

In diesem Szenario sendet der Client ein Objekt nach Wert an den Server. Dies bedeutet, dass der Server eine deserialisierte Kopie des Objekts empfängt. Der Server kann Methoden für diese Kopie aufrufen und gewährleistet sein, dass kein Rückruf in Clientcode vorhanden ist. Wie bereits erwähnt, sind normale WCF-Datenaustausche von Daten nach Wert. Dadurch wird sichergestellt, dass das Aufrufen von Methoden für eines dieser Objekte nur lokal ausgeführt wird – code wird nicht auf dem Client aufgerufen.

Dieses Szenario stellt den folgenden COM-Methodenaufruf dar:

public interface IRemoteService
{
    void SendObjectByValue(Customer customer);
}

In diesem Szenario wird die gleiche Dienstschnittstelle und derselbe Datenvertrag verwendet, wie im ersten Beispiel gezeigt. Darüber hinaus wird der Client und der Dienst auf die gleiche Weise konfiguriert. In diesem Beispiel wird ein Kanal erstellt, um das Objekt zu senden und auf die gleiche Weise auszuführen. In diesem Beispiel erstellen Sie jedoch einen Client, der den Dienst aufruft, und übergibt ein Objekt nach Wert. Die Dienstmethode, die der Client im Dienstvertrag aufruft, wird fett dargestellt:

[ServiceContract]
public interface ICustomerManager
{
    [OperationContract]     void StoreCustomer(Customer customer);

    [OperationContract]
    Customer GetCustomer(string firstName, string lastName);
}

Hinzufügen von Code zum Client, der ein By-Value-Objekt sendet

Der folgende Code zeigt, wie der Client ein neues Nachwert-Kundenobjekt erstellt, einen Kanal für die Kommunikation mit dem ICustomerManager Dienst erstellt und das Kundenobjekt an ihn sendet.

Das Kundenobjekt wird serialisiert und an den Dienst gesendet, in dem es vom Dienst in eine neue Kopie dieses Objekts deserialisiert wird. Alle Methoden, die der Dienst für dieses Objekt aufruft, werden nur lokal auf dem Server ausgeführt. Beachten Sie, dass dieser Code das Senden eines abgeleiteten Typs (PremiumCustomer) veranschaulicht. Der Dienstvertrag erwartet ein Customer Objekt, aber der Dienstdatenvertrag verwendet das Attribut [KnownTypeAttribute] zur Angabe, dass PremiumCustomer auch zulässig ist. WCF versucht nicht, einen anderen Typ über diese Dienstschnittstelle zu serialisieren oder zu deserialisieren.

PremiumCustomer customer = new PremiumCustomer();
customer.Firstname = "John";
customer.Lastname = "Doe";
customer.DefaultBillingAddress = new Address();
customer.DefaultBillingAddress.Street = "One Microsoft Way";
customer.DefaultDeliveryAddress = customer.DefaultBillingAddress;
customer.AccountID = 42;

ChannelFactory<ICustomerManager> factory =
   new ChannelFactory<ICustomerManager>("customermanager");
ICustomerManager customerManager = factory.CreateChannel();
customerManager.StoreCustomer(customer);

Der Dienst gibt ein Objekt nach Verweis zurück.

In diesem Szenario ruft die Client-App den Remotedienst auf, und die Methode gibt ein Objekt zurück, das vom Dienst an den Client übergeben wird.

Wie bereits erwähnt, geben WCF-Dienste immer Objekt nach Wert zurück. Sie können jedoch ein ähnliches Ergebnis erzielen, indem Sie die EndpointAddress10 Klasse verwenden. Das EndpointAddress10 ist ein serialisierbares Wertobjekt, das vom Client verwendet werden kann, um ein sitzungsgebundenes Referenzobjekt auf dem Server abzurufen.

Das Verhalten des By-Reference-Objekts in WCF in diesem Szenario unterscheidet sich von DCOM. In DCOM kann der Server ein By-Reference-Objekt direkt an den Client zurückgeben, und der Client kann die Methoden dieses Objekts aufrufen, die auf dem Server ausgeführt werden. In WCF wird das zurückgegebene Objekt jedoch immer by-value übertragen. Der Client muss dieses By-Value-Objekt übernehmen, dargestellt durch EndpointAddress10 und verwendet es, um ein eigenes sitzungsbasiertes Nachverweisobjekt zu erstellen. Die Clientmethode ruft das sitzungsbehaftete Objekt auf dem Server auf. Mit anderen Worten, dieses By-Reference-Objekt in WCF ist ein normaler WCF-Dienst, der so konfiguriert ist, dass es sitzungsvoll ist.

In WCF ist eine Sitzung eine Methode zum Korrelieren mehrerer Nachrichten, die zwischen zwei Endpunkten gesendet werden. Dies bedeutet, dass eine Sitzung zwischen dem Client und dem Server hergestellt wird, sobald ein Client eine Verbindung mit diesem Dienst abruft. Der Client verwendet eine einzelne eindeutige Instanz des serverseitigen Objekts für alle Interaktionen innerhalb dieser einzelnen Sitzung. Sitzungsbasierte WCF-Verträge ähneln verbindungsorientierten Netzwerkanforderungs-/Antwortmustern.

Dieses Szenario wird durch die folgende DCOM-Methode dargestellt.

public interface IRemoteService
{
    IRemoteObject GetObjectByReference();
}

Schritt 1: Definieren der Sitzungs-WCF-Dienstschnittstelle und -Implementierung

Definieren Sie zunächst eine WCF-Dienstschnittstelle, die das sitzungsbehaftete Objekt enthält.

In diesem Code wird das sitzungsgebundene Objekt mit dem ServiceContract Attribut gekennzeichnet, das es als gewöhnliche WCF-Dienstschnittstelle identifiziert. Darüber hinaus wird die Eigenschaft SessionMode so festgelegt, dass sie ein sitzungsbehafteter Dienst ist.

[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface ISessionBoundObject
{
    [OperationContract]
    string GetCurrentValue();

    [OperationContract]
    void SetCurrentValue(string value);
}

Der folgende Code zeigt die Dienstimplementierung.

Der Dienst ist mit dem Attribut [ServiceBehavior] gekennzeichnet, und seine InstanceContextMode-Eigenschaft wird auf "InstanceContextMode.PerSessions" festgelegt, um anzugeben, dass für jede Sitzung eine eindeutige Instanz dieses Typs erstellt werden soll.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    public class MySessionBoundObject : ISessionBoundObject
    {
        private string _value;

        public string GetCurrentValue()
        {
            return _value;
        }

        public void SetCurrentValue(string val)
        {
            _value = val;
        }

    }

Schritt 2: Definieren des WCF-Factorydiensts für das sitzungsreiche Objekt

Der Dienst, der das sitzungsbasierte Objekt erstellt, muss definiert und implementiert werden. Der folgende Code zeigt, wie dies funktioniert. Dieser Code erstellt einen weiteren WCF-Dienst, der ein EndpointAddress10 Objekt zurückgibt. Dies ist eine serialisierbare Form eines Endpunkts, die zum Erstellen des sitzungsbezogenen Objekts verwendet werden kann.

[ServiceContract]
    public interface ISessionBoundFactory
    {
        [OperationContract]
        EndpointAddress10 GetInstanceAddress();
    }

Es folgt die Implementierung dieses Diensts. Diese Implementierung verwaltet eine Singleton-Kanalfactory, um sitzungsbasierte Objekte zu erstellen. Beim GetInstanceAddress Aufrufen wird ein Kanal erstellt und ein EndpointAddress10 Objekt erstellt, das auf die mit diesem Kanal verknüpfte Remoteadresse verweist. EndpointAddress10 ist ein Datentyp, der nach Wert an den Client zurückgegeben werden kann.

public class SessionBoundFactory : ISessionBoundFactory
    {
        public static ChannelFactory<ISessionBoundObject> _factory =
            new ChannelFactory<ISessionBoundObject>("sessionbound");

        public SessionBoundFactory()
        {
        }

        public EndpointAddress10 GetInstanceAddress()
        {
            IClientChannel channel = (IClientChannel)_factory.CreateChannel();
            return EndpointAddress10.FromEndpointAddress(channel.RemoteAddress);
        }
    }

Schritt 3: Konfigurieren und Starten der WCF-Dienste

Um diese Dienste zu hosten, müssen Sie die folgenden Ergänzungen zur Konfigurationsdatei des Servers (web.config) vornehmen.

  1. Fügen Sie einen <client> Abschnitt hinzu, der den Endpunkt für das zustandsbehaftete Objekt beschreibt. In diesem Szenario fungiert der Server auch als Client und muss so konfiguriert werden.

  2. Deklarieren Sie im <services>-Abschnitt Dienstendpunkte für die Fabrik und das sitzungsorientierte Objekt. Dadurch kann der Client mit den Dienstendpunkten kommunizieren, den EndpointAddress10 Sitzungskanal abrufen und erstellen.

Im Folgenden sehen Sie eine Beispielkonfigurationsdatei mit diesen Einstellungen:

<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="sessionbound"
                address="net.tcp://localhost:8081/SessionBoundObject"
                binding="netTcpBinding"
                contract="Shared.ISessionBoundObject"/>
    </client>

    <services>
      <service name="Server.MySessionBoundObject">
        <endpoint address="net.tcp://localhost:8081/SessionBoundObject"
                  binding="netTcpBinding"
                  contract="Shared.ISessionBoundObject" />
      </service>
      <service name="Server.SessionBoundFactory">
        <endpoint address="net.tcp://localhost:8081/SessionBoundFactory"
                  binding="netTcpBinding"
                  contract="Shared.ISessionBoundFactory" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Fügen Sie einer Konsolenanwendung die folgenden Zeilen hinzu, um den Dienst selbst zu hosten und die App zu starten.

ServiceHost factoryHost = new ServiceHost(typeof(SessionBoundFactory));
factoryHost.Open();

ServiceHost sessionBoundServiceHost = new ServiceHost(
typeof(MySessionBoundObject));
sessionBoundServiceHost.Open();

Schritt 4: Konfigurieren des Clients und Aufrufen des Diensts

Konfigurieren Sie den Client für die Kommunikation mit den WCF-Diensten, indem Sie die folgenden Einträge in der Anwendungskonfigurationsdatei des Projekts (app.config) vornehmen.

<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="sessionbound"
                address="net.tcp://localhost:8081/SessionBoundObject"
                binding="netTcpBinding"
                contract="Shared.ISessionBoundObject"/>
      <endpoint name="factory"
                address="net.tcp://localhost:8081/SessionBoundFactory"
                binding="netTcpBinding"
                contract="Shared.ISessionBoundFactory"/>
    </client>
  </system.serviceModel>
</configuration>

Um den Dienst aufzurufen, fügen Sie dem Client den Code hinzu, um Folgendes auszuführen:

  1. Erstellen Sie einen Kanal für den ISessionBoundFactory Dienst.

  2. Verwenden Sie den Kanal, um den ISessionBoundFactory Dienst aufzurufen, um ein EndpointAddress10 Objekt abzurufen.

  3. Verwenden Sie EndpointAddress10, um einen Kanal zu erstellen, damit ein sitzungsreiches Objekt abgerufen werden kann.

  4. Rufen Sie die SetCurrentValue- und GetCurrentValue-Methoden auf, um zu zeigen, dass dieselbe Objektinstanz bei mehreren Aufrufen verwendet wird.

ChannelFactory<ISessionBoundFactory> factory =
        new ChannelFactory<ISessionBoundFactory>("factory");

ISessionBoundFactory sessionBoundFactory = factory.CreateChannel();

EndpointAddress10 address = sessionBoundFactory.GetInstanceAddress();

ChannelFactory<ISessionBoundObject> sessionBoundObjectFactory =
    new ChannelFactory<ISessionBoundObject>(
        new NetTcpBinding(),
        address.ToEndpointAddress());

ISessionBoundObject sessionBoundObject =
        sessionBoundObjectFactory.CreateChannel();

sessionBoundObject.SetCurrentValue("Hello");
if (sessionBoundObject.GetCurrentValue() == "Hello")
{
    Console.WriteLine("Session-full instance management works as expected");
}

Siehe auch