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 theIXmlSerializablemethods 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
DataContractSerializercan 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
XmlRootAttributeto generate the namespace differently. Unfortunately there is no type information available when it’s invoked, only the genericResponseContractdata. It’s possible to generate a random namespace to circumvent the problem, but dynamically changing the schema breaks the WSDL metadata. -
Making
ResponseContracta 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?
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, andnullfor subcontracts.