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:

  1. 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.
  2. Una classe Javascript che si occupera' di prelevare l'XML, processarlo ed estrarre i dati
  3. 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">&lt;row&gt;</xsl:text>
               <xsl:for-each select="@*">
                      <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
                       <xsl:value-of select="name()"/>
                       <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
                       <xsl:value-of select="."/>
                       <xsl:text disable-output-escaping="yes">&lt;/</xsl:text>
                       <xsl:value-of select="name()"/>
                       <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
               </xsl:for-each>
       <xsl:text disable-output-escaping="yes">&lt;/row&gt;</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.