XmlSerializer: rimuove gli spazi dei nomi inutili xsi e xsd


Risposte:


63

Da quando Dave mi ha chiesto di ripetere la mia risposta all'omissione di tutti gli spazi dei nomi xsi e xsd durante la serializzazione di un oggetto in .NET , ho aggiornato questo post e ho ripetuto la mia risposta qui dal link di cui sopra. L'esempio usato in questa risposta è lo stesso esempio usato per l'altra domanda. Ciò che segue è copiato, alla lettera.


Dopo aver letto la documentazione di Microsoft e diverse soluzioni online, ho scoperto la soluzione a questo problema. Funziona con la XmlSerializerserializzazione XML integrata e personalizzata tramite IXmlSerialiazble.

Per fare questo, userò lo stesso MyTypeWithNamespacescampione XML che è stato usato finora nelle risposte a questa domanda.

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

Questo è tutto per questa classe. Ora, alcuni hanno obiettato di avere un XmlSerializerNamespacesoggetto da qualche parte all'interno delle loro classi; ma come puoi vedere, l'ho riposto ordinatamente nel costruttore predefinito ed esposto una proprietà pubblica per restituire gli spazi dei nomi.

Ora, quando arriva il momento di serializzare la classe, useresti il ​​seguente codice:

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

Una volta fatto questo, dovresti ottenere il seguente output:

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

Ho usato con successo questo metodo in un recente progetto con una profonda gerarchia di classi serializzate in XML per le chiamate al servizio web. La documentazione di Microsoft non è molto chiara su cosa fare con il XmlSerializerNamespacesmembro accessibile pubblicamente dopo averlo creato, e così tanti pensano che sia inutile. Ma seguendo la loro documentazione e utilizzandola nel modo mostrato sopra, è possibile personalizzare il modo in cui XmlSerializer genera XML per le classi senza ricorrere a comportamenti non supportati o "far rotolare la propria" serializzazione implementando IXmlSerializable.

Spero che questa risposta ponga fine, una volta per tutte, a come sbarazzarsi dello standard xsie degli xsdspazi dei nomi generati da XmlSerializer.

AGGIORNAMENTO: Voglio solo assicurarmi di aver risposto alla domanda del PO sulla rimozione di tutti gli spazi dei nomi. Il mio codice sopra funzionerà per questo; lascia che ti mostri come. Ora, nell'esempio sopra, non puoi davvero sbarazzarti di tutti gli spazi dei nomi (perché ci sono due spazi dei nomi in uso). Da qualche parte nel tuo documento XML, dovrai avere qualcosa di simile xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Se la classe nell'esempio fa parte di un documento più grande, allora da qualche parte sopra uno spazio dei nomi deve essere dichiarato per uno di (o entrambi) Abracadbrae Whoohoo. In caso contrario, l'elemento in uno o entrambi gli spazi dei nomi deve essere decorato con un prefisso di qualche tipo (non è possibile avere due spazi dei nomi predefiniti, giusto?). Quindi, per questo esempio, Abracadabraè lo spazio dei nomi predefinito. Potrei all'interno della mia MyTypeWithNamespacesclasse aggiungere un prefisso dello Whoohoospazio dei nomi per lo spazio dei nomi in questo modo:

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

Ora, nella mia definizione di classe, ho indicato che l' <Label/>elemento si trova nello spazio dei nomi "urn:Whoohoo", quindi non ho bisogno di fare altro. Quando ora serializzo la classe usando il mio codice di serializzazione sopra invariato, questo è l'output:

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

Poiché si <Label>trova in uno spazio dei nomi diverso dal resto del documento, deve, in qualche modo, essere "decorato" con uno spazio dei nomi. Si noti che non ci sono ancora xsie xsdspazi dei nomi.


Questo termina la mia risposta all'altra domanda. Ma volevo assicurarmi di aver risposto alla domanda del PO sull'uso di nessuno spazio dei nomi, poiché ritengo di non averlo ancora affrontato. Supponiamo che faccia <Label>parte dello stesso spazio dei nomi del resto del documento, in questo caso urn:Abracadabra:

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

Il tuo costruttore sembrerebbe come nel mio primo esempio di codice, insieme alla proprietà pubblica per recuperare lo spazio dei nomi predefinito:

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

Successivamente, nel tuo codice che utilizza l' MyTypeWithNamespacesoggetto per serializzarlo, lo chiameresti come ho fatto sopra:

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

E lo XmlSerializersputare indietro lo stesso XML mostrato immediatamente sopra senza spazi dei nomi aggiuntivi nell'output:

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

Per completezza, forse dovresti includere qui la risposta giusta invece di fare semplicemente riferimento ad essa, e sono anche interessato a sapere come concludi che si tratta di un "comportamento non supportato".
Dave Van den Eynde,

1
Sono venuto di nuovo qui per verificarlo, dato che è la spiegazione più semplice che ho trovato. Grazie @fourpastmidnight
Andre Albuquerque,

2
Non capisco, per la risposta finale del tuo OP, stai ancora usando uno spazio dei nomi durante la serializzazione "urna: Abracadabra" (costruttore), perché non è incluso nell'output finale. L'OP non dovrebbe usare: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
dparkar,

2
Questa è la risposta corretta, sebbene non sia la più votata. La cosa onky che non ha funzionato per me è stata la XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);sostituzione var xtw = XmlTextWriter.Create(memStm, xws);.
Leonel Sanches da Silva,

1
È da un po 'che non scrivo questa risposta. XmlTextWriter.Createrestituisce XmlWriterun'istanza (astratta?) . Quindi @ Preza8 è corretto, perderesti la possibilità di impostare altre XmlTextWriterproprietà specifiche (almeno, non senza il down-casting), quindi il cast specifico 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 ... voi ragazzi siete ribelli. Dice esplicitamente a msdn.microsoft.com/en-us/library/… che non puoi farlo.
Ralph Lavelle,

Bool Yah! (Per superare ciò che ms dice non puoi fare)
granadaCoder

3
Non sono sicuro del perché sia ​​"non supportato", ma questo fa esattamente quello che volevo.
Dan Bechard,

8
Questa risposta genera spazi dei nomi "xmlns: d1p1" e "xmlns: q1". Cos'è questo?
Leonel Sanches da Silva,

2
Bene, questo codice funziona per serializzazioni davvero molto semplici, senza altre definizioni di spazio dei nomi. Per più definizioni dello spazio dei nomi, la risposta operativa è quella accettata.
Leonel Sanches da Silva,

6

Esiste un'alternativa: è possibile fornire un membro di tipo XmlSerializerNamespaces nel tipo da serializzare. Decoralo con l' attributo XmlNamespaceDeclarations . Aggiungi i prefissi e gli URI dello spazio dei nomi a quel membro. Quindi, qualsiasi serializzazione che non fornisce esplicitamente un XmlSerializerNamespaces utilizzerà il prefisso dello spazio dei nomi + le coppie URI che hai inserito nel tuo tipo.

Esempio di codice, supponiamo che questo sia il tuo tipo:

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

Puoi farlo:

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

Ciò significa che qualsiasi serializzazione di quell'istanza che non specifica il proprio set di coppie prefisso + URI utilizzerà il prefisso "p" per lo spazio dei nomi "urn: mycompany.2009". Ometterà anche gli spazi dei nomi xsi e xsd.

La differenza qui è che stai aggiungendo gli spazi XmlSerializerNames al tipo stesso, invece di impiegarlo esplicitamente in una chiamata a XmlSerializer.Serialize (). Ciò significa che se un'istanza del tuo tipo è serializzata dal codice che non possiedi (ad esempio in uno stack di servizi Web) e tale codice non fornisce esplicitamente uno spazio dei nomi XmlSerializer, tale serializzatore utilizzerà gli spazi dei nomi forniti nell'istanza.


1. Non vedo la differenza. Stai ancora aggiungendo lo spazio dei nomi predefinito a un'istanza di XmlSerializerNamespaces.
Dave Van den Eynde,

3
2. Questo inquina di più le classi. Il mio obiettivo non è usare un certo spazio dei nomi, il mio obiettivo non è affatto usare gli spazi dei nomi.
Dave Van den Eynde,

Ho aggiunto una nota sulla differenza tra questo approccio e quello di specificare gli spazi XmlSerializerNames solo durante la serializzazione.
Cheeso,

0

Sto usando:

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

Per ottenere il seguente XML:

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

Se non desideri lo spazio dei nomi, imposta DEFAULT_NAMESPACE su "".


Sebbene questa domanda abbia più di 10 anni, il punto era allora di avere un corpo XML che non contenesse alcuna dichiarazione di spazio dei nomi.
Dave Van den Eynde il

1
Se aggiungo la mia risposta a una domanda di 10 anni, è perché la risposta accettata è più lunga da leggere rispetto alla Bibbia nella sua edizione completa.
Maxence,

E la risposta più votata promuove un approccio (spazio dei nomi vuoto) che non è raccomandato.
Maxence

Non posso farci niente. Posso solo fare della risposta accettata quella che ritengo più corretta.
Dave Van den Eynde,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.