Поделиться через


Миграция с .NET Remoting на WCF

В этой статье описывается перенос приложения, использующего удаленное взаимодействие .NET для использования Windows Communication Foundation (WCF). Он сравнивает аналогичные понятия между этими продуктами, а затем описывает, как выполнить несколько распространенных сценариев удаленного взаимодействия в WCF.

Удаленное взаимодействие .NET — это устаревший продукт, который поддерживается только для обратной совместимости. Он не является безопасным в средах смешанного доверия, так как он не может поддерживать отдельные уровни доверия между клиентом и сервером. Например, никогда не следует размещать конечную точку .NET Remoting в интернет или делать её доступной ненадежным клиентам. Рекомендуется перенести существующие приложения удаленного взаимодействия на более новые и более безопасные технологии. Если проект приложения использует только HTTP и является RESTful, рекомендуется ASP.NET веб-API. Дополнительные сведения см. в разделе ASP.NET веб-API. Если приложение основано на протоколе SOAP или требует протоколов, отличных от HTTP, таких как TCP, рекомендуется ИСПОЛЬЗОВАТЬ WCF.

Сравнение .NET Remoting и WCF

В этом разделе сравниваются основные стандартные блоки удаленного взаимодействия .NET с эквивалентами WCF. Далее мы будем использовать эти стандартные блоки для создания некоторых распространенных сценариев клиентского сервера в WCF. На следующей диаграмме приведены основные похожие черты и различия между Remoting .NET и WCF.

Удаленное взаимодействие в .NET WCF (Windows Communication Foundation)
Тип сервера Подкласс MarshalByRefObject Отметьте атрибут [ServiceContract]
Операции службы Общедоступные методы в типе сервера Отметьте атрибут [OperationContract]
сериализация ISerializable или [Serializable] DataContractSerializer или XmlSerializer
Переданные объекты По значению или по ссылке Только по значению
Ошибки и исключения Любое сериализуемое исключение FaultContract<TDetail>
Объекты прокси клиента Строго типизированные прозрачные прокси-серверы создаются автоматически из MarshalByRefObject. Строго типизированные прокси генерируются по запросу с помощью ChannelFactory<TChannel>
Требуется платформа Клиент и сервер должны использовать ОС Майкрософт и .NET Кроссплатформенная платформа
Формат сообщения Частный Отраслевые стандарты (например, SOAP и WS-*)

Сравнение реализации сервера

Создание сервера в удаленном взаимодействии .NET

Типы серверов удаленного взаимодействия .NET должны быть производными от MarshalByRefObject и определять методы, которые клиент может вызывать следующим образом:

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

Общедоступные методы этого типа сервера становятся общедоступным контрактом, доступным для клиентов. Между общедоступным интерфейсом сервера и его реализацией нет разделения — один тип обрабатывает оба типа.

После определения типа сервера его можно сделать доступным для клиентов, как показано в следующем примере:

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

Существует множество способов сделать тип удаленного взаимодействия доступным как сервер, включая использование файлов конфигурации. Это лишь один пример.

Создание сервера в WCF

Эквивалентный шаг в WCF включает создание двух типов — общедоступного "контракта службы" и конкретной реализации. Первый объявлен как интерфейс, помеченный [ServiceContract]. Методы, доступные клиентам, помечены [OperationContract]:

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

Реализация сервера определена в отдельном конкретном классе, как показано в следующем примере:

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

После определения этих типов сервер WCF можно сделать доступным для клиентов, как показано в следующем примере:

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

Замечание

TCP используется в обоих примерах, чтобы сохранить их как можно более похожими. Примеры использования HTTP можно найти в пошаговых инструкциях по сценарию, представленных далее в этом разделе.

Существует множество способов настройки и размещения служб WCF. Это только один пример, известный как "саморазмещённый". Дополнительные сведения см. в следующих разделах:

Сравнение внедрения клиента

Создание клиента для удаленного взаимодействия с .NET

После того как объект сервера удаленного взаимодействия .NET был доступен, его можно использовать клиентами, как показано в следующем примере:

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

Экземпляр RemotingServer, возвращенный из Активора.GetObject(), называется прозрачным прокси-сервером. Он реализует общедоступный API для типа RemotingServer на клиенте, но все методы вызывают объект сервера, выполняющийся в другом процессе или компьютере.

Создание клиента в WCF

Эквивалентный шаг в WCF включает использование фабрики каналов для явного создания прокси. Как и при удалённом взаимодействии, прокси-объект можно использовать для вызова операций на сервере, как показано в следующем примере:

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

В этом примере показано программирование на уровне канала, так как оно наиболее похоже на пример удаленного взаимодействия. Также доступен подход Add Service Reference в Visual Studio, который создает код для упрощения клиентского программирования. Дополнительные сведения см. в следующих разделах:

Использование сериализации

Удаленное взаимодействие .NET и WCF используют сериализацию для отправки объектов между клиентом и сервером, но они различаются следующими важными способами:

  1. Они используют различные сериализаторы и соглашения, чтобы указать, что следует сериализовать.

  2. Удаленное взаимодействие .NET поддерживает сериализацию "по ссылке", которая позволяет методу или свойству на одном уровне выполнять код на другом уровне, разделенном границами безопасности. Эта возможность предоставляет уязвимости безопасности и является одной из основных причин, по которым конечные точки удаленного взаимодействия никогда не должны предоставляться ненадежным клиентам.

  3. Сериализация, используемая в удаленном взаимодействии, предполагает включение всего по умолчанию и требует явного исключения того, что не следует сериализовать, в то время как сериализация WCF, наоборот, предполагает исключение всего по умолчанию и требует явного указания членов для сериализации.

Сериализация в удаленном взаимодействии .NET

Удаленное взаимодействие .NET поддерживает два способа сериализации и десериализации объектов между клиентом и сервером:

  • По значению — значения объекта сериализуются по границам уровня, а новый экземпляр этого объекта создается на другом уровне. Все вызовы методов или свойств этого нового экземпляра выполняются только локально и не влияют на исходный объект или уровень.

  • По ссылке — специальная "ссылка на объект" сериализуется между границами уровней. Когда один уровень взаимодействует с методами или свойствами этого объекта, он взаимодействует обратно с исходным объектом на исходном уровне. Объекты по ссылке могут передаваться в любом направлении — с сервера на клиент или с клиента на сервер.

Типы значений для удаленного взаимодействия помечаются атрибутом [Serializable] или реализуют ISerializable, как показано в следующем примере:

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

Ссылочные типы являются производными от класса MarshalByRefObject, как показано в следующем примере:

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

Крайне важно понимать последствия объектов удаленного взаимодействия по ссылке. Если любой уровень (клиент или сервер) отправляет объект по ссылке на другой уровень, все вызовы метода выполняются обратно на уровне, принадлежащим объекту. Например, клиент, вызывающий методы на объекте, переданном по ссылке и возвращённом сервером, будет выполнять код на сервере. Аналогичным образом, сервер, вызывающий методы объекта, переданного клиентом по ссылке, будет выполнять код на стороне клиента. По этой причине рекомендуется использовать удаленное взаимодействие .NET только в полностью доверенных средах. Предоставление общедоступной конечной точки удаленного взаимодействия .NET ненадежным клиентам сделает сервер удаленного взаимодействия уязвимым для атаки.

Сериализация в WCF

WCF поддерживает только сериализацию по значению. Наиболее распространенный способ определить тип для обмена между клиентом и сервером, как показано в следующем примере:

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

Атрибут [DataContract] определяет этот тип как тот, который можно сериализовать и десериализировать между клиентом и сервером. Атрибут [DataMember] определяет отдельные свойства или поля для сериализации.

Когда WCF отправляет объект по уровням, он сериализует только значения и создает новый экземпляр объекта на другом уровне. Любые взаимодействия со значениями объекта происходят только локально – они не взаимодействуют с другим слоем так, как объекты, передаваемые по ссылке в .NET Remoting. Дополнительные сведения см. в разделе Сериализация и десериализация.

Возможности обработки исключений

Исключения в удаленном управлении .NET

Исключения, создаваемые сервером удаленного взаимодействия, сериализуются, отправляются клиенту и создаются локально на клиенте, как и любое другое исключение. Пользовательские исключения можно создать, создав подкласс типа Exception и отметив его [Serializable]. Большинство исключений платформы уже таким образом обозначены, что они могут выбрасываться сервером, сериализуются и повторно выбрасываются на клиенте. Хотя эта конструкция удобна во время разработки, информация на стороне сервера может быть непреднамеренно раскрыта клиенту. Это одна из многих причин, по которым удаленное взаимодействие должно использоваться только в полностью доверенных средах.

Исключения и ошибки в WCF

WCF не разрешает возвращать произвольные типы исключений от сервера клиенту, так как это может привести к непреднамеренному раскрытию информации. Если операция службы вызывает неожиданное исключение, это приводит к возникновению на клиенте исключения FaultException общего назначения. Это исключение не содержит никаких сведений о том, почему или где возникла проблема, и для некоторых приложений это достаточно. Приложения, которым нужно сообщать клиенту более полную информацию об ошибках, делают это, определяя контракт на сбой.

Для этого сначала создайте тип [DataContract] для передачи информации о сбое.

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

Укажите контракт сбоя, используемый для каждой операции службы.

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

Сервер передает условия ошибки, вызывая исключение FaultException.

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

И всякий раз, когда клиент отправляет запрос на сервер, он может обрабатывать ошибки, как если бы это были обычные исключения.

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

Дополнительные сведения о контрактах на обработку ошибок см. в разделе FaultException.

Соображения безопасности

Безопасность в удаленном взаимодействии через .NET

Некоторые каналы удаленного взаимодействия .NET поддерживают такие функции безопасности, как проверка подлинности и шифрование на уровне каналов (IPC и TCP). Http-канал использует службы IIS для проверки подлинности и шифрования. Несмотря на данную поддержку, .NET Remoting следует рассматривать как небезопасный протокол связи и использовать его только в полностью доверенных средах. Никогда не предоставляйте общедоступную конечную точку удаленного взаимодействия в Интернете или ненадежным клиентам.

Безопасность в WCF

WCF был разработан с учетом безопасности, в частности для устранения уязвимостей, обнаруженных в .NET Remoting. WCF обеспечивает безопасность на уровне транспорта и сообщения, а также предлагает множество вариантов проверки подлинности, авторизации, шифрования и т. д. Дополнительные сведения см. в следующих разделах:

Перемещение на WCF

Почему миграция с технологии удаленного взаимодействия на WCF?

  • Удаленное взаимодействие .NET — это устаревший продукт. Как описано в .NET Remoting, оно считается устаревшим продуктом и не рекомендуется для новой разработки. Веб-API WCF или ASP.NET рекомендуется использовать для новых и существующих приложений.

  • WCF использует кроссплатформенные стандарты. WCF был разработан с учетом кроссплатформенного взаимодействия и поддерживает множество отраслевых стандартов (SOAP, WS-Security, WS-Trust и т. д.). Служба WCF может взаимодействовать с клиентами, работающими в операционных системах, отличных от Windows. Удаленное взаимодействие было разработано в основном для сред, где серверные и клиентские приложения выполняются с помощью .NET Framework в операционной системе Windows.

  • WCF имеет встроенную безопасность. WCF был разработан с учетом безопасности и предлагает множество вариантов проверки подлинности, безопасности уровня транспорта, безопасности на уровне сообщений и т. д. Удаленное взаимодействие было разработано для упрощения взаимодействия приложений, но не было разработано для защиты в ненадежных средах. WCF был разработан для работы как в доверенных, так и ненадежных средах.

Рекомендации по миграции

Ниже приведены рекомендуемые шаги по миграции с Remoting .NET на WCF:

  • Создайте контракт службы. Определите типы интерфейса службы и пометьте их атрибутом [ServiceContract]. Пометьте все методы, которые клиенты смогут вызывать с помощью [OperationContract].

  • Создайте контракт данных. Определите типы данных, которые будут обмениваться между сервером и клиентом, и пометьте их атрибутом [DataContract]. Пометьте все поля и свойства, которые клиент будет использовать с [DataMember].

  • Создайте контракт сбоя (необязательно). Создайте типы, которые будут обмениваться между сервером и клиентом при обнаружении ошибок. Пометьте эти типы с помощью [DataContract] и [DataMember], чтобы сделать их сериализуемыми. Для всех операций службы, помеченных как [OperationContract], также помечайте их с помощью [FaultContract], чтобы указать, какие ошибки они могут возвращать.

  • Настройте и разместите службу. После создания контракта службы следующим шагом является настройка привязки для предоставления службы в конечной точке. Дополнительные сведения см. в разделе "Конечные точки: адреса, привязки и контракты".

После переноса приложения .NET Remoting в WCF все еще важно удалить зависимости от этой технологии. Это гарантирует, что все уязвимости удаленного взаимодействия удаляются из приложения. Эти действия включают:

  • Прекратите использование MarshalByRefObject. Тип MarshalByRefObject существует только для удаленного взаимодействия и не используется WCF. Все типы приложений, которые должны быть удалены или изменены подклассом MarshalByRefObject.

  • Прекратите использование [Serializable] и ISerializable. Атрибут [Serializable] и интерфейс ISerializable изначально были разработаны для сериализации типов в доверенных средах и используются Ремоутингом. Сериализация WCF зависит от типов, помеченных [DataContract] и [DataMember]. Типы данных, используемые приложением, следует изменять для работы с [DataContract] и не использовать ISerializable или [Serializable].

Сценарии миграции

Теперь давайте посмотрим, как выполнить следующие распространенные сценарии удаленного взаимодействия в WCF:

  1. Сервер возвращает объект по значению клиенту

  2. Сервер возвращает объект по ссылке клиенту.

  3. Клиент отправляет объект по значению на сервер

Замечание

Отправка объекта по ссылке от клиента на сервер не допускается в WCF.

При чтении этих сценариев предположим, что базовые интерфейсы для удаленного взаимодействия .NET выглядят следующим образом. Реализация удаленного взаимодействия .NET не важна, так как мы хотим проиллюстрировать только то, как использовать WCF для реализации эквивалентных функций.

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) {…}  
}  

Сценарий 1. Служба возвращает объект по значению

В этом сценарии показан сервер, возвращающий объект клиенту по значению. WCF всегда возвращает объекты с сервера по значению, поэтому следующие действия просто описывают, как создать обычную службу WCF.

  1. Начните с определения общедоступного интерфейса для службы WCF и пометьте его атрибутом [ServiceContract]. Мы используем [OperationContract], чтобы определить методы на стороне сервера, которые будет вызывать наш клиент.

    [ServiceContract]  
    public interface ICustomerService  
    {  
        [OperationContract]  
        Customer GetCustomer(int customerId);  
    
        [OperationContract]  
        bool UpdateCustomer(Customer customer);  
    }  
    
  2. Следующим шагом является создание контракта данных для этой службы. Для этого мы создадим классы (не интерфейсы), помеченные атрибутом [DataContract]. Отдельные свойства или поля, которые мы хотим видеть как клиенту, так и серверу, помечены как [DataMember]. Если требуется разрешить производные типы, необходимо использовать атрибут [KnownType] для их идентификации. Единственные типы, которые WCF разрешит сериализовать или десериализовать для этой службы, — это те, которые находятся в интерфейсе службы, и эти "известные типы". Попытка обмена любым другим типом, не указанным в этом списке, будет отклонена.

    [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. Далее мы предоставляем реализацию для интерфейса службы.

    public class CustomerService : ICustomerService  
    {  
        public Customer GetCustomer(int customerId)  
        {  
            // read from database  
        }  
    
        public bool UpdateCustomer(Customer customer)  
        {  
            // write to database  
        }  
    }  
    
  4. Чтобы запустить службу WCF, необходимо объявить конечную точку, которая предоставляет этот интерфейс службы по определенному URL-адресу с помощью определенной привязки WCF. Обычно это делается путем добавления следующих разделов в файл web.config проекта сервера.

    <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. Затем службу WCF можно запустить со следующим кодом:

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

    При запуске ServiceHost используется файл web.config для установления соответствующего контракта, привязки и конечной точки. Дополнительные сведения о файлах конфигурации см. в разделе "Настройка служб с помощью файлов конфигурации". Этот стиль запуска сервера называется самостоятельным размещением. Дополнительные сведения о других вариантах размещения служб WCF см. в разделе "Службы размещения".

  6. app.config клиентского проекта должен устанавливать соответствующие параметры привязки для конечной точки службы. Самый простой способ сделать это в Visual Studio — добавить ссылку на службу, которая автоматически обновит файл app.config. Кроме того, эти же изменения можно добавить вручную.

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

    Дополнительные сведения об использовании ссылки на службу см. в статье "Практическое руководство. Добавление, обновление или удаление ссылки на службу".

  7. Теперь мы можем вызвать службу WCF с клиентской стороны. Для этого мы создадим фабрику каналов для этой службы, запрашивая канал и напрямую вызывая метод, который мы хотим использовать в этом канале. Это можно сделать, так как канал реализует интерфейс службы и обрабатывает базовую логику запроса или ответа для нас. Возвращаемое значение из этого вызова метода — десериализированная копия ответа сервера.

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

Объекты, которые WCF возвращает с сервера на клиент, всегда передаются по значению. Объекты являются десериализованными копиями данных, отправляемых сервером. Клиент может вызывать методы на этих локальных копиях без какой-либо опасности вызова кода сервера с помощью обратных вызовов.

Сценарий 2. Сервер возвращает объект по ссылке

Этот сценарий демонстрирует сервер, предоставляющий клиенту объект по ссылке. В .NET Remoting это обрабатывается автоматически для любого типа, который наследуется от MarshalByRefObject и сериализуется по ссылке. Примером этого сценария является возможность для нескольких клиентов иметь независимые серверные объекты, сохраняющие сессии. Как упоминалось ранее, объекты, возвращаемые службой WCF, всегда по значению, поэтому нет прямого эквивалента объекта по ссылке, но можно достичь аналогичной семантике по ссылке с помощью EndpointAddress10 объекта. Это сериализуемый по значению объект, который может использоваться клиентом для получения сеансового объекта по ссылке на сервере. Это позволяет сценарий использования нескольких клиентов с независимыми сессионными объектами на стороне сервера.

  1. Сначала необходимо определить контракт службы WCF, который соответствует объекту, поддерживающему сеансы.

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

    Подсказка

    Обратите внимание, что сеансовый объект помечен как [ServiceContract], тем самым делая его обычным интерфейсом службы WCF. Установка свойства SessionMode указывает, что это будет сеансовая служба. В WCF сеанс — это способ сопоставления нескольких сообщений, отправленных между двумя конечными точками. Это означает, что после подключения к этой службе между клиентом и сервером будет установлена сессия. Клиент будет использовать один уникальный экземпляр объекта на стороне сервера для всех его взаимодействий в рамках этого одного сеанса.

  2. Далее необходимо предоставить реализацию этого интерфейса службы. Обозначая его с помощью [ServiceBehavior] и задав InstanceContextMode, мы сообщаем WCF, что мы хотим использовать уникальный экземпляр этого типа для каждого сеанса.

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]  
        public class MySessionBoundObject : ISessionBoundObject  
        {  
            private string _value;  
    
            public string GetCurrentValue()  
            {  
                return _value;  
            }  
    
            public void SetCurrentValue(string val)  
            {  
                _value = val;  
            }  
    
        }  
    
  3. Теперь нам нужен способ получения экземпляра этого сеансового объекта. Для этого мы создадим другой интерфейс службы WCF, который возвращает объект EndpointAddress10. Это сериализуемая форма конечной точки, которую клиент может использовать для создания сеансового объекта.

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

    И мы реализуем эту службу 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);  
            }  
        }  
    

    Эта реализация поддерживает фабрику однотонных каналов для создания сеансовых объектов. При вызове GetInstanceAddress() он создает канал и создает объект EndpointAddress10, который фактически указывает на удаленный адрес, связанный с этим каналом. EndpointAddress10 — это просто тип данных, который можно вернуть клиенту по значению.

  4. Необходимо изменить файл конфигурации сервера, выполнив следующие два действия, как показано в следующем примере:

    1. Объявите секцию <клиента>, описывающую конечную точку для сеансового объекта. Это необходимо, так как сервер также выступает в качестве клиента в этой ситуации.

    2. Объявите конечные точки для фабрики и сеансового объекта. Это необходимо, чтобы клиент взаимодействовал с конечными точками службы, чтобы получить EndpointAddress10 и создать сеансовый канал.

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

    Затем мы можем запустить следующие службы:

    ServiceHost factoryHost = new ServiceHost(typeof(SessionBoundFactory));  
    factoryHost.Open();  
    
    ServiceHost sessionHost = new ServiceHost(typeof(MySessionBoundObject));  
    sessionHost.Open();  
    
  5. Мы настраиваем клиент, указав те же конечные точки в файле app.config проекта.

    <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. Чтобы создать и использовать этот сеансовый объект, клиент должен выполнить следующие действия:

    1. Создайте канал для службы ISessionBoundFactory.

    2. Используйте этот канал для вызова этой службы, чтобы получить EndpointAddress10.

    3. Используйте EndpointAddress10, чтобы создать канал для получения сеансового объекта.

    4. Взаимодействуйте с сеансовым объектом, чтобы продемонстрировать, что он остается одинаковым для нескольких вызовов.

    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 всегда возвращает объекты по значению, но можно поддерживать эквивалент семантики по ссылке с помощью EndpointAddress10. Это позволяет клиенту запрашивать экземпляр службы WCF с поддержкой сессий, после чего клиент может взаимодействовать с ней, как с любой другой службой WCF.

Сценарий 3: Клиент отправляет серверу экземпляр By-Value

Этот сценарий демонстрирует, как клиент отправляет экземпляр немитивного объекта серверу по значению. Так как WCF отправляет объекты только по значению, этот сценарий демонстрирует обычное использование WCF.

  1. Используйте ту же службу WCF из сценария 1.

  2. Используйте клиент для создания нового объекта по значению (Customer), создайте канал для взаимодействия со службой ICustomerService и отправьте объект в него.

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

    Объект клиента будет сериализован и отправлен на сервер, где он десериализирован в новую копию этого объекта.

    Замечание

    Этот код также иллюстрирует отправку производного типа (PremiumCustomer). Интерфейс службы ожидает объект Customer, но атрибут [KnownType] в классе Customer указал, что PremiumCustomer также разрешен. WCF отклонит любую попытку сериализации или десериализации другого типа через этот интерфейс службы.

Обычные обмены данными в WCF осуществляются по значению. Это гарантирует, что вызов методов на одном из этих объектов данных выполняется только локально. Он не будет вызывать код на другом уровне. Хотя можно добиться чего-то похожего, возвращая объекты по ссылке с сервера, операция передачи объекта по ссылке на сервер невозможна для клиента. Сценарий, требующий беседы между клиентом и сервером, можно достичь в WCF с помощью дуплексной службы. Дополнительные сведения см. в разделе "Дуплексные службы".

Сводка

Удаленное взаимодействие .NET — это платформа коммуникации, предназначенная для использования только в полностью доверенных средах. Это устаревший продукт и поддерживается только для обратной совместимости. Его не следует использовать для создания новых приложений. И наоборот, WCF был разработан с учетом безопасности и рекомендуется для новых и существующих приложений. Корпорация Майкрософт рекомендует существующие приложения удаленного взаимодействия мигрировать для использования WCF или веб-API ASP.NET.