Vous êtes sur la page 1sur 34

11-Cap07 7-04-2004 14:20 Pagina 187

7 Applicazioni pratiche
di XML con .NET
Introduzione
ASP.NET e XML
Inviare XML via HTTP
ADO.NET e XML
Generare ASPX via XSLT
Ottenere XML da SQL Server
Inviare XML a SQL Server
11-Cap07 7-04-2004 14:20 Pagina 188

Programmare con XML I Portatili

Introduzione
Dopo aver visto da un punto di vista teorico quali possibilit
ci mette a disposizione il Framework .NET, in questo capito-
lo vedremo come sfruttarle per migliorare la qualit e le fun-
zionalit delle nostre applicazioni. Ci occuperemo di soluzio-
ni Web ma anche di applicazioni Windows e di integrazione
con SQL Server. Mi piace pensare a questo capitolo come a
un insieme di piccoli consigli e trucchi per sfruttare realmen-
te XML con .NET. Lo vedo come una fonte dalla quale
attingere anche dopo la lettura del libro per prendere qual-
che spunto e qualche pezzetto di codice utile. Non si tratta
di un capitolo introduttivo, ma deve essere letto dopo aver
preso un po di padronanza sia di XML e XSLT, che di
.NET come ambiente di sviluppo. Se leggendolo adesso non
risulta troppo chiaro... non fa nulla, conviene lasciarlo da
parte e riprenderlo dopo qualche mese di lavoro con XML e
.NET: tutto sar pi chiaro e, soprattutto, pi utile.

ASP.NET e XML
Anche se da sempre ribadisco il concetto che XML non
una tecnologia per il Web, eccoci a vedere come sfruttarlo al
meglio con ASP.NET. Nel capitolo precedente abbiamo
visto quali istruzioni e/o controlli servono per trasformare
XML in HTML, quindi non mia intenzione riprendere
quei concetti. Preferisco avere un approccio assolutamente
pratico, orientato a qualche soluzione reale, quindi vedremo
insieme un esempio di pagina ASPX in grado di ricevere un
file XML in upload, validarlo con uno schema XSD e salvar-
lo su disco. Inoltre, vedremo come costruire pagine ASPX
che restituiscono XML o che ricevono XML in POST. Nelle
sezioni successive, a proposito di SQL Server e XML, ritor-
neremo su questi concetti per eseguire direttamente lupload
dei dati ricevuti in una o pi tabelle di SQL Server.

188
11-Cap07 7-04-2004 14:20 Pagina 189

7: Applicazioni pratiche di XML con .NET

Consideriamo una pagina ASPX che abbia la sua FORM


strutturata come la seguente:
<form id=frmUpload method=post runat=server>
<input type=file runat=server
id=xmlInstance><br>
<asp:Button ID=uploadInstance Runat=server
Text=Upload dei dati!></asp:Button><br>
<asp:Label ID=validationResult
Runat=server></asp:Label><br>
<asp:Label ID=sqlResult
Runat=server></asp:Label>
</form>

Dal momento che abbiamo definito un tag INPUT di tipo


FILE con il classico attributo RUNAT=SERVER di
ASP.NET 1, nel codice lato server avremo modo di leggere
sotto forma di Stream il file inviato dallutente finale di que-
sta pagina. Siccome sappiamo che sia XmlDocument che
XmlTextReader possono leggere da uno Stream, ecco come
caricare in memoria un eventuale documento XML inviato:
if (xmlInstance.PostedFile.ContentLength > 0)
{
XmlDocument docXml = new XmlDocument();
docXml.Load(xmlInstance.PostedFile.InputStream);

Response.Write(docXml.DocumentElement.InnerText);
// ecc.
}

o in modo equivalente, qualora ci servisse un XmlReader:


if (xmlInstance.PostedFile.ContentLength > 0)
{
XmlTextReader reader = new

1
Per apprendere le nozioni di base sul funzionamento di ASP.NET consi-
glio di leggere il libro per la certificazione MCAD/MCSD.NET Sviluppare
Applicazioni Web con Microsoft Visual Basic .NET e Microsoft Visual C#
.NET MCAD/MCSD Training (ISBN: 88-8331-415-8), edito da Mondadori
Informatica nella sua versione italiana.

189
11-Cap07 7-04-2004 14:20 Pagina 190

Programmare con XML I Portatili

XmlTextReader
(xmlInstance.PostedFile.InputStream);

while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
break;
case XmlNodeType.Text:
break;
// ecc.
}
}
}

In entrambi i casi, vediamo che il contenuto del file spedito


accessibile, se esiste, attraverso la propriet PostedFi-
le.InputStream delloggetto di tipo HtmlInputFile. Sarebbe
per utile verificare se siamo realmente in presenza di un
documento XML, magari anche validandolo secondo uno
schema. Per questo ci vengono in aiuto le classi XmlSchema e
XmlValidatingReader viste nel capitolo precedente:
if (xmlInstance.PostedFile.ContentLength > 0)
{
XmlValidatingReader valReader =
new XmlValidatingReader(
new XmlTextReader
(xmlInstance.PostedFile.InputStream));

valReader.Schemas.Add
(http://schemas.paolo.com/Order,
new XmlTextReader
(Server.MapPath(Order.xsd)),
new XmlUrlResolver());
valReader.ValidationEventHandler +=
new ValidationEventHandler
(valReader_ValidationEventHandler);

while (valReader.Read()) {}
}

190
11-Cap07 7-04-2004 14:20 Pagina 191

7: Applicazioni pratiche di XML con .NET

Immaginiamo quindi di avere un ordine XML strutturato


come il seguente:
<?xml version=1.0 encoding=utf-8 ?>
<xsd:schema id=order
targetNamespace=http://schemas.paolo.com/Order
elementFormDefault=qualified
xmlns=http://schemas.paolo.com/Order
xmlns:xsd=http://www.w3.org/2001/XMLSchema>

<xsd:element name=order>
<xsd:complexType>
<xsd:sequence>
<xsd:element name=items>
<xsd:complexType>
<xsd:sequence>
<xsd:element name=item
type=itemType
maxOccurs=unbounded />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name=customer
type=customerType />
</xsd:sequence>
</xsd:complexType>
</xsd:element>

<xsd:complexType name=itemType>
<xsd:attribute name=idProduct type=
xsd:int use=required />
<xsd:attribute name=euroPrice type=
xsd:decimal use=required />
<xsd:attribute name=quantity type=
xsd:int use=required />
</xsd:complexType>

<xsd:complexType name=customerType>
<xsd:attribute name=idCustomer use=required>
<xsd:simpleType>
<xsd:restriction base=xsd:string>
<xsd:pattern value=
[A-Z]{2}\d{2}-\d{3} />
</xsd:restriction>

191
11-Cap07 7-04-2004 14:20 Pagina 192

Programmare con XML I Portatili

</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name=fullName type=xsd:string
use=required />
<xsd:attribute name=email type=xsd:string
use=required />
</xsd:complexType>

</xsd:schema>

Una cui istanza per esempio:


<?xml version=1.0 encoding=utf-8 ?>
<order xmlns=http://schemas.paolo.com/Order>
<items>
<item idProduct=1 euroPrice=10.20
quantity=10 />
<item idProduct=2 euroPrice=15.35
quantity=25 />
<item idProduct=3 euroPrice=12.85
quantity=20 />
</items>
<customer idCustomer=PP01-003
email=paolo@devleap.it
fullName=Paolo Pialorsi />
</order>

Potremmo essere interessati a riceverlo in upload da unin-


terfaccia Web, il caso del quale abbiamo appena visto il
codice, ma potremmo anche essere interessati a fornire un
accesso diretto, magari autenticato, via HTTP a un nostro
client Windows. Verso la fine del libro vedremo cosa sono i
Web Service, in questo momento preoccupiamoci di otte-
nere il nostro risultato con gli strumenti gi a nostra dispo-
sizione. Il codice della pagina ASPX di ricezione dovr leg-
gere dallo Stream di richiesta, che si ottiene attraverso la
propriet InputStream delloggetto Request di ASP.NET, il
contenuto XML speditole, dovr validarlo secondo lo sche-
ma XSD da noi definito e, qualora il contenuto sia valido,
inserire nellipotetico sistema informativo lordine, resti-
tuendo un ID univoco dellordine ricevuto e alcuni dati

192
11-Cap07 7-04-2004 14:20 Pagina 193

7: Applicazioni pratiche di XML con .NET

salienti, come il codice cliente e limporto totale transato.


La nostra pagina ASPX fornir sempre una risposta in for-
mato XML, anche in caso di errore.
Vediamo passo dopo passo il codice per raggiungere questo
obiettivo. Innanzitutto dobbiamo caricare il file XML rice-
vuto in POST e validarlo:
Response.ContentType = text/xml;

if ((Request.InputStream.Length > 0) &&


(Request.ContentType == text/xml))
{
// Validiamo il documento XML con il suo XSD
XmlValidatingReader valReader =
new XmlValidatingReader(
new XmlTextReader(Request.InputStream));

String nsURI = http://schemas.paolo.com/Order;

XmlTextReader schemaReader =
new XmlTextReader
(Server.MapPath(../order.xsd));
try
{
valReader.Schemas.Add(nsURI, schemaReader,
new XmlUrlResolver());
}
finally
{
schemaReader.Close();
}

Ci appoggiamo a una variabile XmlTextReader dedicata alla


lettura dello schema per non tenere bloccato il file su disco,
per questo inseriamo la lettura in un blocco try...finally 2.

2
Per ulteriori dettagli sul rilascio delle risorse in .NET, consiglio di leggere i
primi capitoli del libro di Marco Russo .NET Full Contact Common
Language Runtime (ISBN: 88-8331-518-9), edito da Mondadori Informatica.

193
11-Cap07 7-04-2004 14:20 Pagina 194

Programmare con XML I Portatili

Successivamente, invece di leggere il documento semplice-


mente eseguendo un ciclo sul metodo Read delloggetto
XmlValidatingReader, trovo pi furbo in questo particolare
caso utilizzare un XPathDocument, al quale faremo poi rife-
rimento per cercare nellordine ricevuto le informazioni di
nostro interesse.
// da usare per cercare i nodi di nostro interesse
XPathDocument xpathDoc = new
XPathDocument(valReader);
XPathNavigator orderNavigator =
xpathDoc.CreateNavigator();

// Definisco il Namespace XML di contesto


XmlNamespaceManager nsManager =
new XmlNamespaceManager
(orderNavigator.NameTable);
nsManager.AddNamespace(o, nsURI);

// Calcoliamo limporto totale dellordine


XPathExpression searchItems =
orderNavigator.Compile
(/o:order/o:items/o:item);
SetContext(nsManager);
searchItems.S
XPathNodeIterator items =
orderNavigator.Select(searchItems);

// Preparo le informazioni di conversione da


// decimal XSD a decimal .NET
Decimal orderEuroAmount = 0;
System.Globalization.NumberFormatInfo nfi =
new System.Globalization.NumberFormatInfo();
nfi.CurrencyDecimalSeparator = .;

while(items.MoveNext())
{
orderEuroAmount +=
Convert.ToInt32(items.Current.GetAttribute
(quantity, String.Empty)) *
Convert.ToDecimal(items.Current.GetAttribute
(euroPrice, String.Empty), nfi);
}

194
11-Cap07 7-04-2004 14:20 Pagina 195

7: Applicazioni pratiche di XML con .NET

// Leggiamo lidCustomer
XPathExpression getIdCustomer =
orderNavigator.Compile
(/o:order/o:customer/@idCustomer);
getIdCustomer.SetContext(nsManager);
String idCustomer = orderNavigator.Evaluate
(getIdCustomer).ToString();

Come abbiamo gi visto nel capitolo precedente, possiamo


creare un XPathNavigator partendo da un XPathDocument.
Listanza di XPathNavigator ci permetter di cercare con
delle regole XPath ad hoc i nodi da leggere. Dal momento
che il nostro ordine ha un Namespace XML associato,
dovremo informare le singole XPathExpression del fatto
che /o:order corrisponde al tag order nel Namespace
http://schemas.paolo.com/Order. Otteniamo questo
risultato creando unistanza di classe XmlNamespa-
ceManager, la quale si basa su una tabella di nomi relativa a
un documento, per noi la propriet NameTable del
XPathNavigator, e permette di aggiungere una lista di URI
di Namespace XML con i relativi prefissi di alias. Si noti che
durante la lettura del prezzo in euro di ciascun singolo ele-
mento item dobbiamo convertire il formato xsd:decimal, che
utilizza il punto (.) come separatore della parte decimale
dalla parte intera, nel formato Decimal di .NET, creando un
oggetto di tipo NumberFormatInfo 3 che utilizziamo nel chia-
mare il metodo statico 4 ToDecimal delloggetto Convert.

3
Credo che questa particolare porzione di esempio serva pi in generale
nello sviluppo di molte applicazioni .NET, tutte le volte che dobbiamo con-
vertire dei valori numerici da un formato a un altro. Generalmente, usare
trucchi come le Replace dei caratteri non mai particolarmente elegante n
tantomeno sicuro, per questo motivo ho volutamente creato una situazione
in cui potesse essere illustrata questa modalit di lavoro.
4
Per metodo statico, semplificando il concetto, si intende un metodo che
non legato a una particolare istanza della classe, ma che pu essere richia-
mato direttamente senza dover creare un apposito oggetto.

195
11-Cap07 7-04-2004 14:20 Pagina 196

Programmare con XML I Portatili

A questo punto, curioso osservare il codice che ho defini-


to per costruire sia la risposta positiva che quella negativa.
Infatti, abbiamo intenzionalmente due diverse soluzioni: la
risposta positiva costruita utilizzando un XmlTextWriter
che scrive direttamente sulla propriet OutputStream del-
loggetto Response di ASP.NET; la risposta in caso di errore
ottenuta con un XmlDocument.
La conferma dordine cos generata:
private void prepareXmlResponse(String idCustomer,
Decimal orderEuroAmount, Int32 idOrder)
{
XmlTextWriter xmlResponse =
new XmlTextWriter(Response.OutputStream,
System.Text.Encoding.UTF8);

xmlResponse.Formatting = Formatting.Indented;

xmlResponse.WriteStartDocument(true);
xmlResponse.WriteStartElement(or,
orderResponse,
http://schemas.paolo.com/OrderResponse);
xmlResponse.WriteAttributeString(idOrder,
idOrder.ToString());
xmlResponse.WriteAttributeString(idCustomer,
idCustomer);
xmlResponse.WriteAttributeString
(orderEuroAmount, orderEuroAmount.ToString());
xmlResponse.WriteEndElement();
xmlResponse.WriteEndDocument();
xmlResponse.Flush();
xmlResponse.Close();
}

Di nuovo rispetto a quanto mostrato nel capitolo precedente


c il fatto che utilizziamo un enconding personalizzato
(UTF-8) per essere in linea con lencoding della risposta pre-
definita di ASP.NET, inoltre chiediamo che la risposta XML

196
11-Cap07 7-04-2004 14:20 Pagina 197

7: Applicazioni pratiche di XML con .NET

sia indentata configurando la propriet Formatting al valore


Formatting.Indented. Infine nel creare il tag root, cio il
documentElement, invocando il metodo WriteStartElement
gli associamo un Namespace XML specifico.
Leventuale errore invece sar fornito come di seguito illu-
strato:
private void prepareXmlErrorResponse
(String errorMessage)
{
XmlDocument xmlResponse = new XmlDocument();

xmlResponse.AppendChild
(xmlResponse.CreateProcessingInstruction(
xml, version=1.0 encoding=UTF-8));

xmlResponse.AppendChild(xmlResponse.CreateElement
(error));
xmlResponse.DocumentElement.AppendChild(
xmlResponse.CreateTextNode(errorMessage));

xmlResponse.Save(Response.OutputStream);
}

Si notino nuovamente lenconding UTF-8 nella processing


instruction di prologo e linvocazione del metodo Save del
XmlDocument con destinazione lOutputStream della
Response.

Inviare XML via HTTP


Affinch sia possibile chiudere il cerchio dellesempio visto
nella sezione precedente, dobbiamo capire come inviare tra-
mite HTTP, tipicamente con un POST, un documento XML,
da un client .NET che non sia necessariamente unapplicazio-
ne Web verso la pagina ASPX appena scritta. Pensiamo, per

197
11-Cap07 7-04-2004 14:20 Pagina 198

Programmare con XML I Portatili

esempio a unapplicazione Windows Forms 5 che presenta un


pulsante di upload dellordine. Per eseguire richieste HTTP
da .NET possiamo appoggiarci alle classi HttpWebRequest e
HttpWebResponse del Namespace System.Net.
Si tratta di classi di alto livello, derivate dalle classi base
astratte WebRequest e WebResponse, che internamente uti-
lizzano classi di pi basso livello della System.Net.
// Inizializzo la richiesta
HttpWebRequest request =
WebRequest.Create(
(HttpWebRequest)W
new Uri(http://localhost/SecuredUpload/
SecuredOrderUpload.aspx));
request.ContentType = text/xml;
request.Credentials = new NetworkCredential
(Paolo, Password, Dominio);
request.Method = POST;
request.UserAgent = Paolo Windows Forms Client;

// Carico il DOM che conterr lXML da inviare


XmlDocument requestXmlData = new XmlDocument();
requestXmlData.Load(order.xml);

// Salvo il DOM sullo Stream della richiesta


GetRequestStream());
requestXmlData.Save(request.G
request.GetRequestStream().Close();

// Invio la richiesta
HttpWebResponse response =
(HttpWebResponse)request.GetResponse();

// Leggo la risposta XML


XmlDocument responseXmlData = new XmlDocument();

5
Con applicazione Windows Forms intendo unapplicazione Windows basa-
ta su finestre e realizzata con .NET. Si utilizza questa espressione in quanto le
classi .NET da utilizzare per scrivere simili applicazioni hanno come
Namespace radice System.Windows.Forms. Per una panoramica introduttiva
allo sviluppo di applicazioni Windows Forms con .NET, consiglio la lettura del
libro Sviluppare Applicazioni Windows con Microsoft Visual Basic .NET e
Microsoft Visual C# .NET MCAD/MCSD Training (ISBN: 88-8331-419-0).

198
11-Cap07 7-04-2004 14:20 Pagina 199

7: Applicazioni pratiche di XML con .NET

GetResponseStream());
responseXmlData.Load(response.G
response.Close();

// La salvo sulla console


responseXmlData.Save(Console.Out);

Dobbiamo creare un oggetto HttpWebRequest; di questo


serve impostare il contenuto della richiesta, lHTTP-
Method e opzionalmente le nostre credenziali di rete e lo
UserAgent 6. Quindi possiamo salvare un XmlDocument
allinterno dello Stream di richiesta delloggetto
HttpWebRequest. Si noti la chiusura esplicita dello Stream
di richiesta, per evitare che loggetto HttpWebRequest
rimanga in attesa di altri byte da inviare al server. Il metodo
GetResponse, del quale esiste anche la versione asincrona
Begin/EndGetRequest 7, ci restituisce un oggetto HttpWeb-
Response, del quale possiamo interrogare il metodo
GetResponseStream, per leggerne il contenuto dentro a un
altro XmlDocument che conterr lalbero dei nodi della
risposta. Anche nel caso della risposta ricordiamoci di chiu-
dere lo Stream. Dovremmo prevedere nel codice gli oppor-
tuni blocchi try...catch ove necessario, come ho fatto nel
codice degli esempi allegati a questo libro.

ADO.NET e XML
Un altro ambito di .NET nel quale XML fa sentire la sua
presenza quello dellaccesso ai dati. Esistono decine di

6
Lo UserAgent la firma che identifica univocamente un client HTTP nei
confronti di un Web Server. Tutti i browser Web hanno un loro nome che iden-
tifica univocamente loro e il sistema operativo sul quale sono installati. Di solito
questa firma viene detta UserAgent e permette alle applicazioni Web di cambia-
re il loro comportamento, a seconda del tipo di client che si collega.
7
Anche le chiamate asincrone sono argomento che esula dagli obiettivi di
questo libro. Per approfondire largomento consiglio ancora la lettura del
libro di Marco Russo, che ho citato in precedenza.

199
11-Cap07 7-04-2004 14:20 Pagina 200

Programmare con XML I Portatili

libri 8 e articoli disponibili sulla rete che descrivono come


leggere e scrivere documenti XML con i DataSet 9. Di
ADO.NET tratteremo solo alcuni concetti chiave, non
essendo questo libro la sede per vederne tutti i dettagli. Il
DataSet espone dei membri di istanza che sicuramente,
anche solo leggendo il loro nome, hanno a che fare con
XML. Troviamo innanzitutto GetXml e GetXmlSchema,
InferXmlSchema, ReadXml e ReadXmlSchema e infine
WriteXml e WriteXmlSchema. Sono tutti metodi utilizzabili
per ottenere sotto forma di XML il contenuto, anche in ter-
mini di struttura XSD, di un DataSet, piuttosto che per leg-
gere dellXML e vederlo come se fosse un DataSet.
Immaginiamo di voler salvare un DataSet in XML; il seguen-
te codice fa al caso nostro:
SqlConnection cn = new SqlConnection(
server=localhost;database=pubs;
integrated security=SSPI;);
SqlDataAdapter da = new SqlDataAdapter(
SELECT TOP 2 au_fname, au_lname,
contract FROM Authors, cn);
DataSet ds = new DataSet(dsPubs);

using(cn)
{
da.Fill(ds, Authors);
}

ds.WriteXml(dsPubs.xml,
XmlWriteMode.IgnoreSchema);

8
Consiglio assolutamente la lettura di Essential ADO .NET di Bob
Beauchemin (ISBN: 88-8331-468-9) e di ADO .NET Full Contact di
Silvano Coriani (ISBN: 88-8331-541-3). Nel secondo libro, in particolare,
presente anche un mio contributo in merito allutilizzo di DataSet e XML
nello scambio di dati tramite Web Service.
9
Il DataSet loggetto che in ADO.NET preposto a contenere i dati letti
da una o pi fonti di dati. Si tratta di un contenitore di tabelle, viste, relazio-
ni e vincoli di integrit sui dati.

200
11-Cap07 7-04-2004 14:20 Pagina 201

7: Applicazioni pratiche di XML con .NET

I possibili valori che pu assumere il parametro XmlWriteMo-


de sono di seguito elencati.

Valore Significato
DiffGram Genera un formato XML particolare,
arricchito di informazioni sullo stato delle
singole righe, per poter poi ricostruire le
modifiche effettuate sui dati ed eseguire
quindi degli aggiornamenti batch.
IgnoreSchema Salva solo il contenuto puro del DataSet,
senza generare alcuna informazione in
merito al suo schema o alle modifiche
sui dati.
WriteSchema Salva il contenuto del DataSet e il suo
schema allinterno di un unico docu-
mento. Non mantiene informazioni sui
cambiamenti avvenuti alle singole righe.
Volendolo poi ricaricare in memoria, possiamo utilizzare:
DataSet ds = new DataSet();
ds.ReadXml(dsPubs.xml, XmlReadMode.ReadSchema);

foreach(DataRow row in ds.Tables[0].Rows)


{
Console.WriteLine({0} {1} => contratto: {2},
row[au_fname], row[au_lname],
row[contract]);
}

Laspetto interessante del poter generare una rappresentazione


XML di un DataSet che in questo modo potremmo creare
dei client Windows Forms in grado di salvare temporaneamen-
te su disco i dati con i quali stanno lavorando, per esempio
prima di spegnere il PC o prima di metterlo in stand-by, per
poi ricercarli successivamente come se non fosse mai accaduto
nulla nel frattempo. Pensiamo a tutte quelle applicazioni che
richiedono la presenza di dati di servizio sul client, anche

201
11-Cap07 7-04-2004 14:20 Pagina 202

Programmare con XML I Portatili

quando il client non connesso alla rete aziendale, per esem-


pio una lista di contatti, le disponibilit giornaliere di prodotti,
un elenco di categorie merceologiche, ecc. In queste situazioni,
il DataSet pu essere visto come il contenitore ideale dei dati
disconnessi. In aggiunta possiamo anche valutare la possibilit
di rendere modificabili questi dati, perch il DataSet sar in
grado di capire quali modifiche lutente ha apportato agli stessi
e, con la stessa logica gi vista con il Recordset ADODB (capi-
tolo 5), eseguire un aggiornamento batch 10.
Spesso risulta comodo anche serializzare un DataSet con
XmlSerializer (capitolo 6). Con XmlSerializer infatti vengo-
no salvati in un unico file XML sia il suo schema XSD che
il suo stato, sotto forma di DiffGram. Il fatto di poter suc-
cessivamente deserializzare lo stream in un oggetto DataSet,
magari anche su un altro PC, rende interessante questa tec-
nica. Non a caso il motore dei Web Service di ASP.NET,
che vedremo nei prossimi capitoli, usa un XmlSerializer per
(de)serializzare i DataSet e in generale qualsiasi altro ogget-
to da trasferire sulla rete.
Il DataSet pu inoltre essere convertito al volo in un oggetto
XmlDocument, in realt si usa la classe XmlDataDocument
che deriva da XmlDocument, per gestirne il contenuto solo
come insieme di nodi e non pi come lista di tabelle fatte di
righe e colonne. Pensate alle potenzialit di questo approc-
cio: in funzione di ci che mi risulta pi comodo ragiono in
base alle tabelle, alle righe, alle colonne e alle relazioni che
legano fra loro le tabelle; oppure in base ai nodi gerarchici
dellalbero XML che rappresenta quei dati. Significa, per
esempio che possiamo eseguire delle ricerche XPath sui
record senza perdere la possibilit di vederli come record di

10
Non vediamo le tecniche di aggiornamento batch del DataSet in quanto
accennarle di sfuggita non sarebbe corretto e non farebbe onore a un argo-
mento cos interessante. Daltra parte, se volessimo dedicare uno spazio ade-
guato allargomento, servirebbero un paio di capitoli di questo libro.

202
11-Cap07 7-04-2004 14:20 Pagina 203

7: Applicazioni pratiche di XML con .NET

una tabella, ma potendo, per un attimo, pensarli anche come


nodi XML. Non male! Ecco come:
SqlConnection cn = new SqlConnection(
server=localhost;database=northwind;
integrated security=SSPI;);
SqlDataAdapter da = new SqlDataAdapter(
SELECT CustomerID, ContactName, CompanyName FROM
Customers, cn);
da.MissingSchemaAction =
MissingSchemaAction.AddWithKey;

DataSet ds = new DataSet(dsNWind);

using(cn)
{
da.Fill(ds, Customers);
}

XmlDataDocument xmlDs = new XmlDataDocument(ds);


XmlNodeList xmlAuthors =
xmlDs.SelectNodes(//Customers);

DataRow row = ds.Tables[0].Rows.Find(ALFKI);


XmlElement author = xmlDs.GetElementFromRow(row);

Response.Write(author.LastChild.InnerText);

Sono evidenziate in grassetto la creazione delloggetto


XmlDataDocument, il cui costruttore vuole in ingresso un
riferimento alloggetto DataSet da rappresentare e il metodo
GetElementFromRow, del quale esiste lopposto GetRow-
FromElement. Come suggeriscono i nomi, si tratta degli
anelli di congiunzione tra le righe del DataSet e gli elementi
(tag) del documento XML.
DataRow row = xmlDs.GetRowFromElement(author);

Le eventuali modifiche apportate ai dati nel DataSet o ai


nodi nel XmlDataDocument saranno inoltre mantenute sin-
cronizzate. Daltra parte, si tratta di due differenti rappre-

203
11-Cap07 7-04-2004 14:20 Pagina 204

Programmare con XML I Portatili

sentazioni delle stesse informazioni. Da notare che pu esi-


stere un solo XmlDataDocument per ogni DataSet.
Unaltra opportunit interessante, che deriva dal poter rap-
presentare in XML un DataSet, quella di generare al volo
una pagina HTML partendo da un DataSet e da una tra-
sformazione XSLT.

Generare ASPX via XSLT


Proprio a proposito di XSLT, esiste una possibilit che in
molti ignorano: generare codice ASPX partendo da XML e
XSLT. Mi riferisco al fatto di poter costruire una trasforma-
zione XSLT il cui risultato sia non HTML, non XML qual-
siasi, ma XML che contiene tag XHTML e tag di controlli
ASP.NET. Chi sviluppa in ASP.NET sa che allinterno dei
file ASPX si scrivono un misto di codice HTML e di codi-
ce XML, dove questultimo dichiara controlli di ASP.NET o
nostri controlli custom, utilizzando elementi identificati da
un tagName e da un prefisso di Namespace. Ebbene, pensia-
mo al documento XML di seguito riportato:
<?xml version=1.0 encoding=utf-8 ?>
<cms:survey
xmlns:cms=http://schemas.paolo.com/cms/survey>
<cms:title>Questionario on-line</cms:title>
<cms:questions>
<cms:question id=name
text=Come ti chiami?
type=textbox size=10 />
<cms:question id=sex
text=Di che sesso sei?
type=radiobutton>
<cms:answer value=M text=Maschio />
<cms:answer value=F text=Femmina />
</cms:question>
<cms:question id=age
text=Quanti anni hai?
type=textbox size=4 />
</cms:questions>
</cms:survey>

204
11-Cap07 7-04-2004 14:20 Pagina 205

7: Applicazioni pratiche di XML con .NET

Si tratta di una descrizione formale di una serie di doman-


de, che insieme costituiscono un semplice questionario. Se
gli applichiamo la seguente trasformazione XSLT:
<?xml version=1.0 encoding=UTF-8 ?>
<xsl:stylesheet version=1.0
xmlns:xsl=http://www.w3.org/1999/XSL/Transform
xmlns:cms=http://schemas.paolo.com/cms/survey
xmlns:asp=http://schemas.microsoft.com/AspNet/
WebControls
exclude-result-prefixes=cms>

<xsl:output method=xml indent=yes />

<xsl:template match=/>
<span>
<xsl:for-each select=
cms:survey/cms:questions/cms:question>
<xsl:choose>
<xsl:when test=@type =
textbox>
<xsl:value-of select=@text />:
<asp:TextBox ID={@id}
Runat=server
Columns={@size} />
<br />
</xsl:when>
<xsl:when test=@type =
radiobutton>
<xsl:value-of select=
@text />:
<xsl:for-each select=
cms:answer>
<asp:RadioButton
GroupName={parent::*/@id}
Runat=server Text={@text}
Value={@value} />
</xsl:for-each>
<br />
</xsl:when>
</xsl:choose>
</xsl:for-each>
</span>

205
11-Cap07 7-04-2004 14:20 Pagina 206

Programmare con XML I Portatili

</xsl:template>

</xsl:stylesheet>

otterremo come risultato il seguente XML:


<?xml version=1.0 encoding=utf-16?>
<span xmlns:asp=
http://schemas.microsoft.com/AspNet/WebControls>
Come ti chiami?:
<asp:TextBox ID=name Runat=server
Columns=10 /><br />
Di che sesso sei?:
<asp:RadioButton GroupName=sex Runat=server
Text=Maschio Value=M />
<asp:RadioButton GroupName=sex Runat=server
Text=Femmina Value=F />
<br />
Quanti anni hai?:
<asp:TextBox ID=age Runat=server
Columns=4 /><br />
</span>

Facendone copia e incolla dentro a una pagina ASPX avrem-


mo un risultato come quello mostrato (figura 7.1).

Figura 7.1 La pagina ASPX visualizzata in un browser.

206
11-Cap07 7-04-2004 14:20 Pagina 207

7: Applicazioni pratiche di XML con .NET

Se invece di copia e incolla facessimo leggere il risultato al


motore di ASP.NET e gli chiedessimo di fare per conto
nostro il rendering al volo del codice ASPX cos genera-
to? Ogni pagina ASP.NET offre proprio questa possibi-
lit, con il metodo ParseControl derivato dalla classe base
System.Web.UI.TemplateControl. Ecco come:
private void Page_Load(object sender,
System.EventArgs e)
{
// Preparo il documento XML sorgente
XPathDocument xmlDoc = new
XPathDocument(Server.MapPath
(cms-Survey.xml));

// Preparo la trasformazione XSLT


XslTransform xslEngine = new XslTransform();
xslEngine.Load(Server.MapPath
(cms-Survey.xslt));

// Trasformo il contenuto XML in ASP.NET


StringBuilder strOutput = new StringBuilder();
xslEngine.Transform(xmlDoc, null,
new StringWriter(strOutput),
new XmlUrlResolver());

// Leggo il codice ASP.NET risultante


Control newControls =
this.ParseControl(strOutput.ToString());

// E lo inserisco in un PlaceHolder
xsltResult.Controls.Add(newControls);
}

Come eseguire la trasformazione XSLT non il punto chia-


ve di questo esempio, e ne abbiamo gi parlato abbondan-
temente nei capitoli precedenti. Ci che conta luso del
metodo ParseControl che restituisce un oggetto di tipi
System.Web.UI.Control, che possibile aggiungere allalbe-
ro di contenuti della pagina ASPX. In pratica questo meto-
do legge il codice ASPX che gli forniamo, lo interpreta

207
11-Cap07 7-04-2004 14:20 Pagina 208

Programmare con XML I Portatili

costruendo in memoria tutti gli oggetti che corrispondono


ai tag marcati come RUNAT=SERVER prodotti dalla tra-
sformazione XSLT, quindi prepara un controllo contenitore
nel quale mette tutto lalbero di oggetti. A noi non resta
altro che agganciare questo albero di oggetti allinterno
della lista dei controlli della pagina ASP.NET. Volendo,
potremmo anche associare degli eventi di codice managed
agli oggetti creati dinamicamente; quindi non abbiamo solo
un rendering brutale dei tag, ma possiamo anche prender-
ne il controllo da codice, dopo averli generati.
Se pensiamo alla possibilit di generare il documento XML
sorgente non come file su disco, ma per esempio come
risultato di una interrogazione su un database, questa tec-
nica diventa molto interessante. In questo modo potrem-
mo realizzare il motore di rendering di un sistema CMS 11
completo.

Ottenere XML da SQL Server


Microsoft SQL Server 2000, e anche la versione 7.0 con
laggiunta di qualche pezzettino di codice 12, possono parla-
re in XML con i loro client. Non a caso ho deciso di inseri-
re questa sezione dopo aver parlato di CMS e di generazio-
ne dinamica di contenuti. Ripensando allultimo esempio
fatto nella sezione precedente, sarebbe bello poter interro-
gare una database relazionale per ottenere in formato XML
i contenuti di un portale Web. Avendo un XML con una
struttura a noi nota potremmo realizzare una o pi trasfor-
mazioni XSLT che ci rendano in grado di avere il layout del

11
CMS lacronimo di Content Management System e si utilizza per tutti
quei sistemi che consentono di pubblicare e gestire i contenuti di un sito
Web. Di solito i CMS hanno uninterfaccia Web in modo da consentire la
gestione remota, dal browser, dei contenuti del sito.
12
Dobbiamo scaricare dal sito di Microsoft le estensioni SQLXML 3.0.
Sono disponibili alla URL http://msdn.microsoft.com/sqlxml/.

208
11-Cap07 7-04-2004 14:20 Pagina 209

7: Applicazioni pratiche di XML con .NET

portale indipendente dai suoi contenuti, oltre che di creare


un portale skinnabile 13. In questa sezione non ci preoccu-
peremo dei differenti layout grafici, ma di come sia possibi-
le ottenere i dati in formato XML da SQL Server.
A partire dalla versione 7.0 con laggiunta di SQLXML o
nativamente con SQL Server 2000 possiamo eseguire delle
query T-SQL di selezione dei record con una sintassi parti-
colare il cui risultato XML anzich un tradizionale result-
set. I puristi di SQL Server e della sintassi SQL in generale,
dopo unaffermazione come questa, di solito inorridiscono.
Dobbiamo sapere e tenere presente da subito che chiedere
a un motore DBMS di costruire per noi un albero di nodi
XML significa chiedergli un grande favore: per lui sar
oneroso accontentarci. Ma anche vero che se con questo
tipo di richiesta sostituiamo 10 o 20 richieste singole con le
relative connessioni al database... beh forse non gli stiamo
facendo poi un dispetto cos grosso, anche se lui magari
non se ne render conto e non potr ringraziarci !
Pensiamo ancora al caso del portale: tipicamente, per fare il
rendering di una pagina media, dobbiamo leggere dal
database le righe di contenuto, le voci di menu, le catego-
rie, i banner, i link e magari qualche immagine, se abbiamo
deciso di usare il database anche per contenere le immagini
del sito. Tralasciamo pure il recupero delle immagini, che
rappresenta un caso a se stante e che di solito conviene
gestire in modo slegato dalle richieste di XML. Pensiamo
per a cosa pu cambiare se invece di richiedere ogni sin-
golo pezzetto di pagina con una query ad hoc dovessimo
chiedere, con una sola query, magari complessa per carit,
di avere un unico documento XML, che rappresenta in un
13
Il concetto di skin dovrebbe ormai essere un inglesismo entrato nel voca-
bolario comune, almeno per gli informatici. In ogni caso per portale skin-
nabile intendo un portale nel quale gli stessi contenuti possono essere pre-
sentati in modo diverso, dal punto di vista del layout grafico, a seconda che
siano visti da un utente piuttosto che da un altro o allinterno di una sezione
del sito piuttosto che unaltra.

209
11-Cap07 7-04-2004 14:20 Pagina 210

Programmare con XML I Portatili

passo solo tutto il documento, per poi trasformarlo con


XSLT in codice HTML o, perch no, ASPX. Io almeno la
prova, per vedere se meglio o peggio di tante piccole
SELECT, la farei. Non c una risposta unica e assoluta a
un dubbio come questo, bisogna provare e scegliere la solu-
zione migliore, caso per caso. Avere gli strumenti per valu-
tare cosa sia meglio per gi un buon punto di partenza.
Per interrogare SQL Server affinch ci restituisca XML
abbiamo diverse strade tra loro alternative, tutte comunque
basate su delle estensioni proprietarie allistruzione
SELECT di SQL. Si parla di SELECT ... FOR XML in
quanto il comando SELECT classico termina con un suffis-
so FOR XML che comunica a SQL Server di produrre
XML e non un normale resultset. Ecco la sintassi:
SELECT Campi FROM Tabelle
FOR XML Modalit [, XMLDATA] [, ELEMENTS]
[, BINARY BASE64]

Dove Modalit pu assumere i seguenti valori:


RAW: restituisce un tag di nome ROW per ogni riga e
rappresenta i campi come attributi dellelemento ROW.
Eventuali JOIN presenti nella SELECT saranno rappre-
sentate in modo piatto (flat), cio senza annidare le
righe figlie allinterno di quelle padre, ma replicando
tutti i dati per ogni riga (elemento).
Per esempio listruzione SQL:
SELECT au_fname, au_lname FROM Authors FOR XML RAW

fornir come risultato:


<?xml version=1.0 encoding=utf-8 ?>
<root>
<row au_fname=Cheryl au_lname=Carson/>
<row au_fname=Michel au_lname=DeFrance/>
<row au_fname=Innes au_lname=del Castillo/>
[...omissis...]
</root>

210
11-Cap07 7-04-2004 14:20 Pagina 211

7: Applicazioni pratiche di XML con .NET

AUTO: restituisce un elemento con nome corrisponden-


te allalias della tabella da cui provengono i dati e, come
comportamento predefinito, gestisce i campi come attri-
buti. Nel caso di JOIN, i record figli saranno annidati in
quelli genitori. Possiamo anche chiedere di avere tutti i
campi sotto forma di elementi anzich di attributi con la
sintassi FOR XML AUTO, ELEMENTS.
Per esempio listruzione SQL:
SELECT au_fname, au_lname FROM Authors FOR XML AUTO

fornir come risultato:


<?xml version=1.0 encoding=utf-8 ?>
<root>
<Authors au_fname=Cheryl au_lname=Carson/>
<Authors au_fname=Michel au_lname=DeFrance/>
<Authors au_fname=Innes
au_lname=del Castillo/>
[...omissis...]
</root>

Mentre:
SELECT au_fname, au_lname FROM Authors
FOR XML AUTO, ELEMENTS

restituir uno stream XML come il seguente:


<?xml version=1.0 encoding=utf-8 ?>
<root>
<Authors>
<au_fname>Cheryl</au_fname>
<au_lname>Carson</au_lname>
</Authors>
<Authors>
<au_fname>Michel</au_fname>
<au_lname>DeFrance</au_lname>
</Authors>
<Authors>
<au_fname>Innes</au_fname>
<au_lname>del Castillo</au_lname>
</Authors>

211
11-Cap07 7-04-2004 14:20 Pagina 212

Programmare con XML I Portatili

[...omissis..]
</root>

Nel caso poi in cui dovessimo eseguire una SELECT


con una JOIN, come la seguente:
SELECT authors.au_id, authors.au_lname,
authors.au_fname, titles.title
FROM authors
INNER JOIN titleauthor ON authors.au_id =
titleauthor.au_id
INNER JOIN titles ON titleauthor.title_id =
titles.title_id
ORDER BY authors.au_id
FOR XML AUTO

restituir uno stream XML come il seguente:


<root>
<authors au_id=172-32-1176 au_lname=White
au_fname=Johnson>
<titles title=Prolonged Data Deprivation:
Four Case Studies/>
</authors>
<authors au_id=213-46-8915 au_lname=Green
au_fname=Marjorie>
<titles title=
The Busy Executive&apos;s Database Guide/>
<titles title=
You Can Combat Computer Stress!/>
</authors>
<authors au_id=238-95-7766
au_lname=Carson au_fname=Cheryl>
<titles title=But Is It User Friendly?/>
</authors>
[Omissis ...]
</root>

Dove si vede chiaramente che i titoli sono raggruppati


secondo lid dellautore che li ha scritti.
EXPLICIT: si tratta della sintassi pi complessa, e per
questo anche pi completa. Permette di decidere con la

212
11-Cap07 7-04-2004 14:20 Pagina 213

7: Applicazioni pratiche di XML con .NET

massima precisione la posizione dei valori nella gerar-


chia del documento e il tipo di nodi tramite i quali rap-
presentarli (Element, Attribute, CDATA, ecc.) sfruttan-
do gli alias sui nomi dei campi.
Per esempio:
SELECT 1 AS Tag,
NULL AS Parent,
Authors.au_id AS [Author!1!authorid],
Authors.au_fname AS [Author!1!firstname!element],
Authors.au_lname AS [Author!1!lastname!element]
FROM Authors
FOR XML EXPLICIT

restituir:
<?xml version=1.0 encoding=utf-8 ?>
<root>
<Author authorid=238-95-7766>
<firstname>Cheryl</firstname>
<lastname>Carson</lastname>
</Author>
<Author authorid=722-51-5454>
<firstname>Michel</firstname>
<lastname>DeFrance</lastname>
</Author>
<Author authorid=712-45-1867>
<firstname>Innes</firstname>
<lastname>del Castillo</lastname>
</Author>
[...omissis...]
</root>

Nel comporre gli alias delle colonne possiamo identificare


diverse aree informative, separate dal simbolo di punto
esclamativo (!):
[NomeTag!Posizione!Nome nodo!Tipologia di Nodo]

Dove NomeTag e Posizione sono intuitivi. Nome


nodo si riferir al nome dellattributo o del tag a seconda

213
11-Cap07 7-04-2004 14:20 Pagina 214

Programmare con XML I Portatili

di come configuriamo il quarto argomento. Infatti come


Tipologia di Nodo possiamo avere:
nessun valore: ottenendo di conseguenza un attributo,
che il caso predefinito.
element: rappresenta un tag.
xml: equivalente a element, ma non codifica eventuali
caratteri non consentiti (per esempio > rimarr tale e non
diventer &gt;).
xmltext: si utilizza quando la colonna contiene dellXML
che rappresenta un intero documento XML e lo si vuole
inserire allinterno del risultato della query senza alcuna
codifica, come con xml.
cdata: si ottiene una CDATA Section.
hide: la colonna non sar prodotta in output, a volte serve
per definire delle JOIN o dei raggruppamenti e/o ordina-
menti senza che poi si voglia mostrare la colonna in output.
In tutti e tre i casi (RAW, AUTO ed EXPLICIT) il
documentElement del risultato non viene creato in automa-
tico, spetta a noi il compito di prevederlo.
Per gestire dei risultati di questo tipo dal nostro codice
.NET possiamo utilizzare due diverse famiglie di classi:
quelle del Namespace System.Data.SqlClient e quelle del
Namespace Microsoft.Data.SqlXml. Mentre il primo
disponibile nativamente nel Framework .NET, il secondo si
installa insieme a SQLXML 3.0 e contiene delle classi dedi-
cate allutilizzo di SQL Server via XML 14.
Vediamo dapprima come ottenere il risultato di una query FOR
XML usando le classi native del Framework. In particolare uti-
14
Se a Redmond non cambiano idea, con la prossima versione di .NET e di
Visual Studio .NET (a oggi chiamato Whidbey e che dovrebbe chiamarsi
Visual Studio 2005) le classi di Microsoft.Data.SqlXml, che oggi sono da
installare a parte, saranno disponibili nativamente e inserite nel Namespace
System.Data.

214
11-Cap07 7-04-2004 14:20 Pagina 215

7: Applicazioni pratiche di XML con .NET

lizzeremo unistanza di SqlCommand, dal momento che espone


un metodo ExecuteXmlReader il cui nome promette bene:
SqlConnection cn = new SqlConnection(
server=localhost;database=Northwind;
integrated security=SSPI;);
SqlCommand cmd = new
SqlCommand(spXmlListCategories, cn);
cmd.CommandType = CommandType.StoredProcedure;

using(cn)
{
cn.Open();
XmlReader resultXml = cmd.ExecuteXmlReader();
XmlDocument docXml = new XmlDocument();
docXml.Load(resultXml);
}

Oltre che utilizzare una variabile di tipo XmlReader di ap-


poggio, potremmo anche leggere direttamente il risultato
della SELECT ... FOR XML in un DataSet, invocandone il
metodo ReadXml:
ds.ReadXml(cmd.ExecuteXmlReader(),
XmlReadMode.InferSchema);

Il vantaggio di un simile approccio che se dobbiamo recu-


perare dei dati in relazione e costruiamo correttamente la
SELECT ... FOR XML, possiamo fare in modo che sia il
DataSet a capire autonomamente qual la struttura dati
(InferSchema) e lasciargli creare in automatico le relazioni
sulle tabelle di dati.
Come dicevamo unaltra possibilit quella di utilizzare delle
classi accessorie, disponibili in una libreria esterna, distribuita
con SQLXML 3.0. Vediamo un semplice esempio:
SqlXmlCommand xmlCmd = new
OLEDBConnectionString);
SqlXmlCommand(O
xmlCmd.RootTag = root;
xmlCmd.CommandText = SELECT * FROM Customers FOR
XML AUTO;
xmlCmd.CommandType = SqlXmlCommandType.Sql;

215
11-Cap07 7-04-2004 14:20 Pagina 216

Programmare con XML I Portatili

XmlReader resultXml = xmlCmd.ExecuteXmlReader();


XmlDocument docXml = new XmlDocument();
docXml.Load(resultXml);
docXml.Save(Response.OutputStream);

Le differenze rispetto al caso precedente risiedono innanzi-


tutto nel fatto che utilizziamo oggetti di un Namespace
diverso e nuovo. Inoltre loggetto SqlXmlCommand, a
dispetto del suo nome, si occupa anche di gestire la connes-
sione a SQL Server, utilizzando tra laltro una connessione
di tipo OLEDB e non SqlClient .NET. Si noti anche il
punto in cui da codice si assegna un nome al tag root del
risultato, che altrimenti non sarebbe well-formed in quanto
privo di documentElement. Linvocazione del metodo
ExecuteXmlReader invece richiama in modo molto fedele il
comportamento di un SqlCommand classico.

Inviare XML a SQL Server


Lultimo aspetto che ci rimane da valutare, per questa breve
carrellata di esempi duso di XML con .NET, il caso del-
linvio di dati in formato XML a SQL Server. Sempre pi
spesso, e per fortuna, ci capita di ricevere da clienti, part-
ner o semplicemente terze parti, dei dati in formato non
pi CSV ma XML. Spesso si finisce per aprire i file XML a
mano, scorrerne i nodi cercando le informazioni necessarie
per poi richiamare N volte una stessa stored procedure di
inserimento o modifica dei dati.
Anche in questo caso abbiamo diversi modi di lavorare e
sicuramente lapproccio mi faccio tutto a mano quello
da adottare solo come ultima spiaggia. Se alle spalle del
nostro sistema informativo abbiamo Microsoft SQL Server
possiamo fargli caricare direttamente i dati in formato
XML, lasciando a lui lonere di leggere i singoli nodi e farli
corrispondere a colonne, righe e tabelle. Ovviamente noi
dovremo impostare delle regole di matching, affinch ci
possa svolgersi correttamente.

216
11-Cap07 7-04-2004 14:20 Pagina 217

7: Applicazioni pratiche di XML con .NET

Un primo modo quello di utilizzare una stored procedure


che riceva come parametro il blocco di XML. Allinterno
della stored procedure faremo uso del comando OPENXML
per andare a leggere i nodi. Pensiamo al solito documento
XML che descrive un ordine:
<?xml version=1.0 encoding=utf-8 ?>
<order xmlns=http://schemas.paolo.com/Order>
<items>
<item idProduct=1 euroPrice=10.20
quantity=10 />
<item idProduct=2 euroPrice=15.35
quantity=25 />
<item idProduct=3 euroPrice=12.85
quantity=20 />
</items>
<customer idCustomer=PP01-003
email=paolo@devleap.it
fullName=Paolo Pialorsi />
</order>

Per caricarlo in un database o anche solo selezionarne il con-


tenuto, vedendolo come una normale tabella, dobbiamo scri-
vere una stored procedure il cui cuore sar simile al seguente:
DECLARE @idXml int

EXEC sp_xml_preparedocument @idXml OUTPUT, @xml,


<root xmlns:o=http://schemas.paolo.com/Order />

SELECT * FROM OPENXML


(@idXml, /o:order/o:items/o:item, 2)
WITH
(
idProduct int @idProduct,
euroPrice money @euroPrice,
quantity int @quantity
)

EXEC sp_xml_removedocument @idXml

Dobbiamo chiamare la stored procedure di sistema


sp_xml_preparedocument per preparare in memoria un albero

217
11-Cap07 7-04-2004 14:20 Pagina 218

Programmare con XML I Portatili

che rappresenti il documento XML fornito come parametro


@xml. Dal momento che il nostro documento XML presenta
un Namespace XML associato, dobbiamo comunicarlo anche
alla stored procedure di sistema sp_xml_preparedocument, e lo
facciamo con il terzo parametro della chiamata. Il risultato di
questa procedura sar la valorizzazione della variabile di out-
put @idXml, che possiamo poi utilizzare nella istruzione
OPENXML successiva, per dichiarare quale albero di nodi
vogliamo leggere (primo parametro), partendo da quale nodo
(secondo parametro), con una regola XPath, e orientando la
nostra ricerca sui tag (valore 2 del terzo parametro), sugli
attributi (1) o in modalit mista tra elementi e attributi (8).
Possiamo poi specificare un blocco WITH che dettagli i nomi
delle colonne, il loro tipo di dato e opzionalmente il percorso
XPath di selezione dei singoli valori. Se omettiamo il percorso
XPath di selezione dei valori, OPENXML far del suo meglio
per trovare i tag o gli attributi, a seconda del valore del terzo
parametro di OPENXML, che abbiano lo stesso nome della
colonna che stiamo selezionando. importante ricordarsi,
dopo aver utilizzato un documento, di rimuovere dalla memo-
ria di SQL Server lalbero di nodi, invocando la stored proce-
dure di sistema sp_xml_removedocument e dandole come
parametro lhandle del documento, nel nostro caso @idXml.
La chiamata da codice .NET a questa stored procedure, se
immaginiamo di ricevere i dati in POST HTTP allinterno
del Request.InputStream, sar:
XmlDocument doc =
loadPostedXmlFileWithXmlValidatingReader();

SqlConnection cn = new SqlConnection(


server=localhost;database=Northwind;integrated
security=SSPI;);
SqlCommand cmd = new SqlCommand(spReadXmlOrder, cn);
cmd.CommandType = CommandType.StoredProcedure;

cmd.Parameters.Add(@Xml, SqlDbType.VarChar,
1000).Value =
doc.DocumentElement.OuterXml;

218
11-Cap07 7-04-2004 14:20 Pagina 219

7: Applicazioni pratiche di XML con .NET

using (cn)
{
cn.Open();
gridResult.DataSource = cmd.ExecuteReader();
gridResult.DataBind();
}

Unaltra possibilit, come nella sezione precedente, quella


di utilizzare le classi del Namespace Microsoft.Data.SqlXml
per eseguire unoperazione di Bulk Load XML. Dobbiamo
innanzitutto creare uno schema XSD con delle estensioni,
dette annotazioni, che definiscano la corrispondenza tra i
nodi del documento XML sorgente e le colonne, righe e
tabelle del database di SQL Server. Riporto solo la parte
relativa ai singoli item dellordine:
<xsd:element name=item maxOccurs=unbounded
sql:relation=tabOrderItems>
<xsd:complexType>
<xsd:attribute
name=idProduct type=xsd:int
use=required sql:field=idProduct />
<xsd:attribute
name=euroPrice type=xsd:decimal
use=required sql:field=euroPrice />
<xsd:attribute name=quantity
type=xsd:int
use=required sql:field=quantity />
</xsd:complexType>
</xsd:element>

Gli attributi sql:relation e sql:field descrivono rispettivamente


a quale tabella di SQL Server stiamo facendo riferimento e a
quali colonne della tabella corrispondono i singoli attributi di
ciascun tag item. Quindi necessario referenziare un oggetto
COM, bench si lavori in .NET, in quanto non esiste una ver-
sione .NET del componente di Bulk Load XML. Il compo-
nente COM per la Bulk Load XML si chiama Microsoft
XML BulkLoad for SQL Server 3.0 Type Library. Infine
dobbiamo scrivere il seguente codice:

219
11-Cap07 7-04-2004 14:20 Pagina 220

Programmare con XML I Portatili

XmlDocument doc =
loadPostedXmlFileWithXmlValidatingReader();
String docPath = String.Format(@d:\temp\{0}.xml,
Guid.NewGuid());
doc.Save(docPath);

try
{
SQLXMLBULKLOADLib.SQLXMLBulkLoad3Class
bulkLoadXml =
new
SQLXMLBULKLOADLib.SQLXMLBulkLoad3Class();
bulkLoadXml.ConnectionString =
OLEDBConnectionString;
bulkLoadXml.ErrorLogFile =
@d:\temp\errorLog.xml;
bulkLoadXml.Transaction = true;
bulkLoadXml.KeepIdentity = false;
bulkLoadXml.Execute(Server.MapPath
(BulkLoadOrder.xsd), docPath);
}
catch(COMException exCOM)
{
Response.Write(exCOM.Message);
}

Il doverci appoggiare a un componente COM, porta come


conseguenza il fatto che dobbiamo usare una connessione
di tipo OLEDB al database, inoltre non semplice fornire
alloggetto SQLXMLBulkLoad3Class uno Stream e, in que-
sto esempio introduttivo, ci conviene passare da un file
temporaneo su disco 15.

15
In realt potremmo lavorare con uninterfaccia .NET di nome
UCOMIStream, disponibile nel Namespace System.Runtime.Interop-
Services, in modo tale da far vedere il file come se fosse uno Stream del
mondo COM, ma questo non un libro sulla interoperabilit tra COM e
.NET, quindi sorvoliamo su questi virtuosismi e ci accontentiamo di salvare
il file su disco. Per i pi curiosi sul portale dedicato a SqlXml spiegato
come fare (http://www.sqlxml.org/faqs.aspx?faq=98).

220