WCF с XmlSerializer: конфликт пространства имен при возврате общих контрактов

Фон

Я разрабатываю 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> (который устанавливает name & namespace) является общим и возвращается всеми методами 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 ответ

Решение

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

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

Другие вопросы по тегам