Gunakan atribut XmlInclude atau SoapInclude untuk menentukan jenis yang tidak dikenal secara statis

98

Saya mendapat masalah yang sangat aneh saat bekerja dengan .NET XmlSerializer.

Ambil kelas contoh berikut:

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, ada tiga metode berbeda untuk menyelesaikannya InvalidOperationExceptionyang disebabkan oleh serializer tidak mengetahui tentang jenis turunannya Payment.

1. Menambahkan XmlIncludeke Paymentdefinisi kelas:

Ini tidak mungkin karena semua kelas dimasukkan sebagai referensi eksternal yang tidak dapat saya kendalikan.

2. Meneruskan tipe turunan selama pembuatan XmlSerializerinstance

Tidak berhasil.

3. Mendefinisikan XmlAttributeOverridesproperti target untuk menimpa serialisasi default properti (seperti yang dijelaskan dalam posting SO ini )

Juga tidak berfungsi ( XmlAttributeOverridesinisialisasi mengikuti).

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

XmlSerializerKonstruktor yang sesuai kemudian akan digunakan.

CATATAN: dengan tidak bekerja maksud saya InvalidOperationException( BankPaymenttidak diharapkan ... ) dilemparkan.

Adakah yang bisa menjelaskan subjek ini? Bagaimana cara men-debug masalah lebih lanjut?

lsoliveira
sumber

Jawaban:

93

Ini berhasil untuk saya:

[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)});
bizl
sumber
15
Jadi tipe dasar perlu mengetahui semua implementasinya? Sepertinya ini bukan solusi yang bagus. Apakah tidak ada cara lain?
Alexander Stolz
2
@AlexanderStolz untuk implementasi umum yang meneruskan Type baru sambil membuat Objek XmlSerializable adalah solusi terbaik. Seperti yang disebutkan stackoverflow.com/a/2689660/698127
Aamol
39

Baru saja menyelesaikan masalah. Setelah menggali beberapa saat lagi, saya menemukan posting SO ini yang mencakup situasi yang sama persis. Itu membuat saya di jalur yang benar.

Pada dasarnya, XmlSerializerkebutuhan untuk mengetahui namespace default jika kelas turunan disertakan sebagai tipe tambahan. Alasan pasti mengapa ini harus terjadi masih belum diketahui tetapi, tetap saja, serialisasi berfungsi sekarang.

lsoliveira
sumber
2

Saya setuju dengan bizl

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

juga jika Anda perlu menerapkan kelas yang disertakan ini ke item objek, Anda dapat melakukan seperti itu

[System.Xml.Serialization.XmlElementAttribute("Warnings", typeof(WarningsType))]
public object[] Items
{
    get
    {
        return this.itemsField;
    }
    set
    {
        this.itemsField = value;
    }
}
Hamit YILDIRIM
sumber
1

Lakukan saja di Base, dengan cara itu setiap anak dapat berseri, lebih sedikit kode pembersih kode.

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

Dengan cara ini Anda dapat memanggil Serialize pada kelas anak tidak peduli situasinya dan masih dapat melakukan apa yang Anda butuhkan sebelum objek berseri.

A. Dady
sumber
0

Berdasarkan ini saya dapat menyelesaikan ini dengan mengubah konstruktor yang XmlSerializersaya gunakan alih-alih mengubah kelas.

Alih-alih menggunakan sesuatu seperti ini (disarankan di jawaban lain):

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

Saya melakukan ini:

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();
}
derekantrican
sumber