XmlSerializer: Entfernen Sie unnötige xsi- und xsd-Namespaces


132

Gibt es eine Möglichkeit, den XmlSerializer so zu konfigurieren, dass keine Standard-Namespaces in das Root-Element geschrieben werden?

Was ich bekomme ist folgendes:

<?xml ...>
<rootelement xmlns:xsi="..." xmlns:xsd="...">
</rootelement>

und ich möchte beide xmlns-Deklarationen entfernen.

Duplikat von : Wie serialisiere ich ein Objekt in XML, ohne xmlns = ”…” zu erhalten?

Antworten:


63

Da Dave mich gebeten hat, meine Antwort auf das Auslassen aller xsi- und xsd-Namespaces beim Serialisieren eines Objekts in .NET zu wiederholen, habe ich diesen Beitrag aktualisiert und meine Antwort hier über den oben genannten Link wiederholt. Das in dieser Antwort verwendete Beispiel ist das gleiche Beispiel, das für die andere Frage verwendet wurde. Was folgt, wird wörtlich kopiert.


Nachdem ich die Dokumentation von Microsoft und verschiedene Lösungen online gelesen habe, habe ich die Lösung für dieses Problem gefunden. Es funktioniert sowohl mit der integrierten XmlSerializerals auch mit der benutzerdefinierten XML-Serialisierung über IXmlSerialiazble.

Ich werde das gleiche MyTypeWithNamespacesXML-Beispiel verwenden, das bisher in den Antworten auf diese Frage verwendet wurde.

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

Das ist alles für diese Klasse. Nun hatten einige Einwände dagegen, XmlSerializerNamespacesirgendwo in ihren Klassen ein Objekt zu haben; Aber wie Sie sehen, habe ich es ordentlich in den Standardkonstruktor gesteckt und eine öffentliche Eigenschaft verfügbar gemacht, um die Namespaces zurückzugeben.

Wenn es Zeit ist, die Klasse zu serialisieren, verwenden Sie den folgenden Code:

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

Sobald Sie dies getan haben, sollten Sie die folgende Ausgabe erhalten:

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

Ich habe diese Methode kürzlich in einem Projekt mit einer tiefen Hierarchie von Klassen erfolgreich verwendet, die für Webdienstaufrufe in XML serialisiert werden. In der Dokumentation von Microsoft ist nicht klar, was mit dem öffentlich zugänglichen XmlSerializerNamespacesMitglied zu tun ist, wenn Sie es erstellt haben, und so viele halten es für nutzlos. Wenn Sie jedoch der Dokumentation folgen und sie auf die oben gezeigte Weise verwenden, können Sie anpassen, wie der XmlSerializer XML für Ihre Klassen generiert, ohne auf nicht unterstütztes Verhalten zurückgreifen oder Ihre eigene Serialisierung durch Implementierung "rollen" zu lassen IXmlSerializable.

Ich hoffe, dass diese Antwort ein für alle Mal zum Erliegen kommt, wie der Standard xsiund die xsdNamespaces, die von der XmlSerializer.

UPDATE: Ich möchte nur sicherstellen, dass ich die Frage des OP zum Entfernen aller Namespaces beantwortet habe. Mein Code oben wird dafür funktionieren; lass mich dir zeigen wie. Im obigen Beispiel können Sie wirklich nicht alle Namespaces entfernen (da zwei Namespaces verwendet werden). Irgendwo in Ihrem XML-Dokument benötigen Sie so etwas wie xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Wenn die Klasse im Beispiel Teil eines größeren Dokuments ist, muss irgendwo über einem Namespace für eines von (oder für beide) Abracadbraund deklariert werden Whoohoo. Wenn nicht, muss das Element in einem oder beiden Namespaces mit einem Präfix versehen sein (Sie können nicht zwei Standard-Namespaces haben, oder?). In diesem Beispiel Abracadabraist dies der Standard-Namespace. Ich könnte in meiner MyTypeWithNamespacesKlasse ein Namespace-Präfix für den WhoohooNamespace wie folgt hinzufügen :

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

In meiner Klassendefinition habe ich nun angegeben, dass sich das <Label/>Element im Namespace befindet "urn:Whoohoo", sodass ich nichts weiter tun muss. Wenn ich jetzt die Klasse mit meinem obigen Serialisierungscode unverändert serialisiere, ist dies die Ausgabe:

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

Da <Label>es sich in einem anderen Namespace als der Rest des Dokuments befindet, muss es in gewisser Weise mit einem Namespace "dekoriert" werden. Beachten Sie, dass es noch keine sind xsiund xsdNamensräume.


Damit ist meine Antwort auf die andere Frage beendet. Aber ich wollte sicherstellen, dass ich die Frage des OP beantwortet habe, keine Namespaces zu verwenden, da ich das Gefühl habe, dass ich sie noch nicht wirklich angesprochen habe. Angenommen, dies <Label>ist Teil desselben Namespace wie der Rest des Dokuments. In diesem Fall urn:Abracadabra:

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

Ihr Konstruktor würde wie in meinem ersten Codebeispiel aussehen, zusammen mit der öffentlichen Eigenschaft, um den Standard-Namespace abzurufen:

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

Dann, später, in Ihrem Code, der das MyTypeWithNamespacesObjekt zum Serialisieren verwendet, würden Sie es wie oben beschrieben aufrufen:

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

Und das XmlSerializerwürde das gleiche XML wie oben oben ohne zusätzliche Namespaces in der Ausgabe zurückspucken:

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

Der Vollständigkeit halber sollten Sie hier vielleicht die richtige Antwort einfügen, anstatt sich einfach darauf zu beziehen, und ich bin auch daran interessiert zu wissen, wie Sie zu dem Schluss kommen, dass es sich um "nicht unterstütztes Verhalten" handelt.
Dave Van den Eynde

1
Kam wieder hierher, um dies zu überprüfen, da es die einfachste Erklärung ist, die ich gefunden habe. Danke @fourpastmidnight
Andre Albuquerque

2
Ich verstehe nicht, für die Antwort Ihres endgültigen OP verwenden Sie während der Serialisierung immer noch einen Namespace "urn: Abracadabra" (Konstruktor). Warum ist das nicht in der endgültigen Ausgabe enthalten? Sollte das OP nicht verwenden: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
dparkar

2
Dies ist die richtige Antwort, obwohl sie nicht die am meisten gewählte ist. Das Onky-Ding, das bei mir nicht funktionierte, war, dass XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);ich es ersetzen musste var xtw = XmlTextWriter.Create(memStm, xws);.
Leonel Sanches da Silva

1
Es ist schon eine Weile her, seit ich diese Antwort geschrieben habe. XmlTextWriter.CreateGibt eine (abstrakte?) XmlWriterInstanz zurück. Wenn @ Preza8 also korrekt ist, verlieren Sie die Möglichkeit, andere XmlTextWriterspezifische Eigenschaften festzulegen (zumindest nicht, ohne sie herunterzuwerfen), daher die spezifische Umwandlung auf 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)

24
Hmmm ... ihr seid Rebellen. Unter msdn.microsoft.com/en-us/library/… heißt es ausdrücklich, dass Sie das nicht tun können.
Ralph Lavelle

Bool Yah! (Um zu überwinden, was ms sagen, können Sie nicht tun)
GranadaCoder

3
Ich bin mir nicht sicher, warum es "nicht unterstützt" wird, aber das macht genau das, was ich wollte.
Dan Bechard

8
Diese Antwort generiert die Namespaces "xmlns: d1p1" und "xmlns: q1". Was ist das?
Leonel Sanches da Silva

2
Nun, dieser Code funktioniert für wirklich sehr einfache Serialisierungen ohne andere Namespace-Definitionen. Bei mehreren Namespace-Definitionen ist die Arbeitsantwort die akzeptierte.
Leonel Sanches da Silva

6

Es gibt eine Alternative: Sie können ein Mitglied vom Typ XmlSerializerNamespaces in dem zu serialisierenden Typ angeben . Dekorieren Sie es mit dem Attribut XmlNamespaceDeclarations . Fügen Sie diesem Mitglied die Namespace-Präfixe und URIs hinzu. Dann verwendet jede Serialisierung, die nicht explizit einen XmlSerializerNamespaces bereitstellt, die Namespace-Präfix + URI-Paare, die Sie in Ihren Typ eingegeben haben.

Beispielcode: Angenommen, dies ist Ihr Typ:

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

Du kannst das:

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

Dies bedeutet, dass bei jeder Serialisierung dieser Instanz, die keinen eigenen Satz von Präfix + URI-Paaren angibt, das Präfix "p" für den Namespace "urn: mycompany.2009" verwendet wird. Außerdem werden die Namespaces xsi und xsd weggelassen.

Der Unterschied besteht darin, dass Sie die XmlSerializerNamespaces zum Typ selbst hinzufügen, anstatt sie explizit bei einem Aufruf von XmlSerializer.Serialize () zu verwenden. Dies bedeutet, dass, wenn eine Instanz Ihres Typs durch Code serialisiert wird, den Sie nicht besitzen (z. B. in einem Webservices-Stack) und dieser Code keine expliziten XmlSerializerNamespaces bereitstellt, dieser Serializer die in der Instanz bereitgestellten Namespaces verwendet.


1. Ich sehe keinen Unterschied. Sie fügen einer Instanz von XmlSerializerNamespaces immer noch den Standard-Namespace hinzu.
Dave Van den Eynde

3
2. Dies verschmutzt die Klassen mehr. Mein Ziel ist es, bestimmte Namespaces nicht zu verwenden. Mein Ziel ist es, überhaupt keine Namespaces zu verwenden.
Dave Van den Eynde

Ich habe einen Hinweis zum Unterschied zwischen diesem Ansatz und dem Angeben der XmlSerializerNamespaces nur während der Serialisierung hinzugefügt.
Cheeso

0

Ich benutze:

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

So erhalten Sie folgendes XML:

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

Wenn Sie den Namespace nicht möchten, setzen Sie DEFAULT_NAMESPACE einfach auf "".


Obwohl diese Frage über 10 Jahre alt ist, bestand der Sinn damals darin, einen XML-Body zu haben, der überhaupt keine Namespace-Deklarationen enthielt.
Dave Van den Eynde

1
Wenn ich einer 10 Jahre alten Frage meine eigene Antwort hinzufüge, liegt dies daran, dass die akzeptierte Antwort länger zu lesen ist als die Bibel in ihrer vollständigen Ausgabe.
Maxence

Und die am häufigsten gewählte Antwort fördert einen Ansatz (leerer Namespace), der nicht empfohlen wird.
Maxence

Das kann ich nicht ändern. Ich kann nur die akzeptierte Antwort geben, die ich für die richtigste halte.
Dave Van den Eynde
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.