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, что означает отсутствие динамических изменений с использованием отражения.
Попытки решения
Декларативное изменение атрибутов XML невозможно, так как
<Api>
корневой элемент и его атрибуты являются полностью общими (вResponseContract
).Изменение пространства имен атрибута во время выполнения с помощью отражения (например, "http://example.com/api/Items/GetItemList") не имеет никакого эффекта. Можно получить атрибуты, но изменения к ним не имеют никакого эффекта. Это сломало бы WSDL в любом случае.
При реализации IXmlSerializable писатель уже позиционируется после
<Api>
начать тег, когдаWriteXml()
вызывается. Можно только переопределить сериализацию<Api>
это дочерние узлы, которые в любом случае не вызывают проблем. Это не сработает, так как исключение выдается передIXmlSerializable
методы называются.Конкатенация пространства имен с константой
typeof()
или подобное, чтобы сделать его уникальным, не работает, потому что пространство имен должно быть константой.По умолчанию
DataContractSerializer
может вставлять имена типов в имя (например,<ApiOfIdeaList>
), ноDataContractSerializer
Выходные данные являются раздутыми и нечитаемыми, и в них отсутствуют атрибуты, что невозможно для внешних пользователей.простирающийся
XmlRootAttribute
генерировать пространство имен по-разному. К сожалению, нет информации о типе, когда он вызывается, только общийResponseContract
данные. Можно создать случайное пространство имен, чтобы обойти проблему, но динамическое изменение схемы нарушает метаданные WSDL.Изготовление
ResponseContract
базовый класс вместо контракта-оболочки должен работать, но это приведет к большому количеству дублированных общих данных. Например,<Pages>
а также<Item>
в приведенном выше примере также контракты, которые будут иметь свой собственный эквивалент<Api>
а также<Response>
элементы.
Заключение
Есть идеи?
1 ответ
У меня есть значок для этого!
Я отказался от описанного подхода, потому что не мог найти жизнеспособное решение. Вместо этого каждый контракт наследует QueryStatus<TErrorCode>
свойство от общего BaseContract<TContract, TErrorCode>
, Это свойство заполняется автоматически для основного договора, и null
для субподрядов.