Gestione dei namespace in LINQ to XML
Data: 5/7/2009
Categoria: ASP.NET 3.5
Codice d'esempio


Nota: questo articolo era nato come tip per il sito LINQItalia.com. Purtroppo però non mi sono accorto che Andrea Zani aveva già proposto il medesimo argomento... Lo riporto qui per completezza. Buona lettura !


La stragrande maggioranza dei file XML con cui ogni sviluppatore ha a che fare durante il lavoro di tutti i giorni vede, al proprio interno, un utilizzo massiccio dei namespace. Tali namespace sappiamo benissimo che servono ad identificare univocamente un gruppo di nomi (utilizzati da elementi ed attributi) all’interno della propria struttura XML.
Chiaramente, ogni linguaggio che è in grado di effettuare delle operazioni di lettura e di scrittura in sintassi XML deve avere le funzioni per la gestione dei namespace e sicuramente, LINQ to XML non poteva essere da meno. Prendiamo come esempio un file XML così definito:

<?xml version="1.0"?>

<books>
<book xmlns="urn:loc.gov:books" xmlns:isbn="urn:ISBN:0-395-36341-6">
<title>ASP.NET 3.5 per tutti</title>
<isbn:number>1568491379</isbn:number>
</book>
<book xmlns="urn:loc.gov:books" xmlns:isbn="urn:ISBN:0-395-36341-6">
<title>Pocket C#</title>
<isbn:number>1786223456</isbn:number>
</book>
</books>

Se pensassimo di scrivere delle query LINQ di selezione, ad esempio per estrappolare tutti i valori di ogni elemento di tipo “title”, oppure se dovessimo utilizzare altri metodi per la scorrimento dell’albero proprio di tale file XML, incapperemmo in una spiacevole sorpresa. Infatti, un utilizzo della sintassi LINQ to XML del genere:

foreach (var item in doc.Descendants("book"))

{
Console.WriteLine("{0}", item.Value);
}

oppure:

var result = from r in doc.Descendants()

where r.Name == "title"
select r;
foreach (var item in result)
{
Console.WriteLine("Valore titolo: {0}", item.Value);
}

non porta ad alcun risultato utile, in quanto le selezioni non vengono fatte tenendo conto dei relativi namespace di ogni elemento.
Per permetterci una corretta selezione delle informazioni proprie di un file XML del genere, ci vengono in aiuto due classi del .NET Framework 3.5:
  • La classe XName – che rappresenta un nome generico proprio di un elemento o di un attributo
  • La classe XNamespace – che rappresenta un singolo spazio di nomi di un file XML.
Entrembe, appartenenti al namespace System.Xml.Linq, possono essere utilizzate per selezionare correttamente i nodi dell’albero XML visto in precedenza.

XName book = XName.Get("book", "urn:loc.gov:books");

foreach (var item in doc.Descendants(book))
{
//selezione corretta XName number = XName.Get("number", "urn:ISBN:0-395-36341-6");
if (item.Element(number) != null)
Console.WriteLine("Number: {0}", item.Element(number).Value);
XName title = XName.Get("title", "urn:loc.gov:books");
if (item.Element(title) != null)
Console.WriteLine("Title: {0}", item.Element(title).Value);
}
var result = from r in doc.Descendants()
where r.Name == XName.Get("title", "urn:loc.gov:books")
select r;
foreach (var item in result)
{
Console.WriteLine("Valore titolo: {0}", item.Value);
}

Così come per gli elementi, possiamo selezionare anche specifici attributi salvati con altrettanto specifici spazi di nomi, sempre attraverso l’utilizzo della medesima tecnica.
Allo stesso modo, in fase di creazione di un file XML, possiamo utilizzare le classi XName e XNamespace, per definire spazi di nomi personalizzati all’interno della struttura ad albero che abbiamo la necessità di creare.

XNamespace aspitalia = "urn:books.aspitalia.com";

XName books = XName.Get("books", "urn:aspitalia.com");
XElement xml = new XElement(books,
new XElement(aspitalia + "book",
new XAttribute("title", "ASP.NET 3.5 per tutti"),
new XElement("authors",
new XElement("author", "Daniele Bochicchio"),
new XElement("author", "Cristian Civera"),
new XElement("author", "Stefano Mostarda"),
new XElement("author", "Riccardo Golia")
)
),
new XElement(aspitalia + "book",
new XAttribute("title", "Pocket C#"),
new XElement("authors",
new XElement("author", "Giuseppe Marchi")
)
)
);
xml.Save(Path.Combine(Environment.CurrentDirectory, "namespaces.xml"));

Come abbiamo potuto vedere, la classe XElement vuole può ricevere come parametro il nome intero dell’elemento, rappresentato dalla combinazione di namespace e nome.
Così facendo, siamo in grado di leggere e scrivere nodi e attributi XML utilizzando namespace specifici, che rendono possibile l’identificazione univoca dei nomi.