Partilhar via


Como: Migrar Managed-Code DCOM para WCF

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

  • O serviço remoto devolve um objeto por valor ao cliente

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

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

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

Para mais detalhes sobre a criação de serviços e clientes WCF para esses serviços, consulte Programação Básica WCF, Desenho e Implementação de Serviços, e Construção de Clientes.

Código de exemplo DCOM

Para estes 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 devolve um objeto por valor

Neste cenário, faz-se uma chamada a um serviço e o método devolve um objeto, que é passado por valor do servidor para o cliente. Este cenário representa a seguinte chamada COM:

public interface IRemoteService
{
    Customer GetObjectByValue();
}

Neste cenário, o cliente recebe uma cópia deserializada de um objeto do serviço remoto. O cliente pode interagir com esta cópia local sem ter de voltar a chamar o serviço. Ou seja, o cliente tem a garantia de que o serviço não estará envolvido de forma alguma quando os métodos da cópia local forem chamados. O WCF devolve sempre os objetos do serviço por valor, pelo que os passos seguintes descrevem a criação de um serviço WCF regular.

Passo 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 pretende expor aos clientes com o atributo [OperationContractAttribute]. O exemplo seguinte mostra a utilização destes atributos para identificar a interface do lado do servidor e os métodos de interface que um cliente pode chamar. O método utilizado neste cenário está mostrado a 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);

}

Passo 2: Defina o contrato de dados

De seguida, deve criar um contrato de dados para o serviço, que descreva como os dados serão trocados entre o serviço e os seus clientes. As classes descritas no contrato de dados devem ser marcadas com o atributo [DataContractAttribute]. As propriedades individuais ou campos que quer que sejam visíveis tanto para o cliente como para o servidor devem estar marcados com o atributo [DataMemberAttribute]. Se quiser que tipos derivados de uma classe no contrato de dados sejam permitidos, deve identificá-los com o atributo [KnownTypeAttribute]. A WCF apenas serializa ou desserializa tipos na interface de serviço e tipos identificados como tipos conhecidos. Se tentar usar um tipo que não é um tipo conhecido, ocorrerá uma exceção.

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

Passo 3: Implementar o serviço WCF

De seguida, deve implementar a classe de serviço WCF que implementa a interface que definiu no passo 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
    }
}

Passo 4: Configurar o serviço e o cliente

Para executar um serviço WCF, é necessário declarar um endpoint que exponha essa interface de serviço numa URL específica usando uma ligação WCF específica. Uma ligação especifica os detalhes do transporte, codificação e protocolo para que os clientes e o servidor possam comunicar. Normalmente adiciona-se ligações ao ficheiro de configuração do projeto de serviço (web.config). O seguinte mostra uma entrada vinculativa 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>

De seguida, precisa de configurar o cliente para corresponder à informação de ligação especificada pelo serviço. Para tal, adicione o seguinte ao ficheiro de configuração da aplicação (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>

Passo 5: Executar o serviço

Por fim, pode alojá-lo sozinho numa aplicação de consola adicionando as seguintes linhas à aplicação de serviço e iniciando a aplicação. Para mais informações sobre outras formas de hospedar uma aplicação de serviço WCF, Serviços de Alojamento.

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

Passo 6: Chamar o serviço do cliente

Para chamar o serviço a partir do cliente, precisa de criar uma fábrica de canais para o serviço e pedir um canal, o que lhe permitirá chamar o método GetCustomer diretamente a partir do cliente. O canal implementa a interface do serviço e trata da lógica subjacente de pedido/resposta por si. 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

Neste cenário, o cliente envia um objeto para o servidor, por valor. Isto 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á callback para o código do cliente. Como mencionado anteriormente, as trocas normais de dados em WCF são por valor. Isto garante que chamar métodos num destes objetos é executado apenas localmente – 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 utiliza a mesma interface de serviço e contrato de dados mostrado no primeiro exemplo. Além disso, o cliente e o serviço serão configurados da mesma forma. Neste exemplo, é criado um canal para enviar o objeto e funcionar da mesma forma. No entanto, neste exemplo, você irá criar um cliente que chama o serviço ao passar um objeto por valor. O método de serviço que o cliente irá chamar no contrato de serviço é mostrado a 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 como valor

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

O objeto do cliente será serializado e enviado para o serviço, onde é desserializado pelo serviço numa nova cópia desse objeto. Quaisquer métodos que o serviço chame neste objeto serão executados apenas localmente no servidor. É importante notar que este 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 falhará nas tentativas de serializar ou desserializar qualquer outro tipo de objeto através desta 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 devolve um objeto por referência

Neste cenário, a aplicação cliente faz uma chamada ao serviço remoto e o método devolve um objeto, que é passado por referência do serviço para o cliente.

Como mencionado anteriormente, os serviços WCF retornam sempre objeto por valor. No entanto, pode alcançar 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 por referência sessional no servidor.

O comportamento do objeto por referência no WCF mostrado neste cenário é diferente do DCOM. No DCOM, o servidor pode devolver 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, o objeto devolvido é sempre por valor. O cliente deve pegar nesse objeto por valor, representado por ele EndpointAddress10 , e usá-lo para criar o seu próprio objeto por referência sessional. O método cliente chama ao objeto sessionivo executado no servidor. Por outras palavras, este objeto por referência no WCF é um serviço WCF normal configurado para ser sessional.

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 pedido/resposta de rede orientados para a conexão.

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

public interface IRemoteService
{
    IRemoteObject GetObjectByReference();
}

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

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

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

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

    [OperationContract]
    void SetCurrentValue(string value);
}

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

O serviço é marcado com o atributo [ServiceBehavior], e a sua propriedade InstanceContextMode está definida como InstanceContextMode.PerSessions para indicar que uma instância única deste 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;
        }

    }

Passo 2: Definir a fábrica de serviços WCF para o objeto com estado de sessão

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

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

Segue-se a implementação deste serviço. Esta implementação mantém uma fábrica de canal singleton para criar objetos com estado de sessão. Quando GetInstanceAddress é chamado, 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 devolvido 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);
        }
    }

Passo 3: Configurar e iniciar os serviços WCF

Para alojar estes serviços, terá de fazer as seguintes adições ao ficheiro de configuração do servidor (web.config).

  1. Adicione uma <client> secção que descreva o endpoint do objeto da sessão. Neste cenário, o servidor também atua como cliente e deve ser configurado para permitir isso.

  2. Na seção <services>, declare os endpoints de serviço para o objeto de fábrica e objeto com sessão. Isto permite ao cliente comunicar com os endpoints do serviço, adquirir o EndpointAddress10 e criar o canal com sessão.

Segue-se um ficheiro de configuração de exemplo 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 uma aplicação de consola, para auto-hospedar o serviço, e inicie a aplicação.

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

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

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

Configure o cliente para comunicar com os serviços WCF fazendo as seguintes entradas no ficheiro de configuração da aplicação 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 serviço ISessionBoundFactory.

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