在Web API中序列化动态类型

dhe*_*man 6 c# reflection asp.net-web-api

我正在尝试创建一个泛型函数,当给定Enum Type时,它将返回一个对象,当WebApi序列化时,它将提供漂亮的输出作为XML/Json.

当序列化为JSON时,此方法可以正常工作,但我无法使用XML.如果我使用XmlSerializer或DataContractSerializer手动序列化返回的对象,我会得到预期的结果.当WebApi本身试图从HttpRequest序列化它时,我得到如下错误:

System.Runtime.Serialization.SerializationException

输入"优先级",数据合同名称为"优先级:http://schemas.datacontract.org/2004/07/",不是预期的.考虑使用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() );
}
Run Code Online (Sandbox Code Playgroud)

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) );
}
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

And*_*tan 8

我相信这最终是因为你发送枚举值作为object- 而不像Json格式化程序,Web API的xml格式化程序,它DataContractSerializer使用(实际上)被序列化的值的编译时类型,而不是运行时类型.

因此,您必须始终确保将您尝试序列化的任何派生类型的基础添加到基础序列化程序的已知类型中.在这种情况下,你有动态枚举(object当然是一个).

在它的面前,似乎SetSerializer(type, serializer)方法应该工作,但是,我敢打赌你与动态类型作为第一个参数调用它-如果你将无法正常工作进行发送枚举的object-因为它是object串行器该XmlRequestFormatter会使用.

这是一个众所周知的问题 - 我在Codeplex上报告了一个问题(那里有一个很好的例子,它在一个更简单的场景中演示了这个问题).

该问题还包括一些属性的C#代码和XmlMediaTypeFormatter(被调用的XmlMediaTypeFormatterEx)替换,它为这个问题提供了一种解决方案 - 它使用声明性的每操作方法.替换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);
Run Code Online (Sandbox Code Playgroud)

现在,在您想要返回此动态枚举的API方法上,您将使用以下内容对其进行装饰:

[XmlUseReturnedUnstanceType]
public object Get()
{

}
Run Code Online (Sandbox Code Playgroud)

现在,无论从Get方法返回什么类型,自定义格式化程序都会启动并DataContractSerializer专门用于运行时类型,而不是object.

这不会处理基数的可枚举或字典 - 它变得非常复杂 - 但对于基本的单实例返回值,它可以正常工作.