ใช้แอ็ตทริบิวต์ XmlInclude หรือ SoapInclude เพื่อระบุชนิดที่ไม่รู้จักแบบคงที่


99

ผมเคยมีปัญหาแปลกมากเมื่อทำงานกับ XmlSerializer.NET

เรียนตามตัวอย่างต่อไปนี้:

public class Order 
{
    public PaymentCollection Payments { get; set; }

    //everything else is serializable (including other collections of non-abstract types)
}

public class PaymentCollection : Collection<Payment>
{
}

public abstract class Payment 
{
    //abstract methods
}

public class BankPayment : Payment
{
    //method implementations
}

AFAIK มีสามวิธีในการแก้ปัญหาInvalidOperationExceptionที่เกิดจาก serializer ไม่รู้เกี่ยวกับประเภทที่ได้รับของPayment.

1. การเพิ่มXmlIncludeกับPaymentการกำหนดระดับ:

สิ่งนี้เป็นไปไม่ได้เนื่องจากคลาสทั้งหมดถูกรวมเป็นข้อมูลอ้างอิงภายนอกซึ่งฉันไม่สามารถควบคุมได้

2. ส่งผ่านประเภทที่ได้รับมาระหว่างการสร้างXmlSerializerอินสแตนซ์

ไม่ทำงาน

3. การXmlAttributeOverridesกำหนดคุณสมบัติเป้าหมายเพื่อแทนที่การทำให้เป็นอนุกรมเริ่มต้นของคุณสมบัติ (ตามที่อธิบายไว้ในโพสต์ SO นี้ )

ยังไม่ทำงาน (การXmlAttributeOverridesเริ่มต้นตามมา)

Type bankPayment = typeof(BankPayment);

XmlAttributes attributes = new XmlAttributes();
attributes.XmlElements.Add(new XmlElementAttribute(bankPayment.Name, bankPayment));

XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Order), "Payments", attributes);

XmlSerializerจากนั้นจะใช้ตัวสร้างที่เหมาะสม

หมายเหตุ: โดยไม่ทำงานฉันหมายถึงInvalidOperationException( BankPaymentไม่คาดว่า ... ) ถูกโยน

ใครช่วยให้ความกระจ่างเกี่ยวกับเรื่องนี้ได้บ้าง? เราจะดำเนินการแก้ไขปัญหาต่อไปอย่างไร

คำตอบ:


94

สิ่งนี้ใช้ได้ผลสำหรับฉัน:

[XmlInclude(typeof(BankPayment))]
[Serializable]
public abstract class Payment { }    

[Serializable]
public class BankPayment : Payment {} 

[Serializable]
public class Payments : List<Payment>{}

XmlSerializer serializer = new XmlSerializer(typeof(Payments), new Type[]{typeof(Payment)});

16
ดังนั้นประเภทพื้นฐานจำเป็นต้องรู้การใช้งานทั้งหมดหรือไม่? นี่ดูเหมือนจะไม่ใช่วิธีแก้ปัญหาที่ดีนัก ไม่มีวิธีอื่นหรือ
Alexander Stolz

3
@AlexanderStolz สำหรับการใช้งานทั่วไปผ่าน Type ใหม่ในขณะที่สร้าง XmlSerializable Object เป็นทางออกที่ดีที่สุด ดังที่ได้กล่าวไว้stackoverflow.com/a/2689660/698127
Aamol

39

เพิ่งแก้ไขปัญหา หลังจากขุดคุ้ยอยู่นานขึ้นฉันก็พบโพสต์ SO นี้ซึ่งครอบคลุมสถานการณ์เดียวกัน มันทำให้ฉันมาถูกทางแล้ว

โดยทั่วไปXmlSerializerความต้องการที่จะทราบเนมสเปซเริ่มต้นหากคลาสที่ได้รับมารวมเป็นชนิดพิเศษ ยังไม่ทราบสาเหตุที่แน่ชัดว่าทำไมสิ่งนี้ถึงเกิดขึ้น แต่ก็ยังใช้งานได้ในขณะนี้


3

จากนี้ฉันสามารถแก้ปัญหานี้ได้โดยการเปลี่ยนคอนสตรัคเตอร์ของXmlSerializerฉันที่ใช้แทนการเปลี่ยนคลาส

แทนที่จะใช้สิ่งนี้ (แนะนำในคำตอบอื่น ๆ ):

[XmlInclude(typeof(Derived))]
public class Base {}

public class Derived : Base {}

public void Serialize()
{
    TextWriter writer = new StreamWriter(SchedulePath);
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<Derived>));
    xmlSerializer.Serialize(writer, data);
    writer.Close();
}

ฉันทำอย่างนี้:

public class Base {}

public class Derived : Base {}

public void Serialize()
{
    TextWriter writer = new StreamWriter(SchedulePath);
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<Derived>), new[] { typeof(Derived) });
    xmlSerializer.Serialize(writer, data);
    writer.Close();
}

2

เพียงแค่ทำในฐานเพื่อให้เด็ก ๆ สามารถต่ออนุกรมรหัสทำความสะอาดโค้ดน้อยลง

public abstract class XmlBaseClass  
{
  public virtual string Serialize()
  {
    this.SerializeValidation();

    XmlSerializerNamespaces XmlNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
    XmlWriterSettings XmlSettings = new XmlWriterSettings
    {
      Indent = true,
      OmitXmlDeclaration = true
    };

    StringWriter StringWriter = new StringWriter();

    XmlSerializer Serializer = new XmlSerializer(this.GetType());
    XmlWriter XmlWriter = XmlWriter.Create(StringWriter, XmlSettings);
    Serializer.Serialize(XmlWriter, this, XmlNamespaces);
    StringWriter.Flush();
    StringWriter.Close();

    return StringWriter.ToString();

  }

  protected virtual void SerializeValidation() {}
}

[XmlRoot(ElementName = "MyRoot", Namespace = "MyNamespace")]
public class XmlChildClass : XmlBaseClass
{
  protected override void SerializeValidation()
  {
    //Add custom validation logic here or anything else you need to do
  }
}

ด้วยวิธีนี้คุณสามารถเรียก Serialize ในคลาสลูกได้ไม่ว่าจะอยู่ในสถานการณ์ใดและยังคงสามารถทำสิ่งที่คุณต้องการก่อนที่จะทำให้วัตถุ Serializes


2

ฉันเห็นด้วยกับ bizl

[XmlInclude(typeof(ParentOfTheItem))]
[Serializable]
public abstract class WarningsType{ }

นอกจากนี้หากคุณต้องการใช้คลาสที่รวมอยู่นี้กับไอเท็มอ็อบเจ็กต์คุณสามารถทำได้เช่นนั้น

[System.Xml.Serialization.XmlElementAttribute("Warnings", typeof(WarningsType))]
public object[] Items
{
    get
    {
        return this.itemsField;
    }
    set
    {
        this.itemsField = value;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.