WCF с XmlSerializer: конфликт пространства имен при возврате общих контрактов - PullRequest
0 голосов
/ 01 октября 2010

фон

Я разрабатываю REST API для веб-приложения на C # .NET с использованием WCF. Я настроил его для использования XmlSerializer, а не его DataContractSerializer по умолчанию, для большего контроля над форматом XML. Я создал общий ResponseContract<TResponse, TErrorCode> контракт данных, который оборачивает ответ с <Api> и <Response> для общих данных, таких как состояние запроса, сообщения об ошибках и пространства имен. Пример метода:

ResponseContract<ItemListContract, ItemListErrorCode> GetItemList(...)

Пример ответа от вышеуказанного метода:

<?xml version="1.0" encoding="utf-8"?>
<Api xmlns="http://example.com/api/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <Response Status="OKAY" ErrorCode="OKAY" ErrorText="">
      <Data Template="ItemList">
         <Pages Template="Pagination" Size="10" Index="1" Count="13" Items="126" />
         <Items>
            <Item example="..." />
            <Item example="..." />
            <Item example="..." />
        </Items>
      </Data>
   </Response>
</Api>

Проблема

Это очень хорошо работает для сервисов, чьи методы имеют одинаковые общие типы ResponseContract. WCF или XmlSerializer ожидает, что каждый контракт будет иметь уникальное имя в своем пространстве имен, но теперь служба возвращает универсальный контракт с разными типами, имеющими одно и то же корневое имя XML:

ResponseContract<ItemListContract, ItemListErrorCode> GetItemList(...)
ResponseContract<ItemContract, ItemErrorCode> GetItem(...)

С результирующим исключением:

The top XML element 'Api' from namespace 'http://example.com/api/' references distinct types Company.Product.ApiServer.Contracts.ResponseContract`2[Company.Product.ApiServer.Contracts.Items.ItemListContract,Company.Product.ApiServer.Interfaces.Items.ItemListErrorCode] and Company.Product.ApiServer.Contracts.ResponseContract`2[Company.Product.ApiServer.Contracts.Items.ItemContract,Company.Product.ApiServer.Items.ItemErrorCode]. Use XML attributes to specify another XML name or namespace for the element or types.

Сервис должен разрешать разные типы возврата. Этого трудно достичь, потому что ResponseContract<TResponse, TErrorCode> (который задает имя и пространство имен) является общим и возвращается всеми методами API. Мне также нужно поддерживать целостность метаданных WSDL , что означает отсутствие динамических изменений при использовании отражения.

Попытки решения

  1. Декларативное изменение атрибутов XML невозможно, поскольку корневой элемент <Api> и его атрибуты являются полностью общими (в ResponseContract).

  2. Изменение пространства имен атрибута во время выполнения с использованием отражения (например, 'http://example.com/api/Items/GetItemList') не имеет никакого эффекта. Можно получить атрибутов, но изменения к ним не имеют никакого эффекта. Это в любом случае нарушило бы WSDL .

  3. При реализации IXmlSerializable модуль записи уже позиционируется после начального тега <Api>, когда вызывается WriteXml(). Можно только переопределить сериализацию дочерних узлов <Api>, которые в любом случае не вызывают проблем. Это не сработает, так как исключение выдается до вызова методов IXmlSerializable.

  4. Объединение пространства имен константы с typeof() или аналогичным, чтобы сделать его уникальным, не работает, потому что пространство имен должно быть константой.

  5. По умолчанию DataContractSerializer может вставлять имена типов в имя (например, <ApiOfIdeaList>), но вывод DataContractSerializer является раздутым, нечитаемым и не имеет атрибутов, что недопустимо для внешних повторных пользователей.

  6. Расширение XmlRootAttribute для генерации пространства имен по-другому. К сожалению, при вызове информация о типе отсутствует, только общие данные ResponseContract. Можно создать случайное пространство имен, чтобы обойти проблему, но динамическое изменение схемы нарушает метаданные WSDL.

  7. Создание ResponseContract базового класса вместо контракта-оболочки должно работать, но приведет к большому количеству дублированных общих данных. Например, <Pages> и <Item> в приведенном выше примере также являются контрактами, которые имеют свои собственные эквивалентные элементы <Api> и <Response>.

Заключение

Есть идеи?

1 Ответ

0 голосов
/ 13 октября 2010

У меня есть значок для этого!

Я отказался от описанного подхода, потому что не смог найти жизнеспособное решение.Вместо этого каждый контракт наследует свойство nullable QueryStatus<TErrorCode> от универсального BaseContract<TContract, TErrorCode>.Это свойство заполняется автоматически для основного контракта и null для субконтрактов.

...