XmlSerializer: usuń niepotrzebne przestrzenie nazw xsi i xsd

Odpowiedzi:

65

Ponieważ Dave poprosił mnie o powtórzenie mojej odpowiedzi na temat Pomijania wszystkich przestrzeni nazw xsi i xsd podczas serializacji obiektu w .NET , zaktualizowałem ten post i powtórzyłem moją odpowiedź tutaj z wyżej wymienionego łącza. Przykład użyty w tej odpowiedzi jest tym samym przykładem zastosowanym w przypadku innego pytania. Poniższy tekst jest kopiowany dosłownie.


Po przeczytaniu dokumentacji Microsoftu i kilku rozwiązań online, znalazłem rozwiązanie tego problemu. Działa z wbudowaną XmlSerializeri niestandardową serializacją XML za pośrednictwem IXmlSerialiazble.

Na marginesie użyję tego samego MyTypeWithNamespacesprzykładu XML, który został użyty w odpowiedziach na to pytanie.

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

To wszystko do tej klasy. Niektórzy sprzeciwiają się posiadaniu XmlSerializerNamespacesobiektu gdzieś w swoich klasach; ale jak widać, schludnie schowałem go w domyślnym konstruktorze i ujawniłem właściwość publiczną, która zwraca przestrzenie nazw.

Teraz, gdy przyjdzie czas na serializację klasy, użyjesz następującego kodu:

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

Gdy to zrobisz, powinieneś otrzymać następujący wynik:

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

Z powodzeniem zastosowałem tę metodę w niedawnym projekcie z głęboką hierarchią klas, które są serializowane do XML dla wywołań usług internetowych. Dokumentacja Microsoftu nie jest zbyt jasna, co zrobić z publicznie dostępnym XmlSerializerNamespacesczłonkiem po utworzeniu, a tak wielu uważa, że ​​jest bezużyteczna. Ale postępując zgodnie z ich dokumentacją i używając jej w sposób pokazany powyżej, możesz dostosować sposób, w jaki XmlSerializer generuje XML dla twoich klas bez uciekania się do nieobsługiwanego zachowania lub „toczenia własnej” serializacji przez implementację IXmlSerializable.

Mam nadzieję, że ta odpowiedź raz na zawsze położy kres temu, jak pozbyć się standardu xsii xsdprzestrzeni nazw generowanych przez XmlSerializer.

AKTUALIZACJA: Chcę się tylko upewnić, że odpowiedziałem na pytanie OP dotyczące usunięcia wszystkich przestrzeni nazw. Mój kod powyżej będzie działał w tym celu; Pokażę ci jak. Teraz, w powyższym przykładzie, naprawdę nie możesz pozbyć się wszystkich przestrzeni nazw (ponieważ są używane dwie przestrzenie nazw). Gdzieś w dokumencie XML będziesz potrzebować czegoś takiego jak xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Jeśli klasa w przykładzie jest częścią większego dokumentu, to gdzieś powyżej przestrzeni nazw musi być zadeklarowana dla jednego z (lub obu) Abracadbrai Whoohoo. Jeśli nie, to element w jednej lub obu przestrzeniach nazw musi być ozdobiony jakimś prefiksem (nie możesz mieć dwóch domyślnych przestrzeni nazw, prawda?). W tym przykładzie Abracadabrajest to domyślna przestrzeń nazw. Mógłbym wewnątrz mojej MyTypeWithNamespacesklasy dodać prefiks przestrzeni nazw dla Whoohooprzestrzeni nazw w następujący sposób:

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

Teraz w mojej definicji klasy wskazałem, że <Label/>element znajduje się w przestrzeni nazw "urn:Whoohoo", więc nie muszę nic więcej robić. Kiedy teraz serializuję klasę przy użyciu niezmienionego mojego powyższego kodu serializacji, jest to wynik:

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

Ponieważ <Label>znajduje się w innej przestrzeni nazw niż reszta dokumentu, w jakiś sposób musi być „ozdobiony” przestrzenią nazw. Zauważ, że nadal nie ma przestrzeni nazw xsii xsd.


To kończy moją odpowiedź na drugie pytanie. Ale chciałem się upewnić, że odpowiedziałem na pytanie OP dotyczące używania żadnych przestrzeni nazw, ponieważ czuję, że tak naprawdę jeszcze do tego nie odpowiedziałem. Załóżmy, że <Label>jest to część tej samej przestrzeni nazw, co reszta dokumentu, w tym przypadku urn:Abracadabra:

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

Twój konstruktor wyglądałby tak, jak w moim pierwszym przykładzie kodu, wraz z właściwością publiczną do pobrania domyślnej przestrzeni nazw:

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

Później w swoim kodzie, który używa MyTypeWithNamespacesobiektu do serializacji, nazwałbyś to tak, jak zrobiłem powyżej:

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

I XmlSerializerwypluliby ten sam kod XML, który pokazano bezpośrednio powyżej, bez dodatkowych przestrzeni nazw w wyniku:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>
o czwartej po północy
źródło
Aby uzyskać kompletność, być może powinieneś zamieścić tutaj właściwą odpowiedź, zamiast po prostu się do niej odnosić. Interesuje mnie również, jak wyciągniesz wniosek, że jest to „nieobsługiwane zachowanie”.
Dave Van den Eynde
1
Przyszedłem tu ponownie, aby to sprawdzić, ponieważ jest to najprostsze wyjaśnienie, jakie znalazłem. Dzięki @fourpastmidnight
Andre Albuquerque
2
Nie rozumiem, jako ostateczna odpowiedź OP, nadal używasz przestrzeni nazw podczas serializacji „urn: Abracadabra” (konstruktor), dlaczego nie jest to uwzględnione w końcowym wyniku. Czy OP nie powinien używać: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
dparkar
2
To jest poprawna odpowiedź, chociaż nie jest najczęściej głosowana. Najgorszą rzeczą, która nie działała dla mnie, było to, XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);że musiałem wymienić var xtw = XmlTextWriter.Create(memStm, xws);.
Leonel Sanches da Silva
1
Minęło trochę czasu, odkąd napisałem tę odpowiedź. XmlTextWriter.Createzwraca (abstrakcyjną?) XmlWriterinstancję. Więc @ Preza8 jest poprawne, straciłbyś możliwość ustawiania innych XmlTextWriterspecyficznych właściwości (przynajmniej nie bez rzucania go w dół), a więc konkretnego rzutowania na XmlTextWriter.
czwartej północy
262
//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
źródło
25
Hmmm ... jesteście buntownikami. Pod adresem msdn.microsoft.com/en-us/library/ jest wyraźnie napisane, że nie możesz tego zrobić.
Ralph Lavelle,
Bool Yah! (Aby przezwyciężyć to, co mówi pani, że nie można zrobić)
granadaCoder
3
Nie jestem pewien, dlaczego jest „nieobsługiwany”, ale robi dokładnie to, czego chciałem.
Dan Bechard,
10
Ta odpowiedź generuje przestrzenie nazw „xmlns: d1p1” i „xmlns: q1”. Co to jest?
Leonel Sanches da Silva
3
Cóż, ten kod działa w przypadku naprawdę bardzo prostych serializacji, bez innych definicji przestrzeni nazw. W przypadku definicji wielu przestrzeni nazw działającą odpowiedzią jest ta akceptowana.
Leonel Sanches da Silva
6

Istnieje alternatywa - można podać element członkowski typu XmlSerializerNamespaces w typie do serializacji. Udekoruj go atrybutem XmlNamespaceDeclarations . Dodaj prefiksy przestrzeni nazw i identyfikatory URI do tego elementu członkowskiego. Następnie każda serializacja, która nie zapewnia jawnie XmlSerializerNamespaces, użyje prefiksu przestrzeni nazw + par URI, które zostały wprowadzone do typu.

Przykładowy kod, załóżmy, że to Twój typ:

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

Możesz to zrobić:

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

A to oznacza, że ​​każda serializacja tej instancji, która nie określa własnego zestawu par prefiks + URI, użyje przedrostka „p” dla przestrzeni nazw „urn: mycompany.2009”. Pominie również przestrzenie nazw xsi i xsd.

Różnica polega na tym, że dodajesz XmlSerializerNamespaces do samego typu, zamiast używać go jawnie w wywołaniu XmlSerializer.Serialize (). Oznacza to, że jeśli wystąpienie twojego typu jest serializowane przez kod, którego nie jesteś właścicielem (na przykład w stosie usług sieci Web), a ten kod nie zapewnia jawnie XmlSerializerNamespaces, ten serializator użyje przestrzeni nazw podanych w wystąpieniu.

Ser
źródło
1. Nie widzę różnicy. Nadal dodajesz domyślną przestrzeń nazw do wystąpienia XmlSerializerNamespaces.
Dave Van den Eynde
3
2. To bardziej zanieczyszcza klasy. Moim celem jest nie używanie określonej przestrzeni nazw, moim celem jest nie używanie w ogóle przestrzeni nazw.
Dave Van den Eynde
Dodałem uwagę na temat różnicy między tym podejściem a określaniem XmlSerializerNamespaces tylko podczas serializacji.
Cheeso
0

Używam:

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

Aby uzyskać następujący kod XML:

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

Jeśli nie potrzebujesz przestrzeni nazw, po prostu ustaw DEFAULT_NAMESPACE na „”.

Maxence
źródło
Chociaż to pytanie ma ponad 10 lat, chodziło o to, aby mieć treść XML, która w ogóle nie zawierała żadnych deklaracji przestrzeni nazw.
Dave Van den Eynde
1
Jeśli dodam własną odpowiedź na pytanie, które ma 10 lat, to dlatego, że zaakceptowana odpowiedź jest dłuższa do przeczytania niż Biblia w jej pełnym wydaniu.
Maxence
A najczęściej głosowana odpowiedź promuje podejście (pusta przestrzeń nazw), które nie jest zalecane.
Maxence
1
Nic na to nie poradzę. Mogę tylko uczynić zaakceptowaną odpowiedź tą, która moim zdaniem jest najbardziej poprawna.
Dave Van den Eynde