WCF 与 XmlSerializer:返回通用协定时出现命名空间冲突
背景
我正在使用 WCF 为 C#.NET Web 应用程序开发 REST API。我将其配置为使用 XmlSerializer 而不是其默认的 DataContractSerializer,以便更好地控制 XML 格式。我创建了一个通用的 ResponseContract
数据协定,它使用
和
包装响应以实现通用请求状态、错误消息和命名空间等数据。一个示例方法:
ResponseContract
; 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
(设置名称和命名空间)是通用的并且由所有 API 方法返回。我还需要维护 WSDL 元数据 完整性,这意味着不会使用反射进行动态更改。
尝试的解决方案
不可能以声明方式更改 XML 属性,因为
根元素及其属性是完全通用的(在ResponseContract
中)。使用反射在运行时更改属性命名空间(例如“http://example.com/api/Items/GetItemList”)没有任何效果。可以获取属性,但对它们的更改没有效果。无论如何,这都会破坏 WSDL。
实现 IXmlSerialized 时,调用
WriteXml()
时,编写器已位于
开始标记之后。只能覆盖
子节点的序列化,无论如何都不会造成问题。无论如何,这不起作用,因为在调用IXmlSerialized
方法之前抛出异常。使用
typeof()
或类似方法连接常量命名空间以使其唯一是行不通的,因为命名空间必须是常量。默认的
DataContractSerializer
可以在名称中插入类型名称(如
),但是DataContractSerializer
的输出臃肿且不可读且缺少属性,这对于外部重用者来说是不可行的。扩展
XmlRootAttribute
以不同方式生成命名空间。不幸的是,调用时没有可用的类型信息,只有通用的 ResponseContract 数据。可以生成随机命名空间来规避该问题,但动态更改架构会破坏 WSDL 元数据。将 ResponseContract 设为基类而不是包装器契约应该可行,但会导致大量重复的通用数据。例如,上例中的
和
也是合约,它们有自己的等效
> 和
元素。
结论
有什么想法吗?
The background
I'm developing a REST API for a C#.NET web application using WCF. I configured it to use the XmlSerializer rather than its default DataContractSerializer, for greater control over the XML format. I created a generic ResponseContract<TResponse, TErrorCode>
data contract, which wraps the response with <Api>
and <Response>
for generic data like request status, error messages, and namespaces. An example method:
ResponseContract<ItemListContract, ItemListErrorCode> GetItemList(...)
An example response from the above method:
<?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>
The problem
This works very well for services whose methods all have the same generic ResponseContract
types. WCF or XmlSerializer
expects each contract to have a unique name within its namespace, but the service is now returning a generic contract with different types having the same XML root name:
ResponseContract<ItemListContract, ItemListErrorCode> GetItemList(...)
ResponseContract<ItemContract, ItemErrorCode> GetItem(...)
With the resulting exception:
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.
The service must allow different return types. This is difficult to achieve because the ResponseContract<TResponse, TErrorCode>
(which sets the name & namespace) is generic and returned by all API methods. I also need to maintain WSDL metadata integrity, which means no dynamic changes using reflection.
Attempted solutions
Changing the XML attributes declaratively is not possible, since the
<Api>
root element and its attributes are completely generic (inResponseContract
).Changing the attribute namespace at runtime using reflection (eg, 'http://example.com/api/Items/GetItemList') has no effect. It's possible to get attributes, but changes to them have no effect. This would break WSDL anyway.
When implementing IXmlSerializable, the writer is already positioned after the
<Api>
start tag whenWriteXml()
is invoked. It's only possible to override the serialization of<Api>
's child nodes, which cause no problem anyway. This wouldn't work anyway, since the exception is thrown before theIXmlSerializable
methods are called.Concatenating the constant namespace with
typeof()
or similar to make it unique doesn't work, because the namespace must be a constant.The default
DataContractSerializer
can insert type names into the name (like<ApiOfIdeaList>
), butDataContractSerializer
's output is bloated and unreadable and lacks attributes, which is not feasible for the external reusers.Extending
XmlRootAttribute
to generate the namespace differently. Unfortunately there is no type information available when it's invoked, only the genericResponseContract
data. It's possible to generate a random namespace to circumvent the problem, but dynamically changing the schema breaks the WSDL metadata.Making
ResponseContract
a base class instead of a wrapper contract should work, but would result in a lot of duplicated generic data. For example,<Pages>
and<Item>
in the example above are also contracts, which would have their own equivalent<Api>
and<Response>
elements.
Conclusion
Any ideas?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我为此获得了风滚草徽章!
我放弃了所描述的方法,因为我找不到可行的解决方案。相反,每个合约都会从通用
BaseContract
继承可为 null 的QueryStatus
属性。对于主合约,此属性会自动填充;对于子合约,此属性会自动填充为null
。I got the tumbleweed badge for this one!
I abandoned the described approach because I couldn't find a viable solution. Instead, every contract inherits a nullable
QueryStatus<TErrorCode>
property from the genericBaseContract<TContract, TErrorCode>
. This property is populated automatically for the main contract, andnull
for subcontracts.