背景
WCF を使用して C#.NET Web アプリケーション用の REST API を開発しています。XML 形式をより詳細に制御するために、デフォルトの DataContractSerializer ではなく XmlSerializer を使用するように構成しました。汎用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 メタデータの整合性を維持する必要もあります。つまり、リフレクションを使用した動的な変更はありません。
試みられた解決策
<Api>
ルート要素とその属性は完全に汎用的であるため ( で) 、XML 属性を宣言的に変更することはできませんResponseContract
。リフレクションを使用して実行時に属性の名前空間を変更しても (たとえば、「http://example.com/api/Items/GetItemList」)、効果はありません。属性を取得することは可能ですが、属性を変更しても効果はありません。とにかく、これは WSDL を壊します。
IXmlSerializable を実装する場合、ライターはが呼び出された
<Api>
ときに既に開始タグの後に配置されています。の子ノードのWriteXml()
シリアライゼーションをオーバーライドすることだけが可能であり、いずれにしても問題はありません。メソッドが呼び出される<Api>
前に例外がスローされるため、これはとにかく機能しません。IXmlSerializable
typeof()
名前空間は定数でなければならないため、定数名前空間を一意にするために、または同様に連結しても機能しません。デフォルトで
DataContractSerializer
は名前に型名を挿入できますが ( のように<ApiOfIdeaList>
)、DataContractSerializer
の出力は肥大化して判読できず、属性が不足しているため、外部の再利用者には適していません。XmlRootAttribute
名前空間を別の方法で生成するように拡張します。残念ながら、呼び出されたときに利用できる型情報はなく、一般的なResponseContract
データのみです。この問題を回避するためにランダムな名前空間を生成することは可能ですが、スキーマを動的に変更すると WSDL メタデータが壊れます。ラッパー コントラクトの代わりに基本クラスを作成
ResponseContract
しても機能するはずですが、多くの汎用データが重複することになります。たとえば、上記の例では、<Pages>
and<Item>
もコントラクトであり、独自の同等の<Api>
and<Response>
要素があります。
結論
何か案は?