XmlSerializer を使用した WCF:ジェネリックコントラクトを返すときの名前空間の衝突
-
26-09-2019 - |
質問
背景
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メタデータ 整合性。これは、リフレクションを使用した動的な変更がないことを意味します。
試みられた解決策
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>
要素。
結論
何か案は?
解決
これのタンブルウィードバッジを手に入れました!
実行可能な解決策が見つからなかったため、説明されているアプローチを放棄しました。代わりに、すべてのコントラクトは null 許容値を継承します。 QueryStatus<TErrorCode>
ジェネリックからのプロパティ BaseContract<TContract, TErrorCode>
. 。このプロパティは主契約に対して自動的に設定されます。 null
下請け向け。