Compartilhar via


Como migrar Managed-Code DCOM para o WCF

O Windows Communication Foundation (WCF) é a opção recomendada e segura em relação ao 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 cenários a seguir.

  • 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 obtido no WCF usando um serviço duplex. Para obter mais informações sobre serviços duplex, consulte Duplex Services.

Para obter mais detalhes sobre como criar serviços e clientes do WCF para esses serviços, consulte Basic WCF Programming, Designing and Implementing Services e Building Clients.

Código de exemplo DCOM

Para esses cenários, as interfaces DCOM ilustradas usando o 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 o método retorna um objeto, que é passado por valor do servidor para o cliente. Esse 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 ligar de volta para o serviço. Em outras palavras, o cliente tem a garantia de que o serviço não estará envolvido de forma alguma quando os métodos na cópia local forem chamados. O 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 do 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 como usar esses 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 esse 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 fiquem visíveis ao cliente e ao servidor devem ser marcados com o atributo [DataMemberAttribute]. Se você quiser que tipos derivados de uma classe no contrato de dados sejam permitidos, você deve identificá-los com o atributo [KnownTypeAttribute]. O WCF só serializará ou desserializará tipos na interface de serviço e tipos identificados como tipos conhecidos. Se você tentar usar um tipo que não seja 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 que você definiu 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 que os clientes e o servidor se comuniquem. Normalmente, você adiciona associações ao arquivo de configuração do projeto de serviço (web.config). Mostra-se a seguir 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 associação especificadas pelo serviço. Para fazer isso, adicione o seguinte ao arquivo de configuração de aplicativo (app.config) do cliente.

<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

Por fim, 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 a partir do cliente, você precisa criar uma fábrica de canais para o serviço e solicitar um canal, o que permitirá que você execute o método GetCustomer diretamente do cliente. O canal implementa a interface do serviço e manipula a lógica de solicitação/resposta subjacente para você. O valor retornado 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 passado 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. Conforme mencionado anteriormente, as trocas normais de dados do WCF são por valor. Isso garante que os métodos de chamada em um desses objetos sejam executados localmente somente – ele não invocará o código no cliente.

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

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

Esse cenário usa a mesma interface de serviço e o contrato de dados, conforme mostrado no primeiro exemplo. Além disso, o cliente e o serviço serão configurados da mesma maneira. 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 do cliente para ele.

O objeto do cliente será serializado e enviado para o serviço, onde será desserializado pelo serviço em uma nova cópia desse objeto. Todos os métodos que o serviço chamar nesse objeto serão executados apenas localmente no servidor. É importante observar 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. WCF não conseguirá serializar ou desserializar qualquer outro tipo via essa 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.

Conforme 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 com estado 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 tomar esse objeto por valor, representado por EndpointAddress10, e usá-lo para criar seu próprio objeto de referência baseado em sessão. O método do cliente chama o objeto sessionful para execução no servidor. Em outras palavras, esse objeto por referência no WCF é um serviço WCF normal que está configurado para ser sessionful.

WCF, uma sessão é uma forma de correlacionar várias mensagens enviadas entre dois pontos finais. 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 nesta única sessão. Os contratos do WCF com sessão são semelhantes aos padrões de solicitação/resposta de rede orientados à conexão.

Esse cenário é representado pelo método DCOM a seguir.

public interface IRemoteService
{
    IRemoteObject GetObjectByReference();
}

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

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

Nesse código, o objeto com estado de sessão é marcado com o atributo ServiceContract, que o identifica como uma interface de serviço WCF padrão. 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 com sessão

O serviço que cria o objeto de sessão 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. Essa é uma forma serializável de um ponto de extremidade que pode ser usado para criar o objeto completo da sessão.

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

Veja a seguir a implementação desse serviço. Essa implementação mantém uma fábrica de canais única para criar objetos de 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 dado 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 do 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 descreve o ponto de extremidade para o objeto de sessão. Nesse cenário, o servidor também atua como um cliente e deve ser configurado para habilitá-lo.

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

Veja a seguir um arquivo de configuração de exemplo com estas configuraçõ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 linhas a seguir a um aplicativo de console, para hospedar o serviço automaticamente e iniciar 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 do 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 para obter um EndpointAddress10 objeto.

  3. Use o EndpointAddress10 para criar um canal para obter um objeto com sessão.

  4. Chame os métodos SetCurrentValue e GetCurrentValue para demonstrar que 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