Partilhar via


Migrando da comunicação remota .NET para WCF

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

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

Comparando a comunicação remota do .NET com o WCF

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

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

Comparação de implementação de servidor

Criando um servidor com .NET Remoting

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

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

Os métodos públicos associados a este tipo de servidor constituem 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 lida com ambos.

Uma vez definido o tipo de servidor, ele pode 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á muitas maneiras de disponibilizar o tipo de comunicação remota como um servidor, incluindo o uso de arquivos de configuração. Este é apenas um exemplo.

Criando 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 os clientes estã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) { … }  
}  

Uma vez que esses tipos tenham sido definidos, o servidor WCF pode ser disponibilizado para 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 semelhantes possível. Consulte as instruções passo a passo do cenário mais adiante neste tópico para obter exemplos usando 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 os seguintes tópicos:

Comparação de Implementações de Cliente

Criando um cliente na comunicação remota .NET

Depois que um objeto de servidor .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". Ele implementa a API pública para o tipo RemotingServer no cliente, mas todos os métodos chamam o objeto do servidor em execução em um processo ou máquina diferente.

Criando um cliente no WCF

A etapa equivalente no WCF envolve a utilização de uma fábrica de canais para criar o proxy de forma explícita. Tal como o Remoting, o objeto proxy pode ser utilizado 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 de comunicação remota. Também está disponível a abordagem Add Service Reference no Visual Studio que gera código para simplificar a programação do cliente. Para obter mais informações, consulte os seguintes tópicos:

Uso da serialização

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

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

  2. O .NET Remoting oferece suporte à serialização "por referência" que permite o acesso ao método ou à propriedade em uma camada para executar código na outra camada, que está além dos limites de segurança. Esse recurso expõe vulnerabilidades de segurança e é uma das principais razões pelas quais os endpoints remotos nunca devem ser expostos a clientes não confiáveis.

  3. A serialização utilizada pelo Remoting é o mecanismo 'opt-out' (exclui explicitamente o que não deve ser serializado) e a serialização no WCF é 'opt-in' (marca explicitamente quais membros devem ser serializados).

Serialização na comunicação remota do .NET

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

  • Por valor – os valores do objeto são serializados através dos limites da 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 através dos limites da camada. Quando uma camada interage com métodos ou propriedades desse objeto, ela se comunica de volta ao objeto original na camada original. Os objetos por referência podem fluir em qualquer direção – servidor para cliente ou cliente para servidor.

Os tipos de subvalor em Comunicação remota são marcados com o atributo [Serializable] 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 compreender as implicações dos objetos por referência do Remoting. Se qualquer 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 proprietária do objeto. Por exemplo, um cliente chamando métodos em um objeto por referência retornado pelo servidor executará código no servidor. Da mesma forma, um servidor chamando métodos 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 apenas em ambientes totalmente confiáveis. Expor um ponto de extremidade público do .NET Remoting a clientes não confiáveis tornará um servidor de Remoting vulnerável a ataques.

Serialização no WCF

O WCF suporta apenas a serialização por valor. A maneira mais comum de definir um tipo de troca entre cliente e servidor é como no 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 que pode ser serializado e desserializado entre cliente e servidor. O atributo [DataMember] identifica as propriedades individuais ou campos 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. Quaisquer interações com os valores do objeto ocorrem apenas localmente – eles não se comunicam com a outra camada da mesma forma que os objetos de referência do .NET Remoting. Para obter mais informações, consulte Serialização e desserialização.

Recursos de tratamento de exceções

Exceções na comunicação remota do .NET

As exceções lançadas por um servidor remoto são serializadas, enviadas ao cliente e lançadas localmente no cliente como qualquer outra exceção. As exceções personalizadas podem ser criadas subclassificando o tipo de exceção e marcando-o com [Serializável]. A maioria das exceções de framework já estão marcadas dessa forma, permitindo que a maioria seja lançada pelo servidor, serializada e relançada no cliente. Embora esse design seja conveniente durante o desenvolvimento, as informações do lado do servidor podem ser divulgadas inadvertidamente ao cliente. Esta é uma das muitas razões pelas quais a comunicação remota deve ser usada 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 porque isso pode levar à divulgação inadvertida de informações. Se uma operação de serviço lançar uma exceção inesperada, ela fará com que um FaultException de uso geral seja lançado no cliente. Esta exceção não contém qualquer informação sobre o porquê ou onde o problema ocorreu, e para algumas aplicações isso é suficiente. Os aplicativos que precisam comunicar informações de erro mais ricas para o 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 um 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 detetar 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 contratos de falha, consulte FaultException.

Considerações de segurança

Segurança na comunicação remota .NET

Alguns canais .NET Remoting suportam 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 apenas em ambientes totalmente confiáveis. Nunca exponha um endpoint de Remoting público à Internet ou a clientes não fidedignos.

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 e oferece muitas opções de autenticação, autorização, criptografia e assim por diante. Para obter mais informações, consulte os seguintes tópicos:

Migrando para WCF

Por que migrar da comunicação remota para o WCF?

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

  • O WCF usa padrões multiplataforma. O WCF foi projetado com a interoperabilidade entre plataformas em mente e suporta 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 integrada. O WCF foi projetado com a segurança em mente e oferece muitas opções de autenticação, segurança de nível de transporte, segurança de nível de mensagem, etc. A comunicação remota foi projetada para facilitar a interoperação de 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

A seguir estão as etapas recomendadas para migrar do .NET Remoting para WCF:

  • Crie o contrato de serviço. Defina os 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 servidor e cliente e marque-os com o atributo [DataContract]. Marque todos os campos e propriedades que o cliente terá permissão para 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. Uma vez que o contrato de serviço tenha sido criado, a próxima etapa é configurar uma associação para expor o serviço em um endpoint. Para obter mais informações, consulte Pontos de extremidade: endereços, ligações e contratos.

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

  • Descontinue o uso de MarshalByRefObject. O tipo MarshalByRefObject existe apenas para comunicação remota e não é usado pelo WCF. Qualquer tipo de aplicação que seja uma subclasse de MarshalByRefObject deve ser removido ou alterado.

  • 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 depende de tipos marcados com [DataContract] e [DataMember]. Os tipos de dados usados por uma aplicação devem ser modificados para usar [DataContract] em vez de usar ISerializable ou [Serializable].

Cenários de migração

Agora vamos ver como realizar os seguintes cenários comuns de comunicação remota no WCF:

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

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

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

Observação

O envio de 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 comunicação remota .NET se pareçam com o exemplo a seguir. A implementação do .NET Remoting não é importante aqui porque queremos ilustrar apenas como usar o WCF para implementar 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: Serviço retorna um objeto por valor

Este cenário demonstra um servidor retornando um objeto para o cliente por valor. WCF sempre retorna objetos do servidor por valor, 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 irá chamar.

    [ServiceContract]  
    public interface ICustomerService  
    {  
        [OperationContract]  
        Customer GetCustomer(int customerId);  
    
        [OperationContract]  
        bool UpdateCustomer(Customer customer);  
    }  
    
  2. O próximo passo é criar o contrato de dados para este serviço. Fazemos isso criando classes (não interfaces) marcadas com o atributo [DataContract]. As propriedades individuais ou campos que queremos que sejam visíveis para o cliente e o servidor estão marcados com [DataMember]. Se quisermos que os 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 este serviço são aqueles na interface de serviço e esses "tipos conhecidos". A tentativa de troca de 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. Em seguida, fornecemos a implementação para a interface de 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, precisamos 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. Isso geralmente é feito adicionando as seguintes seções 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. O serviço WCF pode então 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, o binding e o endpoint adequados. Para obter mais informações sobre arquivos de configuração, consulte Configurando serviços usando arquivos de configuração. Este estilo de iniciar o servidor é conhecido como auto-hospedagem. Para saber mais sobre outras opções de hospedagem de serviços WCF, consulte Serviços de hospedagem.

  6. O app.config do projeto cliente deve declarar informação de ligação correspondente ao 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 a partir da aplicação cliente. Fazemos isso criando uma fábrica de canais para esse serviço, solicitando um canal e ligando diretamente para o método que queremos nesse canal. Podemos fazer isso porque o canal implementa a interface do serviço e lida com a lógica de solicitação/resposta subjacente para nós. O valor de retorno dessa chamada de método é 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

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

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

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

    Sugestão

    Observe que o objeto sessionful está marcado com [ServiceContract], tornando-o uma interface de serviço WCF normal. Definir a propriedade SessionMode indica que será um serviço com sessão. 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.

  2. Em seguida, precisamos fornecer a implementação desta interface de serviço. Ao denotá-lo com [ServiceBehavior] e definir o InstanceContextMode, dizemos 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 desse objeto sessionful. Fazemos isso criando outra interface de serviço WCF que retorna um objeto EndpointAddress10. Esta é uma forma serializável de um ponto de extremidade que o cliente pode usar para criar o objeto sessionful.

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

    Esta implementação mantém uma fábrica de canal singleton para criar objetos com estado de sessão. Quando GetInstanceAddress() é chamado, ele cria um canal e cria um objeto EndpointAddress10 que efetivamente aponta para o endereço remoto associado a esse canal. O 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 as duas coisas a seguir, conforme mostrado no exemplo abaixo:

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

    2. Declare pontos de extremidade para o objeto factory e sessionful. Isso é necessário para permitir que o cliente comunique com os endereços de serviço, adquira o EndpointAddress10 e estabeleça um canal com 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 então podemos iniciar estes 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 endereços no ficheiro app.config do seu 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 esse objeto sessionful, o cliente deve executar as seguintes etapas:

    1. Crie 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 com sessão.

    4. Interaja com o objeto sessionful 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");  
    }  
    

WCF sempre retorna objetos por valor, mas é possível suportar o equivalente de semântica por referência através do uso de EndpointAddress10. Isso permite que o cliente solicite uma instância de serviço WCF com sessão, após a qual ele pode interagir com ela como qualquer outro serviço WCF.

Cenário 3: O cliente envia ao servidor uma instância By-Value

Este cenário demonstra o cliente a enviar uma instância de objeto não primitivo para o servidor pelo seu valor. Como o WCF só envia 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 do cliente será serializado e enviado para o servidor, onde será desserializado em uma nova cópia desse objeto.

    Observação

    Este código também ilustra o envio de um tipo derivado (PremiumCustomer). A interface de serviço espera um objeto Cliente, mas o atributo [KnownType] na classe Cliente indicou que PremiumCliente também era permitido. O WCF rejeitará qualquer tentativa de serializar ou desserializar qualquer outro tipo através desta interface de serviço.

As trocas normais de dados WCF são por valor. Isso garante que a invocação de métodos em um desses objetos de dados seja executada apenas localmente – ele não invocará código na outra camada. Embora seja possível obter algo como objetos por referência retornados do servidor, não é possível para um cliente passar um objeto por referência para o servidor. 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 obter mais informações, consulte Serviços Duplex.

Resumo

O .NET Remoting é uma estrutura de comunicação destinada a ser usada apenas em ambientes totalmente confiáveis. É um produto legado e suportado apenas para compatibilidade com versões anteriores. Ele 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 de comunicação remota existentes sejam migrados para usar WCF ou ASP.NET API Web.