6

私は、列挙型を指定すると、WebApi によってシリアル化されたときに XML/Json として見栄えの良い出力を提供するオブジェクトを返す汎用関数を作成しようとしています。

このメソッドは、JSON としてシリアル化すると問題なく動作しますが、XML では動作しません。返されたオブジェクトを XmlSerializer または DataContractSerializer を使用して手動でシリアル化すると、期待どおりの結果が得られます。一方、WebApi 自体が HttpRequest からシリアル化しようとすると、次のようなエラーが発生します。

System.Runtime.Serialization.SerializationException

データ コントラクト名が 'Priority:http://schemas.datacontract.org/2004/07/' のタイプ 'Priority' は想定されていません。DataContractResolver の使用を検討するか、既知の型のリストに静的に認識されていない型を追加します。たとえば、KnownTypeAttribute 属性を使用するか、DataContractSerializer に渡される既知の型のリストにそれらを追加します。

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() );
}

Web API コントローラーの方法:

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) );
}

何か案は?

4

1 に答える 1

8

これは最終的には、列挙型の値をobject- として送信しているためだと思います。また、Json フォーマッタとは異なり、を使用する Web API の xml フォーマッタはDataContractSerializer、実行時型ではなく、シリアル化される値のコンパイル時型を (事実上) 使用します。 .

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

一見すると、このアプローチは機能するはずですが、最初の引数として動的型を使用して呼び出すことは間違いありません-列挙型を送信しているSetSerializer(type, serializer)場合は機能しません-これはシリアライザーobjectであるためですobjectXmlRequestFormatter使用します。

これはよく知られた問題であり、私が codeplex の問題として報告した問題です(より単純なシナリオで問題を示す良い例があります)。

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

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);

そして、この動的列挙型を返したい API メソッドでは、次のように装飾します。

[XmlUseReturnedUnstanceType]
public object Get()
{

}

これで、メソッドから返された型が何であれGet、カスタム フォーマッタが起動し、DataContractSerializerではなくランタイム型専用の を使用しますobject

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

于 2012-12-17T10:43:47.697 に答える