【问题标题】:Serializing a Dynamic Type in Web API在 Web API 中序列化动态类型
【发布时间】:2012-11-30 05:08:18
【问题描述】:

我正在尝试创建一个通用函数,当给定枚举类型时,它将返回一个对象,当被 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() );
}

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

有什么想法吗?

【问题讨论】:

  • 能否包含在返回 XML 时重现此错误的 web api 方法?我想我知道问题出在哪里,但我需要看看您是如何尝试将此枚举作为内容返回的。
  • @AndrasZoltan 使用示例 Web API 方法编辑了我的原始问题
  • 正如我所怀疑的那样 - 将对象作为 object 传递到内容中 - 我可以从接受中看到我建议的解决方案有效:)
  • @AndrasZoltan 确实做到了 - 很棒的解决方案,完美运行 - 谢谢!

标签: c# reflection asp.net-web-api


【解决方案1】:

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

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

从表面上看,SetSerializer(type, serializer) 方法似乎应该可行,但是,我敢打赌,您将动态类型作为第一个参数调用它 - 如果您 将枚举作为object 发送出去 - 因为它是XmlRequestFormatter 将使用的object 序列化程序。

这是一个众所周知的问题 - 还有一个 which I've reported as an issue on 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);

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

[XmlUseReturnedUnstanceType]
public object Get()
{

}

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-20
    相关资源
    最近更新 更多