簡體   English   中英

使用Web API反序列化為具有抽象屬性的類

[英]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似乎不尊重KnownTypeDataContractSerializer版本。 我是否需要滾動自己的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(或那里的任何使用者)之間的通信不會中斷。

注意不要將類名完全相同的AliasedTypeKnownType混合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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM