Udostępnij za pośrednictwem


Instrukcje: migrowanie Managed-Code DCOM do programu WCF

Windows Communication Foundation (WCF) jest zalecanym i bezpiecznym wyborem zamiast Distributed Component Object Model (DCOM) dla wywołań kodu zarządzanego między serwerami i klientami w środowisku rozproszonym. W tym artykule pokazano, jak przeprowadzić migrację kodu z modelu DCOM do programu WCF w następujących scenariuszach.

  • Usługa zdalna zwraca obiekt według wartości do klienta

  • Klient wysyła obiekt według wartości do usługi zdalnej

  • Usługa zdalna zwraca obiekt przez odwołanie do klienta

Ze względów bezpieczeństwa wysyłanie obiektu przez referencję od klienta do usługi nie jest dozwolone w programie WCF. Scenariusz, który wymaga konwersacji między klientem a serwerem, można osiągnąć w programie WCF przy użyciu usługi dwukierunkowej. Aby uzyskać więcej informacji na temat usług dupleksowych, zobacz Usługi dupleksowe.

Aby uzyskać więcej informacji na temat tworzenia usług i klientów programu WCF dla tych usług, zobacz Podstawowe programowanie WCF, projektowanie i implementowanie usług oraz Tworzenie klientów.

Przykładowy kod DCOM

W przypadku tych scenariuszy interfejsy DCOM ilustrowane przy użyciu programu WCF mają następującą strukturę:

[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
{
}

Usługa zwraca obiekt według wartości

W tym scenariuszu wykonujesz wywołanie do usługi, a jej metoda zwraca obiekt, który jest przekazywany przez wartość z serwera do klienta. Ten scenariusz reprezentuje następujące wywołanie COM:

public interface IRemoteService
{
    Customer GetObjectByValue();
}

W tym scenariuszu klient otrzymuje deserializowaną kopię obiektu z usługi zdalnej. Klient może wchodzić w interakcje z tą kopią lokalną bez ponownego wywoływania usługi. Innymi słowy, klient ma gwarancję, że usługa nie będzie w żaden sposób zaangażowana, gdy wywoływane są metody na lokalnej kopii. Program WCF zawsze zwraca obiekty z usługi według wartości, więc w poniższych krokach opisano tworzenie regularnej usługi WCF.

Krok 1. Definiowanie interfejsu usługi WCF

Zdefiniuj interfejs publiczny dla usługi WCF i oznacz go za pomocą atrybutu [ServiceContractAttribute]. Oznacz metody, które chcesz uwidocznić klientom za pomocą atrybutu [OperationContractAttribute]. W poniższym przykładzie pokazano użycie tych atrybutów w celu zidentyfikowania interfejsu po stronie serwera i metod interfejsu, które klient może wywołać. Metoda użyta w tym scenariuszu została zaznaczona pogrubioną czcionką.

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

}

Krok 2. Definiowanie kontraktu danych

Następnie należy utworzyć kontrakt danych dla usługi, który będzie opisywać sposób wymiany danych między usługą a jej klientami. Klasy opisane w kontrakcie danych powinny być oznaczone atrybutem [DataContractAttribute]. Poszczególne właściwości lub pola, które mają być widoczne zarówno dla klienta, jak i serwera, powinny być oznaczone atrybutem [DataMemberAttribute]. Jeśli chcesz, aby typy pochodzące z klasy w umowie danych były dozwolone, musisz zidentyfikować je za pomocą atrybutu [KnownTypeAttribute]. WCF serializuje lub deserializuje tylko typy w interfejsie usługi oraz typy zidentyfikowane jako znane typy. Jeśli spróbujesz użyć typu, który nie jest znanym typem, wystąpi wyjątek.

Aby uzyskać więcej informacji na temat kontraktów danych, zobacz Kontrakty danych.

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

Krok 3. Implementowanie usługi WCF

Następnie należy zaimplementować klasę usługi WCF, która implementuje interfejs zdefiniowany w poprzednim kroku.

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

Krok 4. Konfigurowanie usługi i klienta

Aby uruchomić usługę WCF, należy zadeklarować punkt końcowy, który uwidacznia ten interfejs usługi pod określonym adresem URL przy użyciu określonego powiązania WCF. Powiązanie określa szczegóły transportu, kodowania i protokołu dla klientów i serwerów do komunikacji. Zazwyczaj do pliku konfiguracji projektu usługi są dodawane powiązania (web.config). Poniżej przedstawiono wpis powiązania dla przykładowej usługi:

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

Następnie należy skonfigurować klienta tak, aby był zgodny z informacjami powiązania określonymi przez usługę. W tym celu dodaj następujący kod do pliku konfiguracji aplikacji klienta (app.config).

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

Krok 5. Uruchamianie usługi

Na koniec możesz samodzielnie hostować ją w aplikacji konsolowej, dodając następujące wiersze do aplikacji usługi i uruchamiając aplikację. Aby uzyskać więcej informacji na temat innych sposobów hostowania aplikacji usługi WCF, Hosting Services.

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

Krok 6. Wywoływanie usługi z klienta

Aby wywołać usługę z poziomu klienta, należy utworzyć fabrykę kanałów dla usługi i zażądać kanału, który umożliwi bezpośrednie wywołanie metody GetCustomer z klienta. Kanał implementuje interfejs usługi i obsługuje podstawową logikę żądania/odpowiedzi. Wartość zwracana z wywołania tej metody jest deserializowaną kopią odpowiedzi usługi.

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

Klient wysyła obiekt by-value do serwera

W tym scenariuszu klient wysyła obiekt do serwera według wartości. Oznacza to, że serwer otrzyma zdeserializowaną kopię obiektu. Serwer może wywoływać metody na tej kopii i mieć gwarancję, że nie ma wywołania zwrotnego do kodu klienta. Jak wspomniano wcześniej, normalne wymiany danych w programie WCF odbywają się przez wartość. Gwarantuje to, że wywoływanie metod na jednym z tych obiektów odbywa się wyłącznie lokalnie — nie spowoduje to wykonania kodu po stronie klienta.

Ten scenariusz reprezentuje następujące wywołanie metody COM:

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

W tym scenariuszu użyto tego samego interfejsu usługi i kontraktu danych, jak pokazano w pierwszym przykładzie. Ponadto klient i usługa zostaną skonfigurowane w taki sam sposób. W tym przykładzie tworzony jest kanał do wysyłania obiektu i uruchamiania go w taki sam sposób. Jednak w tym przykładzie utworzysz klienta, który wywołuje usługę, przekazując obiekt według wartości. Metoda usługi wywoływana przez klienta w kontrakcie usługi jest wyświetlana pogrubioną czcionką:

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

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

Dodawanie kodu do klienta, który wysyła obiekt by-value

Poniższy kod pokazuje, jak klient tworzy nowy obiekt klienta według wartości, tworzy kanał do komunikowania się z ICustomerManager usługą i wysyła do niego obiekt klienta.

Obiekt klienta zostanie poddany serializacji i wysłany do serwisu, gdzie zostanie zdeserializowany przez serwis do nowej kopii tego obiektu. Wszystkie metody wywoływane przez usługę tego obiektu będą wykonywane tylko lokalnie na serwerze. Należy pamiętać, że ten kod ilustruje wysyłanie typu pochodnego (PremiumCustomer). Kontrakt usługi oczekuje Customer obiektu, ale kontrakt danych usługi używa atrybutu [KnownTypeAttribute] w celu wskazania, że PremiumCustomer jest to również dozwolone. Program WCF nie podejmie prób serializacji lub deserializacji dowolnego innego typu za pośrednictwem tego interfejsu usługi.

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

Usługa zwraca obiekt przez referencję

W tym scenariuszu aplikacja kliencka wykonuje wywołanie usługi zdalnej, a metoda zwraca obiekt, który jest przekazywany przez odwołanie z usługi do klienta.

Jak wspomniano wcześniej, usługi WCF zawsze zwracają obiekt według wartości. Można jednak osiągnąć podobny wynik przy użyciu EndpointAddress10 klasy . EndpointAddress10 jest obiektem przenoszonym przez wartość z możliwością serializacji, który może być używany przez klienta do uzyskania sesyjnego obiektu przez odwołanie na serwerze.

Zachowanie obiektu by-reference w programie WCF pokazanym w tym scenariuszu różni się od modelu DCOM. W modelu DCOM serwer może zwrócić obiekt by-reference do klienta bezpośrednio, a klient może wywołać metody tego obiektu, które są wykonywane na serwerze. Jednak w programie WCF zwracany obiekt jest zawsze wartością. Klient musi przyjąć ten obiekt by-value reprezentowany przez EndpointAddress10 i użyć go do utworzenia własnego obiektu sesji by-reference. Metoda klienta wywołuje obiekt sesyjny, który wykonuje się na serwerze. Innymi słowy, ten obiekt 'by-reference' w WCF jest normalną usługą WCF skonfigurowaną do działania sesyjnego.

W programie WCF sesja jest sposobem korelowania wielu komunikatów wysyłanych między dwoma punktami końcowymi. Oznacza to, że gdy klient uzyska połączenie z tą usługą, zostanie ustanowiona sesja między klientem a serwerem. Klient będzie używać pojedynczego unikatowego wystąpienia obiektu po stronie serwera dla wszystkich jego interakcji w ramach tej pojedynczej sesji. Sesjowe kontrakty WCF są podobne do wzorców żądań/odpowiedzi sieci zorientowanych na połączenie.

Ten scenariusz jest reprezentowany przez następującą metodę DCOM.

public interface IRemoteService
{
    IRemoteObject GetObjectByReference();
}

Krok 1. Definiowanie interfejsu usługi sesji WCF i implementacji

Najpierw zdefiniuj interfejs usługi WCF zawierający obiekt sesji.

W tym kodzie obiekt sesji jest oznaczony atrybutem ServiceContract , który identyfikuje go jako zwykły interfejs usługi WCF. Ponadto właściwość SessionMode jest ustawiona tak, aby wskazywała, że będzie to usługa sesyjna.

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

    [OperationContract]
    void SetCurrentValue(string value);
}

Poniższy kod przedstawia implementację usługi.

Usługa jest oznaczona atrybutem [ServiceBehavior] i jej właściwość InstanceContextMode jest ustawiona na InstanceContextMode.PerSession, aby wskazać, że dla każdej sesji należy utworzyć unikalne wystąpienie tego typu.

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

        public string GetCurrentValue()
        {
            return _value;
        }

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

    }

Krok 2. Definiowanie usługi fabryki WCF dla obiektu sesji

Usługa, która tworzy obiekt sesji, musi być zdefiniowana i zaimplementowana. Poniższy kod pokazuje, jak to zrobić. Ten kod tworzy inną usługę EndpointAddress10 WCF, która zwraca obiekt. Jest to serializowalna forma punktu końcowego, który można użyć do utworzenia obiektu z pełną sesją.

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

Poniżej przedstawiono implementację tej usługi. Ta implementacja obsługuje fabrykę kanałów singleton do tworzenia obiektów z sesją. Po wywołaniu GetInstanceAddress następuje utworzenie kanału i obiektu EndpointAddress10, który wskazuje na zdalny adres powiązany z tym kanałem. EndpointAddress10 to typ danych, który może być przekazany do klienta jako wartość.

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

Krok 3. Konfigurowanie i uruchamianie usług WCF

Aby hostować te usługi, należy wprowadzić następujące dodatki do pliku konfiguracji serwera (web.config).

  1. Dodaj sekcję <client> opisującą punkt końcowy dla obiektu sesji. W tym scenariuszu serwer działa również jako klient i należy go skonfigurować, aby to włączyć.

  2. W sekcji <services> zadeklaruj punkty końcowe usługi dla fabryki i dla obiektu sesyjnego. Dzięki temu klient może komunikować się z punktami końcowymi usługi, uzyskiwać EndpointAddress10 i tworzyć kanał sesyjny.

Poniżej przedstawiono przykładowy plik konfiguracji z następującymi ustawieniami:

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

Dodaj następujące wiersze do aplikacji konsolowej, aby samodzielnie hostować usługę i uruchomić aplikację.

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

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

Krok 4. Konfigurowanie klienta i wywoływanie usługi

Skonfiguruj klienta tak, aby komunikował się z usługami WCF, wykonując następujące wpisy w pliku konfiguracji aplikacji projektu (app.config).

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

Aby wywołać usługę, dodaj kod do klienta, aby wykonać następujące czynności:

  1. Utwórz kanał w usłudze ISessionBoundFactory .

  2. Użyj kanału, aby wywołać usługę ISessionBoundFactory i uzyskać obiekt EndpointAddress10.

  3. Użyj elementu EndpointAddress10, aby utworzyć kanał w celu uzyskania obiektu z sesją.

  4. Wywołaj metody SetCurrentValue i GetCurrentValue, aby zademonstrować, że to samo wystąpienie obiektu jest używane w wielu wywołaniach.

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

Zobacz także