XmlSerializer: menghapus ruang nama xsi dan xsd yang tidak perlu

Jawaban:

63

Karena Dave meminta saya untuk mengulangi jawaban saya untuk Menghilangkan semua namespace xsi dan xsd ketika membuat serial objek di .NET , saya telah memperbarui posting ini dan mengulangi jawaban saya di sini dari tautan yang disebutkan sebelumnya. Contoh yang digunakan dalam jawaban ini adalah contoh yang sama yang digunakan untuk pertanyaan lain. Yang berikut disalin, kata demi kata.


Setelah membaca dokumentasi Microsoft dan beberapa solusi online, saya telah menemukan solusi untuk masalah ini. Ini berfungsi dengan baik XmlSerializerserialisasi XML bawaan dan kustom via IXmlSerialiazble.

Untuk sedikit pun, saya akan menggunakan MyTypeWithNamespacessampel XML yang sama yang telah digunakan dalam jawaban untuk pertanyaan ini sejauh ini.

[XmlRoot("MyTypeWithNamespaces", Namespace="urn:Abracadabra", IsNullable=false)]
public class MyTypeWithNamespaces
{
    // As noted below, per Microsoft's documentation, if the class exposes a public
    // member of type XmlSerializerNamespaces decorated with the 
    // XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
    // namespaces during serialization.
    public MyTypeWithNamespaces( )
    {
        this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
            // Don't do this!! Microsoft's documentation explicitly says it's not supported.
            // It doesn't throw any exceptions, but in my testing, it didn't always work.

            // new XmlQualifiedName(string.Empty, string.Empty),  // And don't do this:
            // new XmlQualifiedName("", "")

            // DO THIS:
            new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
            // Add any other namespaces, with prefixes, here.
        });
    }

    // If you have other constructors, make sure to call the default constructor.
    public MyTypeWithNamespaces(string label, int epoch) : this( )
    {
        this._label = label;
        this._epoch = epoch;
    }

    // An element with a declared namespace different than the namespace
    // of the enclosing type.
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        get { return this._label; }
        set { this._label = value; }
    }
    private string _label;

    // An element whose tag will be the same name as the property name.
    // Also, this element will inherit the namespace of the enclosing type.
    public int Epoch
    {
        get { return this._epoch; }
        set { this._epoch = value; }
    }
    private int _epoch;

    // Per Microsoft's documentation, you can add some public member that
    // returns a XmlSerializerNamespaces object. They use a public field,
    // but that's sloppy. So I'll use a private backed-field with a public
    // getter property. Also, per the documentation, for this to work with
    // the XmlSerializer, decorate it with the XmlNamespaceDeclarations
    // attribute.
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Namespaces
    {
        get { return this._namespaces; }
    }
    private XmlSerializerNamespaces _namespaces;
}

Itu saja untuk kelas ini. Sekarang, beberapa keberatan memiliki XmlSerializerNamespacesobjek di suatu tempat di dalam kelas mereka; tetapi seperti yang Anda lihat, saya menyelipkannya dengan rapi di konstruktor default dan mengekspos properti publik untuk mengembalikan ruang nama.

Sekarang, ketika tiba saatnya untuk membuat serialisasi kelas, Anda akan menggunakan kode berikut:

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

/******
   OK, I just figured I could do this to make the code shorter, so I commented out the
   below and replaced it with what follows:

// You have to use this constructor in order for the root element to have the right namespaces.
// If you need to do custom serialization of inner objects, you can use a shortened constructor.
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces), new XmlAttributeOverrides(),
    new Type[]{}, new XmlRootAttribute("MyTypeWithNamespaces"), "urn:Abracadabra");

******/
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });

// I'll use a MemoryStream as my backing store.
MemoryStream ms = new MemoryStream();

// This is extra! If you want to change the settings for the XmlSerializer, you have to create
// a separate XmlWriterSettings object and use the XmlTextWriter.Create(...) factory method.
// So, in this case, I want to omit the XML declaration.
XmlWriterSettings xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Encoding = Encoding.UTF8; // This is probably the default
// You could use the XmlWriterSetting to set indenting and new line options, but the
// XmlTextWriter class has a much easier method to accomplish that.

// The factory method returns a XmlWriter, not a XmlTextWriter, so cast it.
XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);
// Then we can set our indenting options (this is, of course, optional).
xtw.Formatting = Formatting.Indented;

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

Setelah Anda melakukan ini, Anda harus mendapatkan output berikut:

<MyTypeWithNamespaces>
    <Label xmlns="urn:Whoohoo">myLabel</Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

Saya telah berhasil menggunakan metode ini dalam proyek baru-baru ini dengan hirarki kelas yang mendalam yang diserialisasi ke XML untuk panggilan layanan web. Dokumentasi Microsoft tidak begitu jelas tentang apa yang harus dilakukan dengan XmlSerializerNamespacesanggota yang dapat diakses publik setelah Anda membuatnya, dan begitu banyak orang berpikir itu tidak berguna. Tetapi dengan mengikuti dokumentasi mereka dan menggunakannya dengan cara yang ditunjukkan di atas, Anda dapat menyesuaikan bagaimana XmlSerializer menghasilkan XML untuk kelas Anda tanpa menggunakan perilaku yang tidak didukung atau serialisasi "rolling your own" dengan mengimplementasikan IXmlSerializable.

Ini adalah harapan saya bahwa jawaban ini akan mengistirahatkan, sekali dan untuk semua, bagaimana cara menghilangkan standar xsidan xsdruang nama yang dihasilkan oleh XmlSerializer.

UPDATE: Saya hanya ingin memastikan saya menjawab pertanyaan OP tentang menghapus semua ruang nama. Kode saya di atas akan berfungsi untuk ini; Mari saya tunjukkan bagaimana caranya. Sekarang, dalam contoh di atas, Anda benar-benar tidak bisa menghilangkan semua ruang nama (karena ada dua ruang nama yang digunakan). Di suatu tempat di dokumen XML Anda, Anda harus memiliki sesuatu seperti xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Jika kelas dalam contoh adalah bagian dari dokumen yang lebih besar, maka di suatu tempat di atas namespace harus dideklarasikan untuk salah satu (atau keduanya) Abracadbradan Whoohoo. Jika tidak, maka elemen dalam satu atau kedua ruang nama harus didekorasi dengan semacam awalan (Anda tidak dapat memiliki dua ruang nama default, kan?). Jadi, untuk contoh ini, Abracadabraadalah namespace default. Saya bisa di dalam MyTypeWithNamespaceskelas saya menambahkan awalan namespace untuk Whoohoonamespace seperti:

public MyTypeWithNamespaces
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
        new XmlQualifiedName("w", "urn:Whoohoo")
    });
}

Sekarang, dalam definisi kelas saya, saya menunjukkan bahwa <Label/>elemennya ada di namespace "urn:Whoohoo", jadi saya tidak perlu melakukan apa-apa lagi. Ketika saya sekarang membuat cerita bersambung kelas menggunakan kode serialisasi saya di atas tidak berubah, ini adalah output:

<MyTypeWithNamespaces xmlns:w="urn:Whoohoo">
    <w:Label>myLabel</w:Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

Karena <Label>berada dalam ruang nama yang berbeda dari dokumen lainnya, ia harus, bagaimanapun, "didekorasi" dengan ruang nama. Perhatikan bahwa masih ada xsidan xsdruang nama.


Ini mengakhiri jawaban saya untuk pertanyaan lain. Tapi saya ingin memastikan saya menjawab pertanyaan OP tentang tidak menggunakan ruang nama, karena saya merasa saya belum benar-benar mengatasinya. Asumsikan itu <Label>adalah bagian dari namespace yang sama dengan sisa dokumen, dalam hal ini urn:Abracadabra:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

Konstruktor Anda akan terlihat seperti pada contoh kode pertama saya, bersama dengan properti publik untuk mengambil namespace default:

// As noted below, per Microsoft's documentation, if the class exposes a public
// member of type XmlSerializerNamespaces decorated with the 
// XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
// namespaces during serialization.
public MyTypeWithNamespaces( )
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
    });
}

[XmlNamespaceDeclarations]
public XmlSerializerNamespaces Namespaces
{
    get { return this._namespaces; }
}
private XmlSerializerNamespaces _namespaces;

Kemudian, nanti, dalam kode Anda yang menggunakan MyTypeWithNamespacesobjek untuk membuat cerita bersambung, Anda akan menyebutnya seperti yang saya lakukan di atas:

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });

...

// Above, you'd setup your XmlTextWriter.

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

Dan itu XmlSerializerakan memuntahkan kembali XML yang sama seperti yang ditunjukkan tepat di atas tanpa spasi nama tambahan dalam output:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>
empat tengah malam
sumber
Untuk kelengkapan, mungkin Anda harus memasukkan jawaban yang benar di sini, bukan hanya merujuk padanya, dan juga, saya tertarik untuk mengetahui bagaimana Anda menyimpulkan bahwa itu 'perilaku yang tidak didukung'.
Dave Van den Eynde
1
Datang ke sini lagi untuk memeriksa ini, karena ini adalah penjelasan paling langsung yang saya temukan. Terima kasih @fourpastmidnight
Andre Albuquerque
2
Saya tidak mengerti, untuk jawaban OP akhir Anda, Anda masih menggunakan namespace selama serialisasi "urn: Abracadabra" (konstruktor), mengapa itu tidak termasuk dalam hasil akhir. Seharusnya OP tidak digunakan: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
dparkar
2
Ini adalah jawaban yang benar, meskipun bukan yang paling banyak dipilih. Satu-satunya hal yang tidak berhasil bagi saya adalah XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);saya harus menggantinya var xtw = XmlTextWriter.Create(memStm, xws);.
Leonel Sanches da Silva
1
Sudah lama sejak saya menulis jawaban ini. XmlTextWriter.Createmengembalikan contoh (abstrak?) XmlWriter. Jadi @ Preza8 benar, Anda akan kehilangan kemampuan untuk mengatur XmlTextWriterproperti -specific lainnya (setidaknya, bukan tanpa down-casting), oleh karena itu, pemeran khusus untuk XmlTextWriter.
fourpastmidnight
257
//Create our own namespaces for the output
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

//Add an empty namespace and empty value
ns.Add("", "");

//Create the serializer
XmlSerializer slz = new XmlSerializer(someType);

//Serialize the object with our own namespaces (notice the overload)
slz.Serialize(myXmlTextWriter, someObject, ns)
Jeremy
sumber
24
Hmmm ... kalian pemberontak. Secara eksplisit mengatakan di msdn.microsoft.com/en-us/library/… bahwa Anda tidak dapat melakukan itu.
Ralph Lavelle
Bool Yah! (Untuk mengatasi apa yang Anda katakan tidak dapat Anda lakukan)
granadaCoder
3
Saya tidak yakin mengapa ini "tidak didukung", tetapi ini tepat seperti yang saya inginkan.
Dan Bechard
8
Jawaban ini menghasilkan namespace "xmlns: d1p1" dan "xmlns: q1". Apa ini?
Leonel Sanches da Silva
2
Nah, kode ini berfungsi untuk serialisasi yang benar-benar sangat sederhana, tanpa definisi namespace lainnya. Untuk beberapa definisi namespace, jawaban yang berfungsi adalah yang diterima.
Leonel Sanches da Silva
6

Ada alternatif - Anda dapat memberikan anggota tipe XmlSerializerNamespaces dalam jenis yang akan diserialisasi. Hiasi dengan atribut XmlNamespaceDeclarations . Tambahkan awalan namespace dan URI ke anggota itu. Kemudian, serialisasi apa pun yang tidak secara eksplisit menyediakan XmlSerializerNamespaces akan menggunakan awalan namespace + pasangan URI yang telah Anda masukkan ke dalam tipe Anda.

Kode contoh, anggap ini adalah tipe Anda:

[XmlRoot(Namespace = "urn:mycompany.2009")]
public class Person {
  [XmlAttribute] 
  public bool Known;
  [XmlElement]
  public string Name;
  [XmlNamespaceDeclarations]
  public XmlSerializerNamespaces xmlns;
}

Kamu bisa melakukan ini:

var p = new Person
  { 
      Name = "Charley",
      Known = false, 
      xmlns = new XmlSerializerNamespaces()
  }
p.xmlns.Add("",""); // default namespace is emoty
p.xmlns.Add("c", "urn:mycompany.2009");

Dan itu akan berarti bahwa setiap serialisasi dari instance itu yang tidak menentukan set awalannya sendiri + pasangan URI akan menggunakan awalan "p" untuk namespace "urn: mycompany.2009". Ini juga akan menghilangkan namespace xsi dan xsd.

Perbedaannya di sini adalah bahwa Anda menambahkan XmlSerializerNamespaces ke tipe itu sendiri, daripada menggunakannya secara eksplisit pada panggilan ke XmlSerializer.Serialize (). Ini berarti bahwa jika turunan jenis Anda serial dengan kode yang tidak Anda miliki (misalnya dalam tumpukan layanan web), dan kode itu tidak secara eksplisit menyediakan XmlSerializerNamespaces, serializer itu akan menggunakan ruang nama yang disediakan dalam instance.

Cheeso
sumber
1. Saya tidak melihat perbedaannya. Anda masih menambahkan namespace default ke instance XmlSerializerNamespaces.
Dave Van den Eynde
3
2. Ini lebih mencemari kelas. Tujuan saya bukan untuk menggunakan namespace tertentu, tujuan saya adalah tidak menggunakan namespace sama sekali.
Dave Van den Eynde
Saya menambahkan catatan tentang perbedaan antara pendekatan ini dan yang menentukan XmlSerializerNamespaces selama serialisasi saja.
Cheeso
0

Saya menggunakan:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        const string DEFAULT_NAMESPACE = "http://www.something.org/schema";
        var serializer = new XmlSerializer(typeof(Person), DEFAULT_NAMESPACE);
        var namespaces = new XmlSerializerNamespaces();
        namespaces.Add("", DEFAULT_NAMESPACE);

        using (var stream = new MemoryStream())
        {
            var someone = new Person
            {
                FirstName = "Donald",
                LastName = "Duck"
            };
            serializer.Serialize(stream, someone, namespaces);
            stream.Position = 0;
            using (var reader = new StreamReader(stream))
            {
                Console.WriteLine(reader.ReadToEnd());
            }
        }
    }
}

Untuk mendapatkan XML berikut:

<?xml version="1.0"?>
<Person xmlns="http://www.something.org/schema">
  <FirstName>Donald</FirstName>
  <LastName>Duck</LastName>
</Person>

Jika Anda tidak ingin namespace, tetapkan DEFAULT_NAMESPACE ke "".

Maxence
sumber
Meskipun pertanyaan ini sudah berusia lebih dari 10 tahun, intinya saat itu adalah memiliki badan XML yang tidak mengandung deklarasi namespace sama sekali.
Dave Van den Eynde
1
Jika saya menambahkan jawaban saya sendiri ke pertanyaan yang berumur 10 tahun, itu karena jawaban yang diterima lebih panjang untuk dibaca daripada Alkitab dalam edisi lengkapnya.
Maxence
Dan jawaban yang paling banyak dipilih mempromosikan pendekatan (namespace kosong) yang tidak direkomendasikan.
Maxence
Saya tidak bisa menahannya. Saya hanya bisa membuat jawaban yang diterima yang saya percaya adalah jawaban yang paling benar.
Dave Van den Eynde