Partilhar via


Como: Migrar DCOM de código gerenciado para WCF

O Windows Communication Foundation (WCF) é a opção recomendada e segura sobre o DCOM (Distributed Component Object Model) para chamadas de código gerenciado entre servidores e clientes em um ambiente distribuído. Este artigo mostra como migrar o código do DCOM para o WCF para os seguintes cenários.

  • O serviço remoto retorna um objeto por valor para o cliente

  • O cliente envia um objeto por valor para o serviço remoto

  • O serviço remoto retorna um objeto por referência ao cliente

Por motivos de segurança, o envio de um objeto por referência do cliente para o serviço não é permitido no WCF. Um cenário que requer uma conversa entre cliente e servidor pode ser alcançado no WCF usando um serviço duplex. Para obter mais informações sobre serviços duplex, consulte Serviços duplex.

Para obter mais detalhes sobre como criar serviços e clientes WCF para esses serviços, consulte Programação básica do WCF, projetando e implementando serviços e criando clientes.

Código de exemplo DCOM

Para esses cenários, as interfaces DCOM ilustradas usando WCF têm a seguinte estrutura:

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

O serviço retorna um objeto por valor

Para esse cenário, você faz uma chamada para um serviço e ele método retorna um objeto, que é passado por valor do servidor para o cliente. Este cenário representa a seguinte chamada COM:

public interface IRemoteService  
{  
    Customer GetObjectByValue();  
}  

Nesse cenário, o cliente recebe uma cópia desserializada de um objeto do serviço remoto. O cliente pode interagir com essa cópia local sem chamar de volta para o serviço. Em outras palavras, o cliente tem a garantia de que o serviço não será envolvido de forma alguma quando os métodos na cópia local forem chamados. WCF sempre retorna objetos do serviço por valor, portanto, as etapas a seguir descrevem a criação de um serviço WCF regular.

Etapa 1: Definir a interface de serviço WCF

Defina uma interface pública para o serviço WCF e marque-a com o atributo [ServiceContractAttribute]. Marque os métodos que você deseja expor aos clientes com o atributo [OperationContractAttribute]. O exemplo a seguir mostra o uso desses atributos para identificar a interface do lado do servidor e os métodos de interface que um cliente pode chamar. O método usado para este cenário é mostrado em negrito.

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

Etapa 2: Definir o contrato de dados

Em seguida, você deve criar um contrato de dados para o serviço, que descreverá como os dados serão trocados entre o serviço e seus clientes. As classes descritas no contrato de dados devem ser marcadas com o atributo [DataContractAttribute]. As propriedades ou campos individuais que você deseja que sejam visíveis para o cliente e o servidor devem ser marcados com o atributo [DataMemberAttribute]. Se desejar que os tipos derivados de uma classe no contrato de dados sejam permitidos, você deverá identificá-los com o atributo [KnownTypeAttribute]. O WCF serializará ou desserializará apenas tipos na interface de serviço e tipos identificados como tipos conhecidos. Se você tentar usar um tipo que não é um tipo conhecido, ocorrerá uma exceção.

Para obter mais informações sobre contratos de dados, consulte Contratos de dados.

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

Etapa 3: Implementar o serviço WCF

Em seguida, você deve implementar a classe de serviço WCF que implementa a interface definida na etapa anterior.

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

Etapa 4: Configurar o serviço e o cliente

Para executar um serviço WCF, você precisa declarar um ponto de extremidade que expõe essa interface de serviço em uma URL específica usando uma associação WCF específica. Uma associação especifica os detalhes de transporte, codificação e protocolo para os clientes e o servidor se comunicarem. Normalmente, você adiciona associações ao arquivo de configuração do projeto de serviço (web.config). A seguir mostra uma entrada de vinculação para o serviço de exemplo:

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

Em seguida, você precisa configurar o cliente para corresponder às informações de vinculação especificadas pelo serviço. Para fazer isso, adicione o seguinte ao arquivo de configuração do aplicativo do cliente (app.config).

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

Etapa 5: Executar o serviço

Finalmente, você pode hospedá-lo automaticamente em um aplicativo de console adicionando as seguintes linhas ao aplicativo de serviço e iniciando o aplicativo. Para obter mais informações sobre outras maneiras de hospedar um aplicativo de serviço WCF, Serviços de hospedagem.

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

Etapa 6: Chamar o serviço do cliente

Para chamar o serviço do cliente, você precisa criar uma fábrica de canais para o serviço e solicitar um canal, que permitirá que você chame diretamente o GetCustomer método diretamente do cliente. O canal implementa a interface do serviço e lida com a lógica de solicitação/resposta subjacente para você. O valor de retorno dessa chamada de método é a cópia desserializada da resposta do serviço.

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

O cliente envia um objeto por valor para o servidor

Nesse cenário, o cliente envia um objeto para o servidor, por valor. Isso significa que o servidor receberá uma cópia desserializada do objeto. O servidor pode chamar métodos nessa cópia e ter a garantia de que não há retorno de chamada no código do cliente. Como mencionado anteriormente, as trocas normais de dados WCF são por valor. Isso garante que a chamada de métodos em um desses objetos seja executada apenas localmente – ele não invocará código no cliente.

Este cenário representa a seguinte chamada de método COM:

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

Este cenário usa a mesma interface de serviço e contrato de dados como mostrado no primeiro exemplo. Além disso, o cliente e o serviço serão configurados da mesma forma. Neste exemplo, um canal é criado para enviar o objeto e executar da mesma maneira. No entanto, para este exemplo, você criará um cliente que chama o serviço, passando um objeto por valor. O método de serviço que o cliente chamará no contrato de serviço é mostrado em negrito:

[ServiceContract]  
public interface ICustomerManager  
{  
    [OperationContract]     void StoreCustomer(Customer customer);  
  
    [OperationContract]  
    Customer GetCustomer(string firstName, string lastName);  
}  

Adicionar código ao cliente que envia um objeto por valor

O código a seguir mostra como o cliente cria um novo objeto de cliente por valor, cria um canal para se comunicar com o ICustomerManager serviço e envia o objeto de cliente para ele.

O objeto do cliente será serializado e enviado para o serviço, onde é desserializado pelo serviço em uma nova cópia desse objeto. Todos os métodos que o serviço chama neste objeto serão executados apenas localmente no servidor. É importante notar que esse código ilustra o envio de um tipo derivado (PremiumCustomer). O contrato de serviço espera um Customer objeto, mas o contrato de dados de serviço usa o atributo [KnownTypeAttribute] para indicar que PremiumCustomer também é permitido. O WCF falhará nas tentativas de serializar ou desserializar qualquer outro tipo por meio dessa interface de serviço.

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

O serviço retorna um objeto por referência

Para esse cenário, o aplicativo cliente faz uma chamada para o serviço remoto e o método retorna um objeto, que é passado por referência do serviço para o cliente.

Como mencionado anteriormente, os serviços WCF sempre retornam objeto por valor. No entanto, você pode obter um resultado semelhante usando a EndpointAddress10 classe. O EndpointAddress10 é um objeto serializável por valor que pode ser usado pelo cliente para obter um objeto de referência de sessão no servidor.

O comportamento do objeto por referência no WCF mostrado neste cenário é diferente do DCOM. No DCOM, o servidor pode retornar um objeto por referência diretamente ao cliente e o cliente pode chamar os métodos desse objeto, que são executados no servidor. No WCF, no entanto, o objeto retornado é sempre por valor. O cliente deve pegar esse objeto by-value, representado por EndpointAddress10 e usá-lo para criar seu próprio objeto sessionful by-reference. O método client chama o objeto sessionful executado no servidor. Em outras palavras, esse objeto por referência no WCF é um serviço WCF normal que está configurado para ser sessionful.

No WCF, uma sessão é uma maneira de correlacionar várias mensagens enviadas entre dois pontos de extremidade. Isso significa que, uma vez que um cliente obtém uma conexão com esse serviço, uma sessão será estabelecida entre o cliente e o servidor. O cliente usará uma única instância exclusiva do objeto do lado do servidor para todas as suas interações dentro dessa única sessão. Os contratos WCF com sessão são semelhantes aos padrões de solicitação/resposta de rede orientados à conexão.

Este cenário é representado pelo seguinte método DCOM.

public interface IRemoteService  
{  
    IRemoteObject GetObjectByReference();  
}  

Etapa 1: Definir a interface e a implementação do serviço WCF Sessionful

Primeiro, defina uma interface de serviço WCF que contenha o objeto sessionful.

Neste código, o objeto sessionful é marcado com o ServiceContract atributo, que o identifica como uma interface de serviço WCF regular. Além disso, a SessionMode propriedade está definida para indicar que será um serviço de sessão.

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

O código a seguir mostra a implementação do serviço.

O serviço é marcado com o atributo [ServiceBehavior] e sua propriedade InstanceContextMode definida como InstanceContextMode.PerSessions para indicar que uma instância exclusiva desse tipo deve ser criada para cada sessão.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]  
    public class MySessionBoundObject : ISessionBoundObject  
    {  
        private string _value;  
  
        public string GetCurrentValue()  
        {  
            return _value;  
        }  
  
        public void SetCurrentValue(string val)  
        {  
            _value = val;  
        }  
  
    }  

Etapa 2: Definir o serviço de fábrica do WCF para o objeto sessionful

O serviço que cria o objeto sessionful deve ser definido e implementado. O código a seguir mostra como fazer isso. Esse código cria outro serviço WCF que retorna um EndpointAddress10 objeto. Esta é uma forma serializável de um ponto de extremidade que pode usar para criar o objeto de sessão completa.

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

Segue-se a implementação deste serviço. Essa implementação mantém uma fábrica de canal singleton para criar objetos com sessão. Quando GetInstanceAddress é chamado, ele cria um canal e cria um EndpointAddress10 objeto que aponta para o endereço remoto associado a esse canal. EndpointAddress10 é um tipo de dados que pode ser retornado ao cliente por valor.

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

Etapa 3: Configurar e iniciar os serviços WCF

Para hospedar esses serviços, você precisará fazer as seguintes adições ao arquivo de configuração do servidor (web.config).

  1. Adicione uma <client> seção que descreva o ponto de extremidade para o objeto com sessão. Nesse cenário, o servidor também atua como um cliente e deve ser configurado para habilitar isso.

  2. <services> Na seção , declare pontos de extremidade de serviço para o objeto factory e sessionful. Isso permite que o cliente se comunique com os pontos de extremidade de serviço, adquira e EndpointAddress10 crie o canal de sessão.

Segue-se um exemplo de ficheiro de configuração com estas definições:

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

Adicione as seguintes linhas a um aplicativo de console, para hospedar automaticamente o serviço e inicie o aplicativo.

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

Etapa 4: Configurar o cliente e chamar o serviço

Configure o cliente para se comunicar com os serviços WCF fazendo as seguintes entradas no arquivo de configuração do aplicativo do projeto (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>  

Para chamar o serviço, adicione o código ao cliente para fazer o seguinte:

  1. Crie um canal para o ISessionBoundFactory serviço.

  2. Use o canal para invocar o ISessionBoundFactory serviço e obter um EndpointAddress10 objeto.

  3. Use o EndpointAddress10 para criar um canal para obter um objeto sessionful.

  4. Chame os SetCurrentValue métodos e GetCurrentValue para demonstrar que permanece a mesma instância de objeto é usada em várias chamadas.

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

Consulte também