C# · 12月 23, 2021

c# – 在Web API中序列化动态类型

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

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

System.Runtime.Serialization.SerializationException

Type ‘Priority’ with data contract name
‘Priority:http://schemas.datacontract.org/2004/07/’ is not expected.
Consider using a DataContractResolver or add any types not kNown
statically to the list of kNown types – for example,by using the
KNownTypeAttribute attribute or by adding them to the list of kNown
types passed to 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) );}

有任何想法吗?

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

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

从表面上看,似乎SetSerializer(类型,序列化器)方法应该可以工作,但是,我敢打赌你用动态类型作为第一个参数来调用它 – 如果你发送枚举,它将不起作用作为对象 – 因为它是XmlRequestFormatter将使用的对象序列化程序.

这是一个众所周知的问题 – 一个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专门用于运行时类型,而不是对象.

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