![](/img/trans.png)
[英].NET Core 3 Web API: [FromBody] how bind abstract class property
[英]Using Web API to deserialize into class with abstract property
我试图编写一组类来表示一个特别复杂的对象,并且在那些类中,我有一个属性设置为三个可能的派生类的基类(抽象)。 我正在设置一个ASP.NET Web API来处理序列化和反序列化,这意味着默认情况下,它将Json.NET用于JSON。 如何获得Web API以正确反序列化通过POST或PUT发送的JSON到正确的派生类中?
具有抽象成员的类看起来像这样(为了清楚起见,我包括Xml装饰器,因为它们对于使用XmlSerializer对xml进行反序列化非常有效)
[Serializable]
public class FormulaStructure {
[XmlElement("column", typeof(ColumnStructure))]
[XmlElement("function", typeof(FunctionStructure))]
[XmlElement("operand", typeof(OperandStructure))]
public AFormulaItemStructure FormulaItem;
}
抽象类非常基础:
[Serializable]
public abstract class AFormulaItemStructure { }
抽象类有三个派生类:
[Serializable]
public class ColumnStructure: AFormulaItemStructure {
[XmlAttribute("type")]
public string Type;
[XmlAttribute("field")]
public string Field;
[XmlAttribute("display")]
public string Display;
}
[Serializable]
public class FunctionStructure: AFormulaItemStructure {
[XmlAttribute("type")]
public string Type;
[XmlAttribute("name")]
public string Name;
[XmlElement("parameters")]
public string Parameters;
}
[Serializable]
public class OperandStructure: AFormulaItemStructure {
[XmlAttribute("type")]
public string Type;
[XmlElement("left")]
public string Left;
[XmlElement("right")]
public string Right;
}
当前,使用[DataContract]
属性,Json.NET格式化程序无法填充派生类,而将属性保留为null
。
我可以将 XmlSerializer
属性与 同一类上的 DataContractSerializer
属性 混合 吗? 我使用XmlSerializer
是因为我在设计的xml中使用xml属性,但是由于我自己在开发xml模式,因此可以根据需要更改该属性。
Json.NET中与 [KnownType()]
等效的东西是什么 ? Json.NET似乎不尊重KnownType
的DataContractSerializer
版本。 我是否需要滚动自己的JsonConverter以确定正确的类型?
如何装饰类,以便 DataContractSerializer
或 DataContractJsonSerializer
可以正确反序列化Xml和Json中的对象? 我的目标是将其放入ASP.NET Web API中,因此我希望能够灵活地生成Xml或Json,以适合所请求的类型。 如果Json.NET无法正常工作,我是否需要使用其他格式化程序来处理这个复杂的类?
我需要能够在客户端上生成对象,而不必将.NET类名包含在对象中。
在我对Web API的测试中,默认的序列化发送给客户端:
{"FormulaItem":{"type":"int","field":"my_field","display":"My Field"}}
这对我来说是理想的。 但是,将其返回给API并反序列化为正确的派生类型是行不通的(它会为该属性生成null)。
测试Tommy Grovnes的答案如下,他用于测试的DataContractSerializer
生成:
{"FormulaItem":{"__type":"column:#ExpressionStructureExperimentation.Models","display":"My Field","field":"my_field","type":"int"}}
这对我或代码可维护性都不起作用(如果我将整个名称空间硬编码到JavaScript中以生成这些对象,则重构将成为PITA)。
您可以按照前面提到的进行混合,但是我认为您不需要,我自己没有使用过WEB api,但是WCF Rest从DataContracts(没有Xml ..标签)生成xml和json,像这样标记您的类:
[DataContract]
public class FormulaStructure
{
[DataMember]
public AFormulaItemStructure FormulaItem;
}
[DataContract]
[KnownType(typeof(ColumnStructure))]
[KnownType(typeof(FunctionStructure))]
[KnownType(typeof(OperandStructure))]
public abstract class AFormulaItemStructure { }
[DataContract(Name="column")]
public class ColumnStructure : AFormulaItemStructure
{
[DataMember(Name="type")]
public string Type;
[DataMember(Name = "field")]
public string Field;
[DataMember(Name = "display")]
public string Display;
}
[DataContract(Name="function")]
public class FunctionStructure : AFormulaItemStructure
{
[DataMember(Name = "type")]
public string Type;
[DataMember(Name = "name")]
public string Name;
[DataMember(Name = "parameters")]
public string Parameters;
}
[DataContract(Name = "operand")]
public class OperandStructure : AFormulaItemStructure
{
[DataMember(Name = "type")]
public string Type;
[DataMember(Name = "left")]
public string Left;
[DataMember(Name = "right")]
public string Right;
}
如果需要对生成的XML / JSON进行更多控制,则可能需要进一步调整。 我使用以下代码进行测试:
public static string Serialize(FormulaStructure structure)
{
using (MemoryStream memoryStream = new MemoryStream())
using (StreamReader reader = new StreamReader(memoryStream))
{
var serializer = new DataContractSerializer(typeof(FormulaStructure));
serializer.WriteObject(memoryStream, structure);
memoryStream.Position = 0;
return reader.ReadToEnd();
}
}
public static FormulaStructure Deserialize(string xml)
{
using (Stream stream = new MemoryStream())
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
stream.Write(data, 0, data.Length);
stream.Position = 0;
var deserializer = new DataContractSerializer(typeof(FormulaStructure));
return (FormulaStructure)deserializer.ReadObject(stream);
}
}
在我们以前的答案深入探讨了一些问题之后,我发现JSON可用于序列化/反序列化名称空间的SerializationBinder
类。
我生成了一个类来继承SerializationBinder
:
public class KnownTypesBinder : System.Runtime.Serialization.SerializationBinder {
public KnownTypesBinder() {
KnownTypes = new List<Type>();
AliasedTypes = new Dictionary<string, Type>();
}
public IList<Type> KnownTypes { get; set; }
public IDictionary<string, Type> AliasedTypes { get; set; }
public override Type BindToType(string assemblyName, string typeName) {
if (AliasedTypes.ContainsKey(typeName)) { return AliasedTypes[typeName]; }
var type = KnownTypes.SingleOrDefault(t => t.Name == typeName);
if (type == null) {
type = Type.GetType(Assembly.CreateQualifiedName(assemblyName, typeName));
if (type == null) {
throw new InvalidCastException("Unknown type encountered while deserializing JSON. This can happen if class names have changed but the database or the JavaScript references the old class name.");
}
}
return type;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName) {
assemblyName = null;
typeName = serializedType.Name;
}
}
假设我有一组这样定义的类:
public class Class1 {
public string Text { get; set; }
}
public class Class2 {
public int Value { get; set; }
}
public class MyClass {
public Class1 Text { get; set; }
public Class2 Value { get; set; }
}
这样做是为了让我为要序列化/反序列化的类生成自己的名称。 在我的global.asax
文件中,按如下方式应用活页夹:
KnownTypesBinder binder = new KnownTypesBinder()
binder.AliasedTypes["Class1"] = typeof(Project1.Class1);
binder.AliasedTypes["WhateverStringIWant"] = typeof(Project1.Class2);
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Binder = binder;
现在,每当我将MyClass
序列化为JSON时,都会得到以下信息:
{
item: {
$type: "Project1.MyClass",
Text: {
$type: "Class1",
Text: "some value"
},
Value: {
$type: "WhateverStringIWant",
Value: 88
}
}
}
我还可以选择剥离汇编信息,并通过将信息添加到KnownTypesBinder
来严格使用类名:
KnownTypesBinder binder = new KnownTypesBinder()
binder.KnownTypes.Add(typeof(Project1.Class1));
binder.KnownTypes.Add(typeof(Project1.Class1));
在给出的两个示例中, Class1
的引用方式相同。 但是,如果我将Class1
重构为NewClass1
,那么第二个示例将开始发送另一个名称。 这可能不大,取决于您是否使用这些类型。
AliasedTypes的优点是我可以给它任何想要的字符串,并且无论重构多少代码都没有关系,.NET和JavaScript(或那里的任何使用者)之间的通信不会中断。
注意不要将类名完全相同的AliasedType
和KnownType
混合AliasedType
,因为编写的代码表明AliasType
将胜过KnownType
。 活页夹无法识别类型(别名或已知)时,它将提供该类型的完整程序集名称。
最后,我分解了.NET类信息并将其添加到模块中的字符串变量中,以简化重构。
module.net = {};
module.net.classes = {};
module.net.classes['column'] = "ColumnStructure";
module.net.classes['function'] = "FunctionStructure";
module.net.classes['operand'] = "OperandStructure";
module.net.getAssembly = function (className) {
return "MyNamespace.Models." + module.net.classes[className] + ", MyAssembly";
}
并生成JSON为
{
"FormulaItem": {
"$type": module.net.getAssembly('column'),
"type": "int",
"field": "my_field",
"display": "My Field"
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.