Практическое руководство. Миграция DCOM с управляемым кодов в WCF
Для вызовов управляемого кода между серверами и клиентами в распределенной среде рекомендуется использовать технологию Windows Communication Foundation (WCF), а не модель DCOM, из соображений безопасности. В этом разделе описывается, как перенести код из DCOM в WCF в перечисленных ниже ситуациях.
Удаленная служба возвращает клиенту объект по значению.
Клиент отправляет удаленной службе объект по значению.
Удаленная служба возвращает клиенту объект по ссылке.
По соображениям безопасности отправка объекта по ссылке от клиента службе в WCF запрещена. Сценарий, при котором клиенту и серверу необходимо обмениваться сообщениями друг с другом, можно реализовать в WCF с помощью дуплексной службы. Дополнительные сведения о дуплексных службах см. в разделе Дуплексные службы.
Дополнительные сведения о создании служб WCF и клиентов для них см. в разделах Базовое программирование для WCF, Проектирование и реализация служб, а также Создание клиентов.
Пример кода DCOM
Для этих сценариев интерфейсы DCOM, проиллюстрированные с помощью WCF, имеют приведенную ниже структуру.
[ComVisible(true)]
[Guid("AA9C4CDB-55EA-4413-90D2-843F1A49E6E6")]
public interface IRemoteService
{
Customer GetObjectByValue();
IRemoteObject GetObjectByReference();
void SendObjectByValue(Customer customer);
}
[ComVisible(true)]
[Guid("A12C98DE-B6A1-463D-8C24-81E4BBC4351B")]
public interface IRemoteObject
{
}
public class Customer
{
}
Служба возвращает объект по значению
В этом сценарии выполняется вызов службы, а ее метод возвращает объект, который передается по значению с сервера клиенту. Такой сценарий представляет приведенный ниже вызов COM.
public interface IRemoteService
{
Customer GetObjectByValue();
}
В этом случае клиент получает из удаленной службы десериализованную копию объекта. Клиент может взаимодействовать с этой локальной копией, не выполняя обратные вызовы в службу. Иными словами, клиент получает гарантию, что при вызове методов применительно к локальной копии служба не будет задействована никоим образом. WCF всегда возвращает объекты из службы по значению, поэтому ниже описывается создание стандартной службы WCF.
Шаг 1. Определение интерфейса службы WCF
Определите открытый интерфейс для службы WCF и пометьте его атрибутом [ServiceContractAttribute]. Пометьте атрибутом [OperationContractAttribute] методы, к которым необходимо предоставить доступ клиентам. В примере ниже показано использование этих атрибутов для определения интерфейса на стороне сервера и методов интерфейса, которые может вызывать клиент. Метод, используемый в этом сценарии, выделен полужирным шрифтом.
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
. . .
[ServiceContract]
public interface ICustomerManager
{
[OperationContract]
void StoreCustomer(Customer customer);
[OperationContract] Customer GetCustomer(string firstName, string lastName);
}
Шаг 2. Определение контракта данных
Далее следует создать для службы контракт данных, описывающий то, как будет происходить обмен данными между службой и клиентами. Классы, описываемые в контракте данных, следует пометить атрибутом [DataContractAttribute]. Отдельные свойства или поля, которые должны быть видны и для клиента, и для сервера, отмечаются атрибутом [DataMemberAttribute]. Чтобы разрешить использование типов, производных от класса, в контракте данных, их следует указать с помощью атрибута [KnownTypeAttribute]. WCF сериализует и десериализует только те типы, которые входят в интерфейс службы или определены как известные. При попытке использовать неизвестный тип возникнет исключение.
Дополнительные сведения о контрактах данных см. в разделе Контракты данных.
[DataContract]
[KnownType(typeof(PremiumCustomer))]
public class Customer
{
[DataMember]
public string Firstname;
[DataMember]
public string Lastname;
[DataMember]
public Address DefaultDeliveryAddress;
[DataMember]
public Address DefaultBillingAddress;
}
[DataContract]
public class PremiumCustomer : Customer
{
[DataMember]
public int AccountID;
}
[DataContract]
public class Address
{
[DataMember]
public string Street;
[DataMember]
public string Zipcode;
[DataMember]
public string City;
[DataMember]
public string State;
[DataMember]
public string Country;
}
Шаг 3. Реализация службы WCF
Далее следует реализовать класс службы WCF, который реализует интерфейс, определенный в предыдущем шаге.
public class CustomerService: ICustomerManager
{
public void StoreCustomer(Customer customer)
{
// write to a database
}
public Customer GetCustomer(string firstName, string lastName)
{
// read from a database
}
}
Шаг 4. Настройка службы и клиента
Для запуска службы WCF необходимо объявить конечную точку, предоставляющую доступ к интерфейсу службы по определенному URL-адресу с помощью определенной привязки WCF. Привязка определяет транспорт, кодировку и протокол, используемые для обмена данными между клиентами и сервером. Привязки обычно добавляются в файл конфигурации проекта службы (web.config). Ниже приведен пример записи привязки для службы.
<configuration>
<system.serviceModel>
<services>
<service name="Server.CustomerService">
<endpoint address="http://localhost:8083/CustomerManager"
binding="basicHttpBinding"
contract="Shared.ICustomerManager" />
</service>
</services>
</system.serviceModel>
</configuration>
Затем нужно настроить клиент в соответствии с информацией о привязке, определенной в службе. Для этого добавьте в файл конфигурации клиентского приложения (app.config) приведенный ниже код.
<configuration>
<system.serviceModel>
<client>
<endpoint name="customermanager"
address="http://localhost:8083/CustomerManager"
binding="basicHttpBinding"
contract="Shared.ICustomerManager"/>
</client>
</system.serviceModel>
</configuration>
Шаг 5. Запуск службы
Наконец, вы можете произвести резидентное размещение в консольном приложении, добавив в приложение службы приведенные ниже строки и запустив его. Дополнительные сведения о других способах размещения приложения службы WCF см. в разделе Размещение служб.
ServiceHost customerServiceHost = new ServiceHost(typeof(CustomerService));
customerServiceHost.Open();
Шаг 6. Вызов службы из клиента
Для вызова службы из клиента нужно создать для нее фабрику каналов, а затем запросить канал, чтобы получить возможность вызывать метод GetCustomer
непосредственно из клиента. Канал реализует интерфейс службы и отвечает за базовую логику запросов и ответов. Возвращаемое значение метода представляет собой десериализованную копию ответа службы.
ChannelFactory<ICustomerManager> factory =
new ChannelFactory<ICustomerManager>("customermanager");
ICustomerManager service = factory.CreateChannel();
Customer customer = service.GetCustomer("Mary", "Smith");
Клиент отправляет серверу объект по значению
В этом случае клиент отправляет серверу объект по значению. Это означает, что сервер получит десериализованную копию объекта. Сервер может вызывать методы применительно к этой копии с гарантией того, что обратные вызовы кода клиента выполняться не будут. Как было замечено ранее, обмен данными с WCF обычно происходит по значению. Это гарантирует, что вызовы методов объектов выполняются только локально без вызова кода клиента.
Такой сценарий представляет приведенный ниже вызов метода COM.
public interface IRemoteService
{
void SendObjectByValue(Customer customer);
}
В этом сценарии используется тот же интерфейс службы и тот же контракт данных, что и в первом примере. Кроме того, клиент и служба настраиваются точно так же. В этом примере для отправки объекта создается и запускается канал аналогичным образом. Однако в этом примере вы создадите клиент, который вызывает службу, передавая объект по значению. Метод службы, который клиент вызывает в контракте службы, выделен полужирным шрифтом.
[ServiceContract]
public interface ICustomerManager
{
[OperationContract] void StoreCustomer(Customer customer);
[OperationContract]
Customer GetCustomer(string firstName, string lastName);
}
Добавление в клиент кода для отправки объекта по значению
В приведенном ниже коде показано, как клиент создает объект customer для передачи по значению, создает канал для обмена данными со службой ICustomerManager
и отправляет ей объект customer.
Объект customer будет сериализован и отправлен в службу, где он десериализуется в новую копию этого объекта. Любые методы этого объекта, вызываемые службой, будут выполняться только локально на сервере. Важно отметить, что этот код иллюстрирует отправку производного типа (PremiumCustomer
). Контракт службы ожидает объекта Customer
, но в контракте данных службы используется атрибут [KnownTypeAttribute], указывающий, что объект PremiumCustomer
также допустим. Попытка сериализации или десериализации любого другого типа посредством этого интерфейса службы WCF завершится ошибкой.
PremiumCustomer customer = new PremiumCustomer();
customer.Firstname = "John";
customer.Lastname = "Doe";
customer.DefaultBillingAddress = new Address();
customer.DefaultBillingAddress.Street = "One Microsoft Way";
customer.DefaultDeliveryAddress = customer.DefaultBillingAddress;
customer.AccountID = 42;
ChannelFactory<ICustomerManager> factory =
new ChannelFactory<ICustomerManager>("customermanager");
ICustomerManager customerManager = factory.CreateChannel();
customerManager.StoreCustomer(customer);
Служба возвращает объект по ссылке
В этом случае клиентское приложение вызывает удаленную службу, и метод возвращает объект, который передается по ссылке из службы в клиент.
Как было замечено ранее, службы WCF всегда возвращают объекты по значению. Однако вы можете получить требуемый результат с помощью класса EndpointAddress10. EndpointAddress10 — это сериализуемый объект, передаваемый по значению, с помощью которого клиент может получить на сервере объект, переданный по ссылке в рамках сеанса.
Поведение переданного по ссылке объекта в WCF, которое показано в этом сценарии, отличается от его поведения в DCOM. В DCOM сервер может вернуть клиенту объект по ссылке напрямую, и клиент может вызывать методы этого объекта, которые выполняются на сервере. Однако в WCF объекты всегда возвращаются по значению. Клиент должен получить объект по значению, представленный EndpointAddress10, и с помощью него создать собственный объект, полученный по ссылке в рамках сеанса. Методы объекта сеанса, вызываемые клиентом, выполняются на сервере. Иными словами, этот полученный по ссылке объект в WCF является обычным объектом WCF, настроенным как объект сеанса.
В WCF сеанс — это способ согласования нескольких сообщений, пересылаемых между двумя конечными точками. Это означает, что, как только клиент устанавливает подключение к службе, между ним и сервером создается сеанс. Клиент использует единственный уникальный экземпляр объекта на стороне сервера для любых взаимодействий в рамках данного сеанса. Контракты сеансов WCF аналогичны схемам запросов и ответов, ориентированным на подключения.
Этот сценарий представлен приведенным ниже методом DCOM.
public interface IRemoteService
{
IRemoteObject GetObjectByReference();
}
Шаг 1. Определение интерфейса сеанса службы WCF и его реализация
Сначала определите интерфейс службы WCF, содержащий объект сеанса.
В этом коде объект сеанса помечен атрибутом ServiceContract
, который определяет его как обычный интерфейс службы WCF. Кроме того, свойству SessionMode присвоено значение, указывающее, что это будет служба сеанса.
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface ISessionBoundObject
{
[OperationContract]
string GetCurrentValue();
[OperationContract]
void SetCurrentValue(string value);
}
В приведенном ниже коде показана реализация службы.
Служба помечена атрибутом [ServiceBehavior], а ее свойству InstanceContextMode присвоено значение InstanceContextMode.PerSessions, указывающее, что для каждого сеанса должен создаваться уникальный экземпляр этого типа.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MySessionBoundObject : ISessionBoundObject
{
private string _value;
public string GetCurrentValue()
{
return _value;
}
public void SetCurrentValue(string val)
{
_value = val;
}
}
Шаг 2. Определение службы фабрики WCF для объекта сеанса
Необходимо определить и реализовать службу, создающую объект сеанса. В следующем примере кода показано, как это сделать: Этот код создает еще одну службу WCF, которая возвращает объект EndpointAddress10. Это сериализуемая форма конечной точки, с помощью которой можно создать объект сеанса.
[ServiceContract]
public interface ISessionBoundFactory
{
[OperationContract]
EndpointAddress10 GetInstanceAddress();
}
Далее показана реализация этой службы. Эта реализация поддерживает единственную фабрику каналов для создания объектов, связанных с сеансами. При вызове метода GetInstanceAddress
создается канал и объект EndpointAddress10, указывающий на удаленный адрес, связанный с этим каналом. EndpointAddress10 — это тип данных, который можно вернуть клиенту по значению.
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);
}
}
Шаг 3. Настройка и запуск служб WCF
Для размещения этих служб необходимо добавить указанные ниже записи в файл конфигурации сервера (web.config).
Добавьте раздел
<client>
, который описывает конечную точку для объекта сеанса. В этом сценарии сервер также выступает в роли клиента, и его нужно настроить соответствующим образом.В разделе
<services>
объявите конечные точки службы для фабрики и объекта сеанса. Это позволит клиенту связаться с конечными точками службы, получить адрес EndpointAddress10 и создать канал сеанса.
Далее приведен пример файла конфигурации с этими параметрами:
<configuration>
<system.serviceModel>
<client>
<endpoint name="sessionbound"
address="net.tcp://localhost:8081/SessionBoundObject"
binding="netTcpBinding"
contract="Shared.ISessionBoundObject"/>
</client>
<services>
<service name="Server.MySessionBoundObject">
<endpoint address="net.tcp://localhost:8081/SessionBoundObject"
binding="netTcpBinding"
contract="Shared.ISessionBoundObject" />
</service>
<service name="Server.SessionBoundFactory">
<endpoint address="net.tcp://localhost:8081/SessionBoundFactory"
binding="netTcpBinding"
contract="Shared.ISessionBoundFactory" />
</service>
</services>
</system.serviceModel>
</configuration>
Добавьте приведенные ниже строки в консольное приложение для резидентного размещения службы и запустите приложение.
ServiceHost factoryHost = new ServiceHost(typeof(SessionBoundFactory));
factoryHost.Open();
ServiceHost sessionBoundServiceHost = new ServiceHost(
typeof(MySessionBoundObject));
sessionBoundServiceHost.Open();
Шаг 4. Настройка клиента и вызов службы
Настройте клиент так, чтобы он мог связываться со службами WCF, добавив указанные ниже записи в файл конфигурации приложения проекта (app.config).
<configuration>
<system.serviceModel>
<client>
<endpoint name="sessionbound"
address="net.tcp://localhost:8081/SessionBoundObject"
binding="netTcpBinding"
contract="Shared.ISessionBoundObject"/>
<endpoint name="factory"
address="net.tcp://localhost:8081/SessionBoundFactory"
binding="netTcpBinding"
contract="Shared.ISessionBoundFactory"/>
</client>
</system.serviceModel>
</configuration>
Для вызова службы добавьте в клиент код, выполняющий следующие задачи:
создание канала для связи со службой
ISessionBoundFactory
;использование канала для вызова службы
ISessionBoundFactory
и получения объекта EndpointAddress10;создание канала для получения объекта сеанса с помощью адреса EndpointAddress10;
вызов методов
SetCurrentValue
иGetCurrentValue
для демонстрации того, что в рамках нескольких вызовов используется один и тот же экземпляр объекта.
ChannelFactory<ISessionBoundFactory> factory =
new ChannelFactory<ISessionBoundFactory>("factory");
ISessionBoundFactory sessionBoundFactory = factory.CreateChannel();
EndpointAddress10 address = sessionBoundFactory.GetInstanceAddress();
ChannelFactory<ISessionBoundObject> sessionBoundObjectFactory =
new ChannelFactory<ISessionBoundObject>(
new NetTcpBinding(),
address.ToEndpointAddress());
ISessionBoundObject sessionBoundObject =
sessionBoundObjectFactory.CreateChannel();
sessionBoundObject.SetCurrentValue("Hello");
if (sessionBoundObject.GetCurrentValue() == "Hello")
{
Console.WriteLine("Session-full instance management works as expected");
}