2012-11-30 9 views
6

Enum型を指定すると、WebApiでシリアル化されたときにXML/Jsonとして見栄えのよい出力が得られるオブジェクトを返すジェネリック関数を作成しようとしています。Web APIでの動的型のシリアライズ

このメソッドは、JSONとしてシリアル化すると完全に正常に動作しますが、XMLで動作させることができません。 XmlSerializerまたはDataContractSerializerのいずれかを使用して返されたオブジェクトを手動でシリアル化すると、期待どおりの結果が得られます。 WEBAPI自体はHttpRequestのからの一方でそれをシリアル化しようとすると、私は次のようなエラーが出る:データコントラクト名 「優先順位付き

System.Runtime.Serialization.SerializationException

タイプ「優先順位」 :http://schemas.datacontract.org/2004/07/ 'は期待されていません。 KnockTypeAttribute属性を使用するか、DataContractSerializerに渡される既知の 型のリストに追加するなど、DataContractResolverを使用するか、または既知の型のリストに静的には、 型を追加することを検討してください。

私はブレークポイントを設定から作品を知って発生したタイプにシリアライザを設定するGlobalConfiguration.Configuration.Formatters.XmlFormatter.SetSerializerを使用して試してみたが、ちょうどそれを無視しているようだと、同じ例外がスローされます。列挙型は整数によってサポートされ、各エントリに対して一意の値を持つことが保証されます。ここでは、型を生成し、その型を返すために使用しているコードを示します。

public object GetSerializableEnumProxy(Type enumType) { 

    if (enumType == null) { 
     throw new ArgumentNullException("enumType"); 
    } 

    if (!enumType.IsEnum) { 
     throw new InvalidOperationException(); 
    } 

    AssemblyName assemblyName = new AssemblyName("DataBuilderAssembly"); 
    AssemblyBuilder assemBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); 
    ModuleBuilder moduleBuilder = assemBuilder.DefineDynamicModule("DataBuilderModule"); 
    TypeBuilder typeBuilder = moduleBuilder.DefineType(enumType.Name, TypeAttributes.Class | TypeAttributes.Public); 

    // Add the [DataContract] attribute to our generated type 
    typeBuilder.SetCustomAttribute(
     new CustomAttributeBuilder(typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes), new object[] {}) 
    ); 

    CustomAttributeBuilder dataMemberAttributeBuilder = new CustomAttributeBuilder(
     typeof(DataMemberAttribute).GetConstructor(Type.EmptyTypes), new object[] {} 
    ); 

    // For each name in the enum, define a corresponding public int field 
    // with the [DataMember] attribute 
    foreach (var value in Enum.GetValues(enumType).Cast<int>()) { 
     var name = Enum.GetName(enumType, value); 

     var fb = typeBuilder.DefineField(name, typeof(int), FieldAttributes.Public); 

     // Add the [DataMember] attribute to the field 
     fb.SetCustomAttribute(dataMemberAttributeBuilder); 

     // Set the value of our field to be the corresponding value from the Enum 
     fb.SetConstant(value); 
    }  

    // Return an instance of our generated type 
    return Activator.CreateInstance(typeBuilder.CreateType()); 
} 

ウェブアピコントローラ方法:

private static IEnumerable<Type> RetrievableEnums = new Type[] { 
    typeof(Priority), typeof(Status) 
}; 

[GET("enum/{enumName}")] 
public HttpResponseMessage GetEnumInformation(string enumName) { 

    Type enumType = RetrievableEnums.SingleOrDefault(type => 
     String.Equals(type.Name, enumName, StringComparison.InvariantCultureIgnoreCase)); 

    if (enumType == null) { 
     return Request.CreateErrorResponse(HttpStatusCode.NotFound, "The requested enum could not be retrieved"); 
    } 

    return Request.CreateResponse(HttpStatusCode.OK, GetSerializableEnumProxy(enumType)); 
} 

任意のアイデア?

+0

XMLを返すときにこのエラーを再現するWeb APIメソッドを含めることはできますか?問題の内容を知っていると思いますが、コンテンツとしてこの列挙型を返す方法を確認する必要があります。 –

+0

@AndrasZoltan私の疑問に思ったWeb APIの例 – dherman

+0

を元の質問に編集しました - オブジェクトをコンテンツとしてオブジェクトに渡します - 私が提案した解決策がうまく機能していることを受け入れることができます:) –

答えて

8

これは、objectとしてenum値を送信していると思われます.Jsonフォーマッタとは異なり、DataContractSerializerを使用するWeb APIのxmlフォーマッタは、(実際には)値のコンパイル時の型を使用します。実行時の型ではなく直列化された型。

常にである必要があります。シリアル化しようとしている派生型のいずれかが、基礎となるシリアライザの既知の型に追加されていることを確認してください。この場合、動的列挙型(もちろんobject)があります。その顔には

SetSerializer(type, serializer)アプローチが動作するはずと思われる、しかし、私はあなたが最初の引数として動的タイプとそれを呼び出す賭ける - あなたがenumを送信している場合は動作しません。これobjectとして送信します。XmlRequestFormatterが使用するのはobjectシリアライザです。

これはよく知られている問題です。もう1つはwhich I've reported as an issue on codeplexです(より単純なシナリオで問題を示す良い例があります)。

この問題には、この問題の解決策を提供するXmlMediaTypeFormatterXmlMediaTypeFormatterExと呼ばれます)の属性と置換用のC#コードも含まれています。これは宣言的な操作ごとのアプローチを使用しています。コード内の1でXmlMediaTypeFormatterを交換してください - このような何か( - おそらく多少無意味にこのコードは、すでに定義されたXMLフォーマッタが存在しない場合を扱う注意してください)を使用して:

あなたのAPIメソッドの
var configuration = GlobalConfiguration.Configuration; 
var origXmlFormatter = configuration.Formatters.OfType<XmlMediaTypeFormatter>() 
         .SingleOrDefault(); 

XmlMediaTypeFormatterEx exXmlFormatter = new XmlMediaTypeFormatterEx(origXmlFormatter); 

if (origXmlFormatter != null) 
{ 
    configuration.Formatters.Insert(
     configuration.Formatters.IndexOf(origXmlFormatter), exXmlFormatter); 
    configuration.Formatters.Remove(origXmlFormatter); 
} 
else 
    configuration.Formatters.Add(exXmlFormatter); 

そして今を今

[XmlUseReturnedUnstanceType] 
public object Get() 
{ 

} 

、あなたがGetメソッドから返さどんなタイプ、カスタムフォーマッタキックでと実行時の型ではなく、objectのために特別にDataContractSerializerを使用しています:あなたはこれでそれを飾るだろう、このダイナミックな列挙型を返すようにしたいです。

これは、ベースの列挙型または辞書を処理しません。それは非常に複雑になりますが、基本的な単一インスタンスの戻り値では正常に動作します。