среда, 2 сентября 2009 г.

Известные типы в WCF. Часть 4

Задание обобщенных известных типов

Обобщенные типы (generic types) играют важную роль при программировании на платформе .Net, но их применение весьма ограничено, когда речь заходит о разработке контрактов сервисов. Это связано с тем, что обобщенные типы относятся к специфике платформы .Net, а одной из основных парадигм сервис-ориентированного программирования является публикация контракта и сокрытие деталей реализации.

Хотя применение обобщенных методов в качестве контракта сервиса невозможно, применение «закрытых» (closed) обобщенных типов вполне допустимо, если аргумент типа является допустимым в качестве контракта данных.

ПРИМЕЧАНИЕ
Закрытым обобщенным типом (closed generic type) называется обобщенный тип, которому всем аргументам-типам переданы действительные типы данных. Таким образом, List является открытым типом, а List - закрытым.

[DataContract]
public class Shape<T> { ... }
 
[DataContract]
public class Circle<T> : Shape<T> { ... }
 
[DataContract]
public class Square<T> : Shape<T> { ... }
 
[ServiceContract]
public interface IService
{
    [OperationContract]
    void ProcessShapeOfInt(Shape<int> shape); //Вариант 1
 
    [OperationContract]
    void ProcessShape<T>(Shape<T> shape); //Вариант 2
 
    ...
}

Применение операции ProcessShapOfInt вполне допустимо, в то время, как применение операции ProcessShape приведет к следующей ошибке:

ERROR: System.Runtime.Serialization.InvalidDataContractException: Невозможно экспортировать тип "Server.Shape`1[T]" как тип схемы, так как он является открытым базовым типом. Базовый тип можно экспортировать, только если все типы его базовых параметров являются реальными типами.

ПРИМЕЧАНИЕ
Возможность применения закрытых типов обусловлено тем, что при импортировании метаданных контракта все закрытые обобщенные параметры переименовываются согласно следующей схемы: <исходное имя>of<имена параметров типов><хеш>. Таким образом метаданные службы не содержат информации об обобщенных параметрах и не нарушают принципы сервис-ориентированного программирования.

Как и применение обобщенных типов в операциях контракта, добавление обобщенных типов в список известных типов имеет ряд особенностей.

Рассмотрим следующий код:

[DataContract]
[KnownType(typeof(Circle<>))]
[KnownType(typeof(Square<>))]
public class Shape<T> { ... }

Хотя приведенный код компилируется и запускается, при обновление метаданных с помощью Metadata Exchange Endpoint будет невозможно. При попытке обновления метаданных с помощью Microsoft Visual Studio вы получите ошибку: «Метаданные содержат неразрешимую ссылку: “net.tcp://localhost:6101/WCFTestService/mex”». Чтобы понять точнее, что же не так с метаданными, необходимо воспользоваться утилитой SvcUtil.exe. В этом случае ошибка будет более понятной:

Ошибка при получении известных типов для типа "Server.Square`1[T]". Этот тип не должен быть открытым или частичным базовым классом.

Для задания обобщенных известных типов с помощью атрибута KnownType необходимо воспользоваться версией конструктора, принимающей имя метода:

[DataContract]
[KnownType("GetKnownTypes")]
public class Shape<T>
{
    ...
    static IEnumerable<Type> GetKnownTypes()
    {
        return new [] { typeof(Circle<T>), typeof(Square<T>) };
    }
}


Еще одним способом задания обобщенных известных типов является применение конфигурационного файла приложения следующим образом:

<system.runtime.serialization>
  <dataContractSerializer>
    <declaredTypes>
      <add type = "Server.Shape2`1, Server">
        <knownType type = "Server.Circle2`1, Server">
          <parameter index="0"/>
        </knownType>
        <knownType type = "Server.Square2`1, Server">
          <parameter index="0"/>
        </knownType>
      </add>
    </declaredTypes>
  </dataContractSerializer>
</system.runtime.serialization>

Выводы

Разработчики WCF предусмотрели множество способов задания известных типов, каждый из которых имеет свои преимущества и недостатки. Для наиболее простых приложений самым простым способом является применение атрибута KnownTypeAttribute к базовому классу иерархии; для более сложных способов лучше воспользоваться атрибутом ServiceKnownTypeAttribute либо применять декларативный способ задания известных типов с помощью конфигурационного файла. Передача известных типов в конструкторе DataContractSerializer является наиболее трудоемкой, но позволяет не только задавать известные типы, но и перейти к NetDataContractSerializer в случае невозможности заранее определить динамические типы объектов, передаваемые в операциях контракта.

1 комментарий:

  1. Будет ли работать вариант когда сборка клиента уже включает в себя "контрактные типы" и при подключении сервиса (в VS2008) указанно использовать типы из подключенной сборки (общей для сервиса и клиента)?

    ОтветитьУдалить