AJAX + ASP + SQL
I miei continui esperimenti con AJAX portano continuamente alla nascita di nuove 'follie'.
L'ultima riguarda una inconsueta mescolanza tra Javascript, ASP ed SQL.
Lo scenario e' questo: in un progetto ASP+MSSQL ho necessita' di creare dinamicamente con Javascript svariati elementi (in particolare tabelle e combobox), popolandoli con dei dati presenti sul database.
Il dover creare continuamente nuove pagine ASP per estrapolare i dati, e altre per salvarli mi rallenta di molto il lavoro.
A questo punto l'idea: perche' non realizzare una pagina ASP 'generica', alla quale passare semplicemente la query da eseguire, farmi restituire esclusivamente i dati e processarli con Javascript?
La struttura partorita dalla mia mente malata e' quindi cosi' costituita:
- Una pagina ASP, che accettera' in ingresso (anche con una semplice chiamata GET) una query, la eseguira' sul database e restituira' i dati sotto forma di XML.
- Una classe Javascript che si occupera' di prelevare l'XML, processarlo ed estrarre i dati
- I dati cosi' ottenuti verranno poi utilizzati da altre funzioni in Js che si occuperanno di creare gli elementi della pagina (Tabelle, ComboBox ecc..).
Partiamo quindi dal codice ServerSide:
SQL2XML.ASP
<%
set con = Server.CreateObject ("ADODB.connection")
Con.Open Application("Connessione_al_DataBase")sql = request("query")
set rs = con.Execute(sql)
Dim oDOM
Set oDOM = Server.CreateObject("MSXML2.DOMDocument")
oDOM.async = False
rs.Save oDOM, 1 'adPersistXML
Dim oXSL
Set oXSL = Server.CreateObject("MSXML2.DOMDocument")
oXSL.async = False
oXSL.load Server.MapPath("ADOGeneric.xsl")
Response.Write "<XML id='xmlData' name='xmlData'><root>" & vbCrLf
Response.Write oDOM.transformNode(oXSL)
Response.Write "</root></XML>"%>
Il funzionamento e' semplice: estrae la query da eseguire dalla QueryString, effettua la connessione al DataBase, raccoglie i dati, li converte in XML e li formatta utilizzando lo StyleSheet XML ADOGeneric.xsl.
ADOGeneric.xsl
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:z="#RowsetSchema">
<s:Schema id="RowsetSchema"/>
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:apply-templates select="//z:row"/>
</xsl:template><xsl:template match="z:row">
<xsl:text disable-output-escaping="yes"><row></xsl:text>
<xsl:for-each select="@*">
<xsl:text disable-output-escaping="yes"><</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:value-of select="."/>
<xsl:text disable-output-escaping="yes"></</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:for-each>
<xsl:text disable-output-escaping="yes"></row></xsl:text>
</xsl:template>
</xsl:stylesheet>
La riformattazione utilizzando lo stylesheet rende piu' comodo il successivo processo di interpretazione dei dati tramite Javascript.
Mettiamo ora mano al codice ClientSide:
Query2Table.js
/*
* Creazione tabella da query
* @param Query Stringa contenente la query da inviare al server
* @param elemento ID dell'elemento della pagina che conterrà la tabella
*/function SQL2Table(query, elemento)
{
if (document.implementation && document.implementation.createDocument)
{
xmlDoc = document.implementation.createDocument("", "", null);
xmlDoc.onload = createTable(elemento);
}
else if (window.ActiveXObject)
{
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.onreadystatechange = function () {
if (xmlDoc.readyState == 4) createTable(elemento)
};
}
else
{
alert('Your browser can\'t handle this script');
return;
}
query = query.replace(" ","+");
xmlDoc.load("SQL2XML.asp?query=" + query);
}function createTable(oggetto)
{
var x = xmlDoc.getElementsByTagName('row');
var newEl = document.createElement('TABLE');
newEl.setAttribute('cellPadding',5);
var tmp = document.createElement('TBODY');
newEl.appendChild(tmp);
var row = document.createElement('TR');
for (j=0;j<x[0].childNodes.length;j++)
{
if (x[0].childNodes[j].nodeType != 1) continue;
var container = document.createElement('TH');
var theData = document.createTextNode(x[0].childNodes[j].nodeName);
container.appendChild(theData);
row.appendChild(container);
}
tmp.appendChild(row);
for (i=0;i<x.length;i++)
{
var row = document.createElement('TR');
for (j=0;j<x[i].childNodes.length;j++)
{
if (x[i].childNodes[j].nodeType != 1) continue;
var container = document.createElement('TD');
var theData = document.createTextNode(x[i].childNodes[j].firstChild.nodeValue);
container.appendChild(theData);
row.appendChild(container);
}
tmp.appendChild(row);
}
document.getElementById(oggetto).appendChild(newEl);
}
La funzione SQL2Table si occupa, con il solito meccanismo cross browser di richiamare la pagina ASP creata in precedenza passandogli la query da eseguire, estraendo i dati e passandoli alla seconda funzione (createTable) insieme all'ID dell'elemento della pagina che conterra' la tabella (un <p> o un <div>).
createTable processera' l'XML ottenuto da SQL2Table, creando la tabella con nella prima riga le intestazioni contenenti i nomi dei campi estratti dalla query, 'riversando' poi il tutto nell'elemento della pagina specificato.
Volendo testare il corretto funzionamento del nostro script, possiamo realizzare un semplice documento HTML:
Test.htm
<SCRIPT LANGUAGE=javascript src="Query2Table.js"></SCRIPT>
<p id=tabella name=tabella></p>
<INPUT type="button" value="Crea Tabella" id=button1 name=button1 onclick="javascript:SQL2Table('select * from impiegati','tabella');">
Come si puo' evincere dal codice, tutto cio' comporta sicuramente una maggiore rapidita' nella fruizione di dati dal DB, ma include serie conseguenze dal lato della sicurezza:
premetto che questa che sto implementando questa struttura all'interno di un progetto destinato ad una intranet, quindi in mano a personale 'fidato' (lungi da me metter un accrocchio del genere su internet), ma nulla toglie che un utente malizioso e un po' piu' skillato possa fare il bello e il cattivo tempo sul database.
La soluzione da me qui riportata deve essere intesa solo come un proof of concept, niente quindi che possa essere utilizzando in un progetto reale, a meno che non si implementi (come sto gia' facendo) un meccanismo di comunicazione 'sicuro' tra client e server, magari criptando in qualche modo la stringa contenente la query da eseguire.