Автор: Sergey Teplyakov
Инфраструктура WCF требует четкого соблюдения контракта между клиентом и сервисом, что запрещает применение типов, чей статический и динамический типы не совпадают. Причем статический и динамический типы должны совпадать не только для аргументов и возвращаемых значений, но и для всех полей и свойств всех объектов, принимающих участие в операциях контрактов данных. Это требование обусловлено тем, что сервис и клиент должны четко следовать установленному протоколу взаимодействия и любое нарушение этого контракта недопустимо.
Частично решить эту проблему можно путем задания известных типов (Known Types), причем сделать это можно самым различным способом (я описывал все эти способы в цикле статей Известные типы в WCF, часть 1, часть 2, часть 3 и часть 4). Но этот вариант не всегда является удобным решением. Приложение может перейти на WCF с .Net Remoting, в котором используется механизм форматеров для сериализации/десериализации и существует возможность передавать между клиентом и сервером любые сериализируемые сущности. Кроме того, даже в новом приложении, использующем WCF как на стороне сервиса, так и на стороне клиента, могут применяться сложные объекты в качестве аргументов и возвращаемых значений операций контракта. И хотя это не отвечает принципам сервис-ориентированного программирования, разработчики могут сознательно пойти на этот шаг, зная, что их приложение не предусматривает разработку клиентских частей с использованием других технологий.
Невозможность передачи полиморфных объектов связано с тем, что по умолчанию инфраструктура WCF использует DataContractSerializer для сериализации/десериализации объектов. Основной особенностью этого сериализатора является то, что информация о типе не сохраняется при сериализации, а берется из контракта операции. Это вполне естественное поведение, ведь WCF это реализация промышленного стандарта по созданию сервис-ориентированных приложений, а такие приложения являются нейтральными к технологии, на которых создается сервис или его клиент, а тип параметра относится к специфике CLR, а значит является деталью реализации.
Кроме DataContractSerializer в WCF предусмотрена возможность применения других типов сериализаторов, таких как старый добрый XmlSerializer, NetDataContractSerializer и DataContractJsonSerializer. XmlSerializer для своей работы требует, чтобы сериализируемый класс был публичным, имел конструктор по умолчанию и сериализирует вначале все открытые поля в порядке объявления, а затем свойства (read/write properties) также в порядке объявления. Для применения XmlSerializer вместо DataContractSerializer необходимо пометить отдельный метод сервиса, либо класс сервиса целиком атрибутом XmlSerializerFormatAttribute.
NetDataContractSerializer работает аналогично DataContractSerializer, и имеет прямую и обратную совместимость с DataContractSerializer, что позволяет сериализовать объект одним сериализатором, а десериализовать другим.
NetDataContractSerializer добавляет полную информацию о сериализируемом типе в поток данных, что позволяет загрузить необходимую сборку при десериализации и требует совместного использования типом между сервисом и клиентом. Совместное использование типов (sharing types) нарушает принципы сервис-ориентированного программирования, которые основаны на разделении контрактов (sharing contracts), поэтому разработчики WCF не предусмотрели простого способа использования NetDataContractSerializer в приложениях.
Для возможности использования NetDataContractSerializer c определенной операцией, контрактом или службой целиком, необходимо реализовать один из следующих интерфейсов IOperationBehavior, IContractBehavior и IServiceBehavior соответственно. Каждый из этих интерфейсов имеет одинаковую функциональность и сводится к замене поведения с типом DataContractSerializerOperationBehavior на объект собственного класса, возвращающий необходимый сериализатор. Наиболее простым способ применения является создание класса наследника от System.Attribute и применение созданного атрибута к операции, контракту или сервису целиком.
При использовании этого класса нужно помнить о двух вещах: во-первых, необходимо совместное использование типов между сервисом и клиентом, и во-вторых, необходимо изменять сгенерированный код на клиентской стороне и помечать атрибутами соответствующие методы или интерфейс целиком.