1. Introduzione
Sai già che .NET può serializzare e deserializzare oggetti in XML di default, basandosi su campi e proprietà pubbliche. Ma quando vuoi ottenere un XML ordinato, compatibile con altri programmi/standard, o quando non vuoi includere dettagli interni, serve una configurazione più precisa.
In C# per questo ci sono gli attributi di serializzazione — delle "etichette" che applichiamo a classi, proprietà o campi. Grazie a questi attributi possiamo controllare praticamente tutto: quali campi finiscono nell'XML, quali vengono ignorati, come si chiama un elemento, se diventa un attributo, avere strutture complesse e persino l'ordine degli elementi!
Immagina la serializzazione di default come il completamento automatico di un modulo: nome-cognome-secondoNome nelle sue caselle, tutto il resto dove capita. Con gli attributi sei come un funzionario esperto: decidi tu dove va ogni riga, cosa saltare, cosa evidenziare e cosa mettere nella sezione segreta.
Mappatura tra oggetto e XML
| Classe/Proprietà | Attributo | Risultato in XML |
|---|---|---|
|
Classe UserList | |
|
|
|
|
|
id="..." (attributo) |
|
|
|
|
|
|
|
[XmlIgnore] (vedi RegisteredAtString) | - |
|
|
|
|
|
|
2. Panoramica dei principali attributi di serializzazione XML
Passiamo in rassegna gli attributi più popolari che gli sviluppatori .NET usano da anni per decorare le loro classi!
Attributo [XmlElement] — Nome dell'elemento
Cambia il nome dell'elemento in XML o marca la proprietà/campo come elemento XML.
public class Person
{
[XmlElement("FullName")]
public string Name { get; set; }
}
XML:
<Person>
<FullName>Vasiliy Petrov</FullName>
</Person>
Se non specifichi l'attributo — per default il nome dell'elemento corrisponderà al nome della proprietà/campo.
Attributo [XmlAttribute] — Serializzazione come attributo XML
Permette di serializzare una proprietà/campo come attributo XML, non come elemento.
public class Person
{
[XmlAttribute("id")]
public int Id { get; set; }
}
XML:
<Person id="123"></Person>
Nei reali API XML spesso si usano attributi per identificatori, date, flag, ecc.
Attributo [XmlIgnore] — Escludere campo/proprietà
Il sogno di ogni paranoico: escludere completamente una proprietà dalla serializzazione e deserializzazione! Tutto ciò che è marcato con questo attributo semplicemente non finirà nell'XML finale.
public class Person
{
[XmlIgnore]
public string InternalNote { get; set; }
}
XML: (la proprietà InternalNote non esiste qui e non esisterà)
Attributi [XmlArray] e [XmlArrayItem] — controllo sulle collezioni
Attributi speciali per array e collezioni. [XmlArray] imposta il nome del wrapper esterno, mentre [XmlArrayItem] imposta il nome di ogni elemento.
public class Person
{
[XmlArray("Phones")]
[XmlArrayItem("Phone")]
public string[] PhoneNumbers { get; set; }
}
XML:
<Person>
<Phones>
<Phone>+12951234567</Phone>
<Phone>+12876543210</Phone>
</Phones>
</Person>
Attributo [XmlRoot] — Nome dell'elemento radice
Marca la classe per cambiare il nome dell'elemento radice nell'XML.
[XmlRoot("User")]
public class Person
{
public string Name { get; set; }
}
XML:
<User>
<Name>Anna</Name>
</User>
Attributo [XmlText] — Serializzazione nel contenuto testuale dell'elemento
A volte (o per specifiche) vuoi che il valore di una proprietà sia semplicemente il testo interno dell'elemento, non un sottoelemento o un attributo.
public class Note
{
[XmlText]
public string Content { get; set; }
}
XML:
<Note>Chiamare la nonna!</Note>
Attributi [XmlNamespaceDeclarations], [XmlElement(Type = ...)]
Per scenari più avanzati (es. serializzazione con namespace, supporto dell'ereditarietà, ecc.) ci sono anche attributi più specifici. Dai un'occhiata alla documentazione ufficiale sulla serializzazione XML per l'elenco completo.
3. Pratica: miglioriamo la serializzazione con un esempio reale
Passo dopo passo, miglioriamo la nostra app didattica fino a ottenere una "serializzazione XML di livello premium" che piaccia anche all'ispettore XML più severo.
Classe base senza attributi
Supponiamo di avere questa classe utente:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string[] Emails { get; set; }
public DateTime RegisteredAt { get; set; }
public string Password { get; set; }
}
La serializzazione di default produrrà un XML simile (e la data sarà nel suo formato):
<User>
<Id>42</Id>
<Name>Ivan Ivanov</Name>
<Emails>
<string>user@mail.com</string>
<string>admin@site.org</string>
</Emails>
<RegisteredAt>2024-07-01T00:00:00</RegisteredAt>
<Password>qwerty</Password>
</User>
Cosa non va:
- Meglio non serializzare affatto la password.
- Vogliamo la data come attributo in un formato leggibile.
- Vogliamo gli Emails in una forma più pulita.
- Vogliamo l'elemento radice <Person>.
Aggiungiamo le configurazioni tramite attributi
[XmlRoot("Person")]
public class User
{
[XmlAttribute("id")]
public int Id { get; set; }
[XmlElement("FullName")]
public string Name { get; set; }
[XmlArray("EMails")]
[XmlArrayItem("Email")]
public string[] Emails { get; set; }
[XmlAttribute("registered")]
public string RegisteredAtString
{
get => RegisteredAt.ToString("yyyy-MM-dd");
set => RegisteredAt = DateTime.Parse(value);
}
[XmlIgnore]
public string Password { get; set; }
[XmlIgnore]
public DateTime RegisteredAt { get; set; }
}
Spiegazione dei dettagli:
- Usiamo [XmlAttribute("registered")] per serializzare la data come attributo.
- Per serializzare non il tipo DateTime ma la sua rappresentazione testuale, aggiungiamo una proprietà separata RegisteredAtString, mentre il campo reale lo nascondiamo con [XmlIgnore].
- Nascondiamo la password con [XmlIgnore].
- L'array di email è configurato in modo che il tag esterno sia <EMails> e ogni email sia <Email>.
Risultato:
<Person id="42" registered="2024-07-01">
<FullName>Ivan Ivanov</FullName>
<EMails>
<Email>user@mail.com</Email>
<Email>admin@site.org</Email>
</EMails>
</Person>
4. Lavorare con oggetti annidati
XML rende al meglio quando ci sono oggetti annidati (es. utente e i suoi indirizzi).
public class Address
{
[XmlAttribute]
public string City { get; set; }
[XmlText]
public string Details { get; set; }
}
public class User
{
// ...altre proprietà, vedi sopra...
public Address[] Addresses { get; set; }
}
Aggiungiamo la configurazione per l'array di indirizzi:
[XmlArray("UserAddresses")]
[XmlArrayItem("Address")]
public Address[] Addresses { get; set; }
Allora l'XML sarà così:
<Person id="42" registered="2024-07-01">
<FullName>Ivan Ivanov</FullName>
<EMails>
<Email>user@mail.com</Email>
</EMails>
<UserAddresses>
<Address City="Neon city">ul. Zamkovaja, d.1</Address>
<Address City="North Cave">ul. Krajnyaja d.12</Address>
</UserAddresses>
</Person>
Si vede che per ogni <Address> city è un attributo, mentre l'indirizzo vero è il testo interno.
5. Errori comuni e dettagli
Se serializzi un oggetto complesso e non vedi i cambiamenti nell'XML che ti aspettavi — probabilmente hai dimenticato di applicare correttamente un attributo. A volte le proprietà/campi restano senza marca; .NET quindi userà il comportamento di default.
Un'altra trappola è il nome della collezione. Se la collezione (es. array di stringhe) non è marcata né con [XmlArray] né con [XmlArrayItem], gli elementi verranno scritti con il tag string (come nell'esempio iniziale). Per evitare questo, imposta sempre esplicitamente i nomi dei tag con gli attributi.
Stai attento con le proprietà "getter-only" (solo lettura) — queste non vengono deserializzate di default. Cerca di avere un set pubblico quando possibile.
Alcuni tipi (per esempio, dizionari o interfacce) non vengono serializzati dal XmlSerializer standard, o richiedono un trattamento speciale.
GO TO FULL VERSION