Compartilhar via


Migrando de .NET Remoting para o WCF

Este artigo descreve como migrar um aplicativo que usa o .NET Remoting para usar o WCF (Windows Communication Foundation). Ele compara conceitos semelhantes entre esses produtos e descreve como realizar vários cenários comuns de comunicação remota no WCF.

O .NET Remoting é um produto herdado que tem suporte apenas para compatibilidade com versões anteriores. Ele não é seguro em ambientes de confiança misturada porque não pode manter os níveis de confiança separados entre o cliente e o servidor. Por exemplo, você nunca deve expor um ponto de extremidade do .NET Remoting à Internet ou a clientes não confiáveis. Recomendamos que os aplicativos existentes do Remoting sejam migrados para tecnologias mais recentes e seguras. Se o design do aplicativo usa apenas HTTP e é RESTful, recomendamos ASP.NET Web API. Para obter mais informações, consulte ASP.NET Web API. Se o aplicativo for baseado em SOAP ou exigir protocolos não Http, como o TCP, recomendamos o WCF.

Comparação entre o .NET Remoting e o WCF

Esta seção compara os blocos de construção básicos da do .NET Remoting com seus equivalentes do WCF. Usaremos esses blocos de construção posteriormente para criar alguns cenários comuns de cliente-servidor no WCF. O gráfico a seguir resume as principais semelhanças e diferenças entre a o .NET Remoting e o WCF.

Comunicação remota .NET WCF
Tipo de servidor Subclasse MarshalByRefObject Marcar com o atributo [ServiceContract]
Operações de serviço Métodos públicos no tipo de servidor Marcar com o atributo [OperationContract]
Serialização ISerializable ou [Serializable] DataContractSerializer ou XmlSerializer
Objetos passados Por valor ou por referência Somente por valor
Erros/exceções Qualquer exceção serializável FaultContract<TDetail>
Objetos proxy do cliente Proxies transparentes fortemente tipados são criados automaticamente no MarshalByRefObjects Proxies fortemente tipados são gerados sob demanda usando o ChannelFactory<TChannel>
Plataforma necessária O cliente e o servidor devem usar o sistema operacional Microsoft e o .NET Plataforma cruzada
Formato da mensagem Privado Padrões do setor (por exemplo, SOAP e WS-*)

Comparação de implementação do servidor

Criar um servidor no .NET Remoting

Os tipos de servidor do .NET Remoting devem derivar de MarshalByRefObject e definir métodos que o cliente pode chamar, como:

public class RemotingServer : MarshalByRefObject  
{  
    public Customer GetCustomer(int customerId) { … }  
}  

Os métodos públicos desse tipo de servidor se tornam o contrato público disponível para os clientes. Não há separação entre a interface pública do servidor e sua implementação – um tipo serve para ambos.

Depois que o tipo de servidor tiver sido definido, ele poderá ser disponibilizado aos clientes, como no exemplo a seguir:

TcpChannel channel = new TcpChannel(8080);  
ChannelServices.RegisterChannel(channel, ensureSecurity : true);  
RemotingConfiguration.RegisterWellKnownServiceType(  
    typeof(RemotingServer),
    "RemotingServer",
    WellKnownObjectMode.Singleton);  
Console.WriteLine("RemotingServer is running.  Press ENTER to terminate...");  
Console.ReadLine();  

Há várias maneiras de disponibilizar o tipo de comunicação remota como servidor, incluindo o uso de arquivos de configuração. Este é apenas um exemplo.

Criar um servidor no WCF

A etapa equivalente no WCF envolve a criação de dois tipos: o "contrato de serviço" público e a implementação concreta. O primeiro é declarado como uma interface marcada com [ServiceContract]. Os métodos disponíveis para clientes são marcados com [OperationContract]:

[ServiceContract]  
public interface IWCFServer  
{  
    [OperationContract]  
    Customer GetCustomer(int customerId);  
}  

A implementação do servidor é definida em uma classe concreta separada, como no exemplo a seguir:

public class WCFServer : IWCFServer  
{  
    public Customer GetCustomer(int customerId) { … }  
}  

Depois que esses tipos forem definidos, o servidor WCF poderá ser disponibilizado aos clientes, como no exemplo a seguir:

NetTcpBinding binding = new NetTcpBinding();  
Uri baseAddress = new Uri("net.tcp://localhost:8000/wcfserver");  
  
using (ServiceHost serviceHost = new ServiceHost(typeof(WCFServer), baseAddress))  
{  
    serviceHost.AddServiceEndpoint(typeof(IWCFServer), binding, baseAddress);  
    serviceHost.Open();  
  
    Console.WriteLine($"The WCF server is ready at {baseAddress}.");
    Console.WriteLine("Press <ENTER> to terminate service...");  
    Console.WriteLine();  
    Console.ReadLine();  
}  

Observação

O TCP é usado em ambos os exemplos para mantê-los o mais semelhante possível. Consulte o passo a passo do cenário mais adiante neste tópico para obter exemplos do uso de HTTP.

Há muitas maneiras de configurar e hospedar serviços WCF. Este é apenas um exemplo, conhecido como "auto-hospedado". Para obter mais informações, consulte estes tópicos:

Comparação de implementação do cliente

Criar um cliente no .NET Remoting

Depois que um objeto de servidor do .NET Remoting for disponibilizado, ele poderá ser consumido pelos clientes, como no exemplo a seguir:

TcpChannel channel = new TcpChannel();  
ChannelServices.RegisterChannel(channel, ensureSecurity : true);  
RemotingServer server = (RemotingServer)Activator.GetObject(  
                            typeof(RemotingServer),
                            "tcp://localhost:8080/RemotingServer");  
  
RemotingCustomer customer = server.GetCustomer(42);  
Console.WriteLine($"Customer {customer.FirstName} {customer.LastName} received.");

A instância RemotingServer retornada de Activator.GetObject() é conhecida como um "proxy transparente". Implementa a API pública para o tipo RemotingServer no cliente, mas todos os métodos chamam o objeto de servidor em execução em um processo ou computador diferente.

Criar um cliente no WCF

A etapa equivalente no WCF envolve o uso de uma fábrica de canais para criar o proxy explicitamente. Assim como no Remoting, o objeto proxy pode ser usado para invocar operações no servidor, como no exemplo a seguir:

NetTcpBinding binding = new NetTcpBinding();  
String url = "net.tcp://localhost:8000/wcfserver";  
EndpointAddress address = new EndpointAddress(url);  
ChannelFactory<IWCFServer> channelFactory =
    new ChannelFactory<IWCFServer>(binding, address);  
IWCFServer server = channelFactory.CreateChannel();  
  
Customer customer = server.GetCustomer(42);  
Console.WriteLine($"  Customer {customer.FirstName} {customer.LastName} received.");

Este exemplo mostra a programação no nível do canal porque é mais semelhante ao exemplo do Remoting. A abordagem Adicionar Referência de Serviço também está disponível no Visual Studio, que gera código para simplificar a programação do cliente. Para obter mais informações, consulte estes tópicos:

Uso de serialização

Tanto o .NET Remoting como o WCF usam a serialização para enviar objetos entre o cliente e o servidor, mas diferem das seguintes importantes formas:

  1. Usam serializadores e convenções diferentes para indicar o que serializar.

  2. O .NET Remoting dá suporte à serialização "por referência" que permite que o acesso à propriedade ou método em uma camada execute o código na outra camada, que está entre os limites de segurança. Essa funcionalidade expõe vulnerabilidades de segurança e é um dos principais motivos pelos quais os pontos de extremidade do Remoting nunca devem ser expostos a clientes não confiáveis.

  3. A serialização usada pelo Remoting é opt-out (excluir explicitamente o que não deve ser serializado) e a serialização do WCF é opt-in (marcar explicitamente quais membros serializar).

Serialização no .NET Remoting

O .NET Remoting dá suporte a duas maneiras de serializar e desserializar objetos entre o cliente e o servidor:

  • Por valor – os valores do objeto são serializados entre limites de camada e uma nova instância desse objeto é criada na outra camada. Todas as chamadas para métodos ou propriedades dessa nova instância são executadas apenas localmente e não afetam o objeto ou a camada original.

  • Por referência – uma "referência de objeto" especial é serializada entre limites de camada. Quando uma camada interage com métodos ou propriedades desse objeto, ela se comunica novamente com o objeto original na camada original. Objetos por referência podem fluir em qualquer direção – servidor para cliente ou cliente para servidor.

Tipos por valor em Comunicação Remota são marcados com o atributo [Serializável] ou implementam ISerializable, como no exemplo a seguir:

[Serializable]  
public class RemotingCustomer  
{  
    public string FirstName { get; set; }  
    public string LastName { get; set; }  
    public int CustomerId { get; set; }  
}  

Os tipos de referência derivam da classe MarshalByRefObject, como no exemplo a seguir:

public class RemotingCustomerReference : MarshalByRefObject  
{  
    public string FirstName { get; set; }  
    public string LastName { get; set; }  
    public int CustomerId { get; set; }  
}  

É extremamente importante entender as implicações dos objetos de referência do Remoting. Se uma camada (cliente ou servidor) enviar um objeto por referência para a outra camada, todas as chamadas de método serão executadas novamente na camada que possui o objeto. Por exemplo, um cliente que chama métodos em um objeto por referência retornado pelo servidor executará o código no servidor. Da mesma forma, um método de chamada de servidor em um objeto por referência fornecido pelo cliente executará o código de volta no cliente. Por esse motivo, o uso do .NET Remoting é recomendado somente em ambientes totalmente confiáveis. Expor um ponto de extremidade público do .NET Remoting a clientes não confiáveis tornará um servidor Remoting vulnerável a ataques.

Serialização no WCF

O WCF dá suporte apenas à serialização por valor. A maneira mais comum de definir um tipo de troca entre cliente e servidor é semelhante ao exemplo a seguir:

[DataContract]  
public class WCFCustomer  
{  
    [DataMember]  
    public string FirstName { get; set; }  
  
    [DataMember]  
    public string LastName { get; set; }  
  
    [DataMember]  
    public int CustomerId { get; set; }  
}  

O atributo [DataContract] identifica esse tipo como um tipo que pode ser serializado e desserializado entre cliente e servidor. O atributo [DataMember] identifica as propriedades ou campos individuais a serem serializados.

Quando o WCF envia um objeto entre camadas, ele serializa apenas os valores e cria uma nova instância do objeto na outra camada. Todas as interações com os valores do objeto ocorrem apenas localmente – elas não se comunicam com a outra camada da maneira que os objetos de referência do .NET Remoting fazem. Para obter mais informações, consulte Serialização e desserialização.

Recursos de tratamento de exceções

Exceções no .NET Remoting

As exceções geradas por um servidor Remoting são serializadas, enviadas ao cliente e lançadas localmente no cliente, como qualquer outra exceção. Exceções personalizadas podem ser criadas por meio da subclassificação do tipo Exceção e marcando-o com [Serializable]. A maioria das exceções de estrutura já está marcada dessa forma, permitindo que a maioria seja gerada pelo servidor, serializada e lançada novamente no cliente. Embora esse design seja conveniente durante o desenvolvimento, as informações do lado do servidor podem ser inadvertidamente divulgadas ao cliente. Esse é um dos muitos motivos pelos quais o Remoting deve ser usado apenas em ambientes totalmente confiáveis.

Exceções e falhas no WCF

O WCF não permite que tipos de exceção arbitrários sejam retornados do servidor para o cliente pois eles podem levar à divulgação inadvertida de informações. Se uma operação de serviço gerar uma exceção inesperada, ela fará com que uma FaultException de finalidade geral seja lançada no cliente. Essa exceção não contém informações sobre por quê ou onde o problema ocorreu e, para alguns aplicativos, isso é suficiente. Aplicativos que precisam comunicar informações de erro mais avançadas ao cliente fazem isso definindo um contrato de falha.

Para fazer isso, primeiro crie um tipo [DataContract] para carregar as informações de falha.

[DataContract]  
public class CustomerServiceFault  
{  
    [DataMember]  
    public string ErrorMessage { get; set; }  
  
    [DataMember]  
    public int CustomerId {get;set;}  
}  

Especifique o contrato de falha a ser usado para cada operação de serviço.

[ServiceContract]  
public interface IWCFServer  
{  
    [OperationContract]  
    [FaultContract(typeof(CustomerServiceFault))]  
    Customer GetCustomer(int customerId);  
}  

O servidor relata condições de erro lançando uma FaultException.

throw new FaultException<CustomerServiceFault>(  
    new CustomerServiceFault() {
        CustomerId = customerId,
        ErrorMessage = "Illegal customer Id"
    });  

E sempre que o cliente faz uma solicitação ao servidor, ele pode detectar falhas como exceções normais.

try  
{  
    Customer customer = server.GetCustomer(-1);  
}  
catch (FaultException<CustomerServiceFault> fault)  
{  
    Console.WriteLine($"Fault received: {fault.Detail.ErrorMessage}");
}  

Para obter mais informações sobre os contratos, consulte FaultException.

Considerações de segurança

Segurança no .NET Remoting

Alguns canais do .NET Remoting dão suporte a recursos de segurança, como autenticação e criptografia na camada de canal (IPC e TCP). O canal HTTP depende dos Serviços de Informações da Internet (IIS) para autenticação e criptografia. Apesar desse suporte, você deve considerar o .NET Remoting um protocolo de comunicação não seguro e usá-lo somente em ambientes totalmente confiáveis. Nunca exponha um ponto de extremidade público do Remoting à Internet ou a clientes não confiáveis.

Segurança no WCF

O WCF foi projetado com a segurança em mente, em parte para abordar os tipos de vulnerabilidades encontradas no .NET Remoting. O WCF oferece segurança no nível de transporte e mensagem, além de oferecer muitas opções para autenticação, autorização, criptografia e assim por diante. Para obter mais informações, consulte estes tópicos:

Migrar para o WFC

Por que migrar do Remoting para o WCF?

  • O .NET Remoting é uma tecnologia herdada. Conforme descrito no .NET Remoting, ele é considerado um produto herdado e não é recomendado para um novo desenvolvimento. O WCF ou o ASP.NET Web API são recomendados para aplicativos novos e existentes.

  • O WCF usa padrões multiplataforma. O WCF foi projetado com interoperabilidade entre plataformas em mente e dá suporte a muitos padrões do setor (SOAP, WS-Security, WS-Trust etc.). Um serviço WCF pode interoperar com clientes em execução em sistemas operacionais diferentes do Windows. A comunicação remota foi projetada principalmente para ambientes em que os aplicativos cliente e servidor são executados usando o .NET Framework em um sistema operacional Windows.

  • O WCF tem segurança interna. O WCF foi projetado com a segurança em mente e oferece muitas opções para autenticação, segurança no nível do transporte, segurança no nível da mensagem etc. A comunicação remota foi projetada para facilitar a interoperação dos aplicativos, mas não foi projetada para ser segura em ambientes não confiáveis. O WCF foi projetado para funcionar em ambientes confiáveis e não confiáveis.

Recomendações de migração

Estas são as etapas recomendadas para migrar do .NET Remoting para o WCF:

  • Crie o contrato de serviço. Defina seus tipos de interface de serviço e marque-os com o atributo [ServiceContract]. Marque todos os métodos que os clientes poderão chamar com [OperationContract].

  • Crie o contrato de dados. Defina os tipos de dados que serão trocados entre o servidor e o cliente e marque-os com o atributo [DataContract]. Marque todos os campos e propriedades que o cliente poderá usar com [DataMember].

  • Crie o contrato de falha (opcional). Crie os tipos que serão trocados entre o servidor e o cliente quando forem encontrados erros. Marque esses tipos com [DataContract] e [DataMember] para torná-los serializáveis. Para todas as operações de serviço marcadas com [OperationContract], marque-as também com [FaultContract] para indicar quais erros elas podem retornar.

  • Configure e hospede o serviço. Depois que o contrato de serviço for criado, a próxima etapa será configurar uma associação para expor o serviço em um ponto de extremidade. Para obter mais informações, consulte Pontos de extremidade: endereços, associações e contratos.

Depois que um aplicativo de comunicação remota for migrado para o WCF, ainda é importante remover as dependências do .NET Remoting. Isso garante que todas as vulnerabilidades do Remoting sejam removidas do aplicativo. Essas etapas incluem o seguinte:

  • Descontinue o uso de MarshalByRefObject. O tipo MarshalByRefObject existe apenas para o Remoting e não é usado pelo WCF. Todos os tipos de aplicativo que subclassificam o MarshalByRefObject devem ser removidos ou alterados.

  • Descontinue o uso de [Serializable] e ISerializable. O atributo [Serializable] e a interface ISerializable foram originalmente projetados para serializar tipos em ambientes confiáveis e são usados pelo Remoting. A serialização do WCF conta com os tipos que estão sendo marcados com [DataContract] e [DataMember]. Os tipos de dados usados por um aplicativo devem ser modificados para usar [DataContract] e não usar ISerializable ou [Serializable].

Cenários de migração

Agora vamos ver como realizar os seguintes cenários comuns do Remoting no WCF:

  1. O servidor retorna um objeto por valor para o cliente

  2. O servidor retorna um objeto por referência para o cliente

  3. O cliente envia um objeto por valor para o servidor

Observação

Enviar um objeto por referência do cliente para o servidor não é permitido no WCF.

Ao ler esses cenários, suponha que nossas interfaces de linha de base para o .NET Remoting sejam semelhantes ao exemplo a seguir. A implementação do .NET Remoting não é importante aqui porque queremos ilustrar apenas como usar o WCF para implementar a funcionalidade equivalente.

public class RemotingServer : MarshalByRefObject  
{  
    // Demonstrates server returning object by-value  
    public Customer GetCustomer(int customerId) {…}  
  
    // Demonstrates server returning object by-reference  
    public CustomerReference GetCustomerReference(int customerId) {…}  
  
    // Demonstrates client passing object to server by-value  
    public bool UpdateCustomer(Customer customer) {…}  
}  

Cenário 1: O serviço retorna um objeto por valor

Esse cenário demonstra um servidor retornando um objeto para o cliente por valor. O WCF sempre retorna objetos do serviço por valor e, portanto, as etapas a seguir simplesmente descrevem como criar um serviço WCF normal.

  1. Comece definindo uma interface pública para o serviço WCF e marque-a com o atributo [ServiceContract]. Usamos [OperationContract] para identificar os métodos do lado do servidor que nosso cliente chamará.

    [ServiceContract]  
    public interface ICustomerService  
    {  
        [OperationContract]  
        Customer GetCustomer(int customerId);  
    
        [OperationContract]  
        bool UpdateCustomer(Customer customer);  
    }  
    
  2. A próxima etapa é criar o contrato de dados para esse serviço. Fazemos isso criando classes (não interfaces) marcadas com o atributo [DataContract]. As propriedades ou os campos individuais que queremos que estejam visíveis para o cliente e o servidor serão marcados com [DataMember]. Se quisermos que tipos derivados sejam permitidos, devemos usar o atributo [KnownType] para identificá-los. Os únicos tipos que o WCF permitirá que sejam serializados ou desserializados para esse serviço são aqueles na interface de serviço e esses "tipos conhecidos". A tentativa de trocar qualquer outro tipo que não esteja nesta lista será rejeitada.

    [DataContract]  
    [KnownType(typeof(PremiumCustomer))]  
    public class Customer  
    {  
        [DataMember]  
        public string FirstName { get; set; }  
    
        [DataMember]  
        public string LastName { get; set; }  
    
        [DataMember]  
        public int CustomerId { get; set; }  
    }  
    
    [DataContract]  
    public class PremiumCustomer : Customer
    {  
        [DataMember]  
        public int AccountId { get; set; }  
    }  
    
  3. A seguir, fornecemos a implementação para a interface do serviço.

    public class CustomerService : ICustomerService  
    {  
        public Customer GetCustomer(int customerId)  
        {  
            // read from database  
        }  
    
        public bool UpdateCustomer(Customer customer)  
        {  
            // write to database  
        }  
    }  
    
  4. Para executar o serviço WCF, é necessário declarar um ponto de extremidade que expõe essa interface de serviço em uma URL específica usando uma associação do WCF específica. Normalmente, isso é feito adicionando as seções a seguir ao arquivo web.config do projeto de servidor.

    <configuration>  
      <system.serviceModel>  
        <services>  
          <service name="Server.CustomerService">  
            <endpoint address="http://localhost:8083/CustomerService"  
                      binding="basicHttpBinding"  
                      contract="Shared.ICustomerService" />  
          </service>  
        </services>  
      </system.serviceModel>  
    </configuration>  
    
  5. Em seguida, o serviço WCF pode ser iniciado com o seguinte código:

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

    Quando este ServiceHost é iniciado, ele usa o arquivo web.config para estabelecer o contrato, a associação e o ponto de extremidade adequados. Para obter mais informações sobre os arquivos de configuração, consulte Configurar serviços usando arquivos de configuração. Esse estilo de iniciar o servidor é conhecido como auto-hospedagem. Para saber mais sobre outras opções para hospedar serviços WCF, consulte Serviços de hospedagem.

  6. O app.config do projeto cliente deve declarar informações de associação correspondentes para o ponto de extremidade do serviço. A maneira mais fácil de fazer isso no Visual Studio é usar Adicionar Referência de Serviço, que atualizará automaticamente o arquivo app.config. Como alternativa, essas mesmas alterações podem ser adicionadas manualmente.

    <configuration>  
      <system.serviceModel>  
        <client>  
          <endpoint name="customerservice"  
                    address="http://localhost:8083/CustomerService"  
                    binding="basicHttpBinding"  
                    contract="Shared.ICustomerService"/>  
        </client>  
      </system.serviceModel>  
    </configuration>  
    

    Para obter mais informações sobre como usar Adicionar Referência de Serviço, consulte Como adicionar, atualizar ou remover uma referência de serviço.

  7. Agora podemos chamar o serviço WCF do cliente. Fazemos isso criando uma fábrica de canais para esse serviço, solicitando um canal e chamando diretamente o método desejado nesse canal. Podemos fazer isso pois o canal implementa a interface do serviço e manipula a lógica subjacente de solicitação/resposta para nós. O valor retornado dessa chamada de método que é a cópia desserializada da resposta do servidor.

    ChannelFactory<ICustomerService> factory =  
        new ChannelFactory<ICustomerService>("customerservice");  
    ICustomerService service = factory.CreateChannel();  
    Customer customer = service.GetCustomer(42);  
    Console.WriteLine($"  Customer {customer.FirstName} {customer.LastName} received.");
    

Os objetos retornados pelo WCF do servidor para o cliente são sempre por valor. Os objetos são cópias desserializadas dos dados enviados pelo servidor. O cliente pode chamar métodos nessas cópias locais sem qualquer perigo de invocar o código do servidor por meio de retornos de chamada.

Cenário 2: O servidor retorna um objeto por referência

Esse cenário demonstra o servidor fornecendo um objeto para o cliente por referência. No .NET Remoting, isso é tratado automaticamente para qualquer tipo derivado de MarshalByRefObject, que é serializado por referência. Um exemplo desse cenário é permitir que vários clientes tenham objetos de sessão independentes do lado do servidor. Como mencionado anteriormente, os objetos retornados por um serviço WCF são sempre por valor e, portanto, não há equivalente direto de um objeto por referência, mas é possível alcançar algo semelhante à semântica por referência usando um objeto EndpointAddress10. Esse é um objeto serializável por valor que pode ser usado pelo cliente para obter um objeto por referência de sessão no servidor. Isso permite o cenário de ter vários clientes com objetos de sessão independentes do lado do servidor.

  1. Primeiro, precisamos definir um contrato de serviço WCF que corresponda ao próprio objeto de sessão.

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

    Dica

    Observe que o objeto de sessão está marcado com [ServiceContract], tornando-o uma interface de serviço WCF normal. Definir a propriedade SessionMode indica que será um serviço de sessão. No WCF, uma sessão é uma maneira de correlacionar várias mensagens enviadas entre dois pontos de extremidade. Isso significa que, depois que um cliente obtiver uma conexão para 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 interações dele nessa única sessão.

  2. Em seguida, precisamos fornecer a implementação dessa interface de serviço. Ao denotá-lo com [ServiceBehavior] e definir o InstanceContextMode, informamos ao WCF que queremos usar uma instância exclusiva desse tipo 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;  
            }  
    
        }  
    
  3. Agora, precisamos de uma maneira de obter uma instância deste objeto com sessão. Fazemos isso criando outra interface de serviço WCF que retorna um objeto EndpointAddress10. Essa é uma forma serializável de um ponto de extremidade que o cliente pode usar para criar o objeto de sessão.

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

    E implementamos este serviço WCF:

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

    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 objeto EndpointAddress10 que aponta efetivamente para o endereço remoto associado a esse canal. EndpointAddress10 é simplesmente um tipo de dados que pode ser retornado ao cliente por valor.

  4. Precisamos modificar o arquivo de configuração do servidor fazendo estas duas coisas, conforme mostrado no exemplo abaixo:

    1. Declare uma seção <cliente> que descreva o ponto de extremidade para o objeto de sessão. Isso é necessário porque o servidor também atua como um cliente em tal situação.

    2. Declare pontos de extremidade para a fábrica e o objeto de sessão. Isso é necessário para permitir que o cliente se comunicque com os pontos de extremidade de serviço para adquirir EndpointAddress10 e criar o canal de sessão.

    <configuration>  
      <system.serviceModel>  
         <client>  
          <endpoint name="sessionbound"  
                    address="net.tcp://localhost:8081/SessionBoundObject"  
                    binding="netTcpBinding"  
                    contract="Shared.ISessionBoundObject"/>  
        </client>  
        <services>  
          <service name="Server.CustomerService">  
            <endpoint address="http://localhost:8083/CustomerService"  
                      binding="basicHttpBinding"  
                      contract="Shared.ICustomerService" />  
          </service>  
          <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>  
    

    E, assim, podemos iniciar esses serviços:

    ServiceHost factoryHost = new ServiceHost(typeof(SessionBoundFactory));  
    factoryHost.Open();  
    
    ServiceHost sessionHost = new ServiceHost(typeof(MySessionBoundObject));  
    sessionHost.Open();  
    
  5. Configuramos o cliente declarando esses mesmos pontos de extremidade no arquivo app.config do projeto.

    <configuration>  
      <system.serviceModel>  
        <client>  
          <endpoint name="customerservice"  
                    address="http://localhost:8083/CustomerService"  
                    binding="basicHttpBinding"  
                    contract="Shared.ICustomerService"/>  
          <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>  
    
  6. Para criar e usar este objeto de sessão, o cliente deve executar as seguintes etapas:

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

    2. Use esse canal para invocar esse serviço para obter um EndpointAddress10.

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

    4. Interaja com o objeto de sessão para demonstrar que ele permanece a mesma instância em várias chamadas.

    ChannelFactory<ISessionBoundFactory> channelFactory =
        new ChannelFactory<ISessionBoundFactory>("factory");  
    ISessionBoundFactory sessionFactory = channelFactory.CreateChannel();  
    
    EndpointAddress10 address1 = sessionFactory.GetInstanceAddress();  
    EndpointAddress10 address2 = sessionFactory.GetInstanceAddress();  
    
    ChannelFactory<ISessionBoundObject> sessionObjectFactory1 =
        new ChannelFactory<ISessionBoundObject>(new NetTcpBinding(),
                                                address1.ToEndpointAddress());  
    ChannelFactory<ISessionBoundObject> sessionObjectFactory2 =
        new ChannelFactory<ISessionBoundObject>(new NetTcpBinding(),
                                                address2.ToEndpointAddress());  
    
    ISessionBoundObject sessionInstance1 = sessionObjectFactory1.CreateChannel();  
    ISessionBoundObject sessionInstance2 = sessionObjectFactory2.CreateChannel();  
    
    sessionInstance1.SetCurrentValue("Hello");  
    sessionInstance2.SetCurrentValue("World");  
    
    if (sessionInstance1.GetCurrentValue() == "Hello" &&  
        sessionInstance2.GetCurrentValue() == "World")  
    {  
        Console.WriteLine("sessionful server object works as expected");  
    }  
    

O WCF sempre retorna objetos por valor, mas é possível dar suporte ao equivalente à semântica por referência por meio do uso de EndpointAddress10. Isso permite que o cliente solicite uma instância de serviço WCF de sessão, após o que, é possível interagir com ele como qualquer outro serviço WCF.

Cenário 3: o cliente envia ao servidor uma instância por valor

Esse cenário demonstra o cliente enviando uma instância de objeto não primitiva para o servidor por valor. Como o WCF envia apenas objetos por valor, esse cenário demonstra o uso normal do WCF.

  1. Use o mesmo serviço WCF do Cenário 1.

  2. Use o cliente para criar um novo objeto por valor (Customer), criar um canal para se comunicar com o serviço ICustomerService e enviar o objeto para ele.

    ChannelFactory<ICustomerService> factory =  
        new ChannelFactory<ICustomerService>("customerservice");  
    ICustomerService service = factory.CreateChannel();  
    PremiumCustomer customer = new PremiumCustomer {
    FirstName = "Bob",
    LastName = "Jones",
    CustomerId = 43,
    AccountId = 99};  
    bool success = service.UpdateCustomer(customer);  
    Console.WriteLine($"  Server returned {success}.");
    

    O objeto de cliente será serializado e enviado para o servidor, em que ele será desserializado gerando uma nova cópia desse objeto.

    Observação

    Esse código também ilustra o envio de um tipo derivado (PremiumCustomer). A interface de serviço espera um objeto Customer, mas o atributo [KnownType] na classe Customer indicou que PremiumCustomer também foi permitido. Qualquer tentativa do WCF de serializar ou desserializar qualquer outro tipo por meio desta interface de serviço falhará.

As trocas normais de dados do WCF são por valor. Isso garante que invocar métodos em um desses objetos resultará apenas em execução local – não invocará código na outra camada. Embora seja possível obter algo como objetos de referência retornados do servidor, não é possível que um cliente passe um objeto por referência para o servidor. Um cenário que requer uma conversa de ida e volta entre o cliente e o servidor pode ser obtido no WCF usando um serviço duplex. Para obter mais informações, consulte Serviços de duplex.

Resumo

O .NET Remoting é uma estrutura de comunicação destinada a ser usada somente em ambientes totalmente confiáveis. É um produto herdado e tem suporte apenas para compatibilidade com versões anteriores. Não deve ser usado para criar novos aplicativos. Por outro lado, o WCF foi projetado com a segurança em mente e é recomendado para aplicativos novos e existentes. A Microsoft recomenda que os aplicativos existentes do Remoting sejam migrados para usar o WCF ou o ASP.NET Web API.