ASP: la guida introduttiva Capitolo 1 - Introduzione ad ASP 1.0 - Cenni introduttivi ASP, acronimo di Active Server Pages, è una sigla utilizzata per indicare una particolare tecnologia, sviluppata da Microsoft sulla falsariga di PHP, mediante la quale è possibile realizzare delle pagine Web il cui codice venga processato dal server prima di essere inviato al client. Lo scopo di questa guida è quella di introdurre il lettore all'utilizzo della tecnologia ASP, mostrandone gli aspetti di base. Gli unici prerequisiti richiesti sono la completa conoscenza dei meccanismi di HTML, la conoscenza della sintassi di base di JavaScript ed un po' di praticità con il settore del web-publishing, oltre ad un minimo di confidenza con le più basilari tecniche programmative. Non sono richieste conoscenze relative ad altre tecnologie server-side simili ad ASP: l'apprendimento dei meccanismi di questo ambito lavorativo è uno degli scopi della guida stessa. Chi avesse scelto questa lettura per migrare verso ASP avendo già utilizzato precedentemente altre tecnologie server-side può considerare alcune nozioni che verranno presentate nel corso della guida come un semplice ripasso. L'unica fonte ufficiale di diffusione di questa guida è rappresentata da www.sauronsoftware.it, sito che potrà essere consultato quindi per ricercare materiale di supporto o versioni più recenti del manuale in questione. La redistribuzione di questo materiale è consentita solo su esplicito consenso del suo autore. 1.1 - Le Active Server Pages Le Active Server Pages sono particolari pagine Web in grado di integrare al loro interno porzioni di codice, redatte mediante diversi linguaggi di scripting, che verranno elaborate dal server. Di questi script verranno inviati al client solo i rispettivi output. Il linguaggio di scripting solitamente utilizzato da una pagina ASP è VBScript, tuttavia è possibile scrivere pagine in ASP anche utilizzando altri linguaggi, come PerlScript o JScript, purché essi vengano supportati dal server che dovrà ospitare ed elaborare le pagine realizzate. Di norma oltre a VBScript è supportato perlomeno anche JScript. Gli esempi che presenteremo in seguito saranno scritti in JScript, ossia la versione di JavaScript targata Microsoft. Questa scelta, insolita per una guida introduttiva ad ASP, è motivata da diversi fattori. Per prima cosa JScript offre una maggiore chiarezza sintattica rispetto a VBScript, grazie alla sua sintassi C-like, ed inoltre JavaScript risulta comunque il linguaggio di scripting più diffuso nell'ambito delle tecnologie client-side. Per concludere il tipo di sintassi di JScript faciliterà la migrazione verso ASP a chi già sappia sviluppare pagine PHP e faciliterà il passaggio inverso a chi abbia deciso di approcciare le tecnologie server-side mediante questa guida. L'unica cosa che occorre sapere a questo punto è che ogni pagina che realizzeremo in seguito dovrà contenere come prima riga di codice la seguente: <%@ LANGUAGE = JScript %> che comunque non verrà mai omessa dagli esempi. Come riferimento a JScript va presa in considerazione la guida ufficiale liberamente distribuita da Microsoft, la cui versione italiana può essere scaricata dal lettore all'indirizzo www.sauronsoftware.it/javascript. 1.2 - Questione di server Il primo elemento da prendere in considerazione quanto si sviluppano degli script client-side è indubbiamente il browser del potenziale utente. Nel caso di ASP e delle tecnologie server-side, invece, il primo fattore a dover essere preso in considerazione è il server sul quale bisognerà rendere disponibili le proprie realizzazioni. Non tutti i server supportano ASP, anzi, solo una minoranza di quelli diffusi lungo la superficie terrestre offre supporto per questa tecnologia. Sostanzialmente a supportare ASP sono solo i server firmati Microsoft (tranne eccezioni), come i server IIS in esecuzione su sistemi Windows NT. Questo significa che, per vedere funzionare quanto sarà realizzato a seguire all'interno di questa guida, è necessario trovarsi un server Web in grado di supportare adeguatamente ASP. Moltissimi servizi di hosting a pagamento garantiscono il supporto di ASP, mentre sono ben pochi gli spazi gratuiti che facciano altrettanto. Per mettere il lettore in grado di sperimentare on line i propri risultati si consigliano i seguenti servizi in grado di offrire gratuitamente ciò che si sta cercando: asp4free.ravenna2000.it, www.mso.it , www.domaindlx.com e www.atfreeweb.com. 1.3 - Eseguire le pagine ASP in locale Si presume che il lettore, prima di provare i risultati on line, sia intenzionato a verificare la funzionalità delle pagine in locale sulla propria macchina. Per far ciò, purtroppo, non basta aprire con il browser un file con estensione .asp residente sul proprio hard disk, in quanto le pagine ASP hanno bisogno, per l'appunto, di essere prima elaborate da un server. Per risolvere questo problema gli utilizzatori di Windows 98 e successivi hanno la possibilità di installare all'interno del loro sistema operativo un piccolo server in grado di interpretare in maniera corretta le pagine ASP. Inserendo infatti nel proprio lettore CD ROM il CD di Windows è possibile installare il Personal Web Server (abbreviato PWS). Al termine dell'installazione l'hard disk conterrà una nuova cartella (inetpub) e sul desktop sarà presente una nuova icona sottotitolata Pubblica. I propri file .asp andranno a questo punto resi disponibili a partire dalla cartella inetpub\wwwroot\ e, dopo aver avviato il PWS mediante la schermata presentata al doppio click sulla nuova icona, sarà possibile visualizzarle all'interno del browser a partire dall'indirizzo http://localhost/. Per guidare il lettore in questo procedimento si prenda il considerazione il seguente esempio effettuato con una comune pagina HTML. Si realizzi quindi un documento HTML e lo si vada a salvare con il nome prova.htm nella cartella inetpub\wwwroot. Si avvii ora il PWS (se non è già avviato) e si apra il browser Web preferito. Nella barra degli indirizzi del browser digitare http://localhost/prova.htm. Se la procedura è stata eseguita in maniera corretta il lettore dovrebbe essere ora in grado di visualizzare la pagina HTML realizzata all'interno del browser. La stessa procedura dovrà essere osservata in seguito per visualizzare off line le pagine ASP che verranno realizzate. La cartella inetpub\wwwroot è accessibile in tre diverse maniere: http://localhost (come già indicato), http://127.0.0.1 e http://nome_della_propria_macchina. Il nome della propria macchina può essere impostato da pannello di controllo > Rete > Identificazione. Se si modifica il nome in rete della propria macchina sarà necessario riavviare il sistema. 1.4 - La prima pagina ASP E' ora finalmente possibile sperimentare la creazione di una prima pagina ASP di prova. Si salvi il seguente codice in un file nominato primapagina.asp e quindi si provi a pubblicarlo e ad eseguirlo on line o in locale, come suggerito nel paragrafo precedente. <%@ LANGUAGE = JScript %> <html> <head><title>La mia prima pagina ASP</title></head> <body> Questa è una pagina asp!<br><br> <% var d = new Date(); var g = "" + d.getDate() + "/" + (d.getMonth()+1) + "/" + d.getYear(); Response.Write("Per il server oggi è il " + g); %> </body> </html> Eseguendo dal browser questa pagina il lettore potrà ammirare il risultato prodotto. Si provi ad aprire il codice HTML con l'apposita opzione del proprio browser: sarà possibile vedere come esso si discosti dal reale codice del file .asp. Per l'esattezza, infatti, tutto quello contenuto tra <% e %> non verrà di fatto mostrato. Al suo posto sarà presente la semplice frase "Per il server oggi è il " seguita dalla data odierna, come se si trattasse di una comune pagina HTML dove manualmente sia stata inserita quella specifica data. Il lettore provi ora a salvare in un secondo file lo stesso codice di sopra, cambiandone però il nome in primapagina.htm. Il browser mostrerà il codice che nell'esempio precedente veniva invece eseguito. Dal piccolo esempio presentato si evincono tre aspetti fondamentali di ASP. Per prima cosa si impara che i file con estensione .asp vengono in qualche maniera elaborati dal server prima di essere inviati al browser. Il server, infatti, processa questo tipo di file alla ricerca di script per lui eseguibili. Solo l'output di ogni singolo script viene inserito nel risultato finale inviato al browser. Come seconda cosa si apprende il significato, in una pagina ASP, dei due tag speciali <% e %>. Questi, infatti, servono per racchiudere gli script che il server dovrebbe essere in grado di interpretare. Tutto quello che sarà contenuto all'interno di questi marcatori verrà inteso dal server come codice realizzato nel linguaggio di scripting specificato alla prima riga (nel nostro caso JScript). Se questo codice dovesse contenere errori l'esecuzione dello script viene interrotta e l'analisi della pagina bloccata. Nell'output HTML sarà presente una descrizione dell'errore. Infine si può capire che ASP mette a disposizione del linguaggio di scripting utilizzato una serie di comandi aggiuntivi, come lo è nel nostro caso Response.Write(), il cui utilizzo appare evidente. Nel corso di questa guida saranno analizzati tutti questi "comandi speciali" e si comprenderà la loro utilità e il loro scopo. Capitolo 2 - Gli elementi di ASP 2.0 - Fare scripting con ASP ASP permette di integrare due tipi di elementi distinti all'interno di una pagina: elementi di scripting, che saranno eseguiti sul server, ed elementi di testo semplice, che verranno invece inviati inalterati al client senza essere processati dal server. Gli elementi di scripting sono distinti dal resto della pagina che li contiene in quanto racchiusi tra i due tag speciali <% e %>. Questa non è comunque l'unica maniera di includere uno script in una pagina ASP. E' altresì possibile usare una sintassi del tipo: <script language="JScript" runat="server"> // corpo dello script </script> La sintassi utilizzata in questo caso ricorda molto da vicino quella necessaria per inserire script client-side in una qualsiasi pagina HTML. L'unica cosa che rende distinta la dichiarazione di uno script di questo tipo da quella relativa allo scripting client-side risiede nell'attributo runat="server" aggiunto al classico tag <script>. Il vantaggio dell'utilizzare questa sintassi sta nel fatto di poter selezionare l'utilizzo di più linguaggi di scripting all'interno di una stessa pagina, a differenza di <% e %> che invece possono inglobare solamente script realizzati con il linguaggio specificato alla prima riga del codice (<%@ LANGUAGE = JScript %>, ad esempio) oppure con il linguaggio predefinito nel caso di assenza di questa riga (solitamente questo è VBScript). Non si fermano comunque qui le differenze delle due distinte sintassi. Oltre ad una differenza che non tratteremo relativamente all'ambito di visibilità delle variabili, c'è da dire che <% e %>, per quanto limitati dal fatto di poter contenere script di un solo linguaggio, hanno una migliore integrazione con le parti di testo semplice. In ogni caso i due elementi costitutivi di una pagina ASP non sono separati in due parti distinte del codice, anzi, spesso si ritrovano profondamente mescolati tra di loro. Per rendere meglio il concetto si consideri la seguente pagina ASP: <%@ LANGUAGE = JScript %> <html> <head><title>Prova ASP</title></head> <body> <% for (i=1;i<=10;i++) { %> <b>Riga numero <%=i%></b><br> <% } %> </body> </html> Questa pagina si limita a stampare, in output HTML, 10 righe numerate. Si osservi però la struttura mediante la quale è stato realizzato il tutto: codice JScript eseguibile sul server e codice HTML da inviare al browser si autocompenetrano, e questo è possibile proprio grazie alle caratteristiche offerte dall'uso di <% e %>. Si noti poi anche l'utilizzo della forma abbreviata <%=i%>, del tutto equivalente a <% Response.Write(i); %>, ma molto più semplice e veloce da digitare e da rileggere in seguito, specie in questi casi di codice HTML intervallato da parti JScript. 2.1 - Server Side Include Questa panoramica iniziale su ASP non sarebbe completa senza accennare alla libreria dei comandi Server Side Include (SSI) e al più importante di essi: #include. I comandi SSI si presentato a prima vista come dei semplici commenti HTML, se non per il fatto che rispettino sempre la seguente sintassi: <!--#nome_comando attributo="valore"--> I comandi aderenti a questa sintassi, al pari degli script, vengono valutati dal server prima di essere "girati" al client. Qualora il server dovesse riuscire a riconoscere un comando per lui appetibile lo eseguirà, e solo l'output di questo comando sarà trasmesso al browser del visitatore della pagina. Il comando SSI più utilizzato in ambito ASP è, come già accennato, #include. Questo comando si comporta in maniera analoga all'omonima direttiva per il compilatore di C++: <!--#include file="menu.htm"--> Questa riga di codice verrà automaticamente rimpiazzata dal server con il contenuto del file menu.htm residente nella stessa cartella che contiene la pagina ASP in questione. L'utilità è evidente: grazie ad #include non sarà più necessario copiare lo stesso codice in più pagine in quanto questo potrà essere tranquillamente conservato in un unico file esterno. Una modifica su questo file, poi, si rifletterà automaticamente su tutte le pagine che lo includono. Le applicazioni base sono due, una relativa alla parte HTML di una pagina ASP, l'altra relativa alla parte di puro scripting. Nel primo caso, infatti, sarà possibile ripetere lo stesso template HTML all'interno di tutte le pagine che lo includono. Si pensi ad esempio al classico caso di un menù per un sito Web: se lo stesso menù deve presentarsi su 200 pagine distinte sarà sicuramente più conveniente includerlo in esse mediante SSI che non con il classico copia-incolla del codice. Un'aggiornamento sul file contenente questo menù, poi, aggiornerà automaticamente il menù di tutte le pagine del sito. La praticità è notevole. #include, comunque, non si limita ad integrare solo HTML: con esso è possibile inserire ogni tipo di file all'interno di una pagina, e quindi anche script server-side contenenti magari delle funzioni standard da utilizzare in più pagine. Torneremo poi sull'argomento più avanti, per ora questo è tutto quello che occorre sapere. 2.2 - Struttura di una pagina ASP Riassumendo allora quanto detto sino ad ora possiamo concludere che una classica pagina ASP può essere formata da quattro distinti elementi: la dichiarazione del linguaggio di scripting utilizzato (se presente deve necessariamente essere la prima riga del file), i comandi SSI, gli script eseguibili sul server e le parti "semplici" da inviare invariate al client. Imparare a programmare con ASP significa avere padronanza di questi elementi e saperli dosare in un giusto mix. 2.3 - La fase di scripting e gli oggetti built-in La parte sulla quale si focalizzerà maggiormente l'attenzione in questa guida è quella relativa allo scripting. Le possibilità offerte da ASP sono molteplici. Nel caso dello scripting client-side il browser mette a disposizione del linguaggio diversi oggetti relativi a quel dato ambito, come ad esempio l'oggetto navigator per rilevare quale sia il browser in uso. Analogamente ASP mette a disposizione del linguaggio di scripting 5 diversi oggetti che, grazie ai loro metodi, consentono il controllo del server e della comunicazione server-client. Questi 5 oggetti particolari, che sono detti oggetti built-in, vengono automaticamente istanziati all'inizio dell'esecuzione di ogni pagina. Questi sono: Response gestisce l'output del codice Request gestisce l'input del codice Server assolve a funzionalità relative all'ambito di esecuzione Session gestisce le sessioni degli utenti Application permette di condividere informazioni tra più utenti di una stessa applicazione 2.4 - Gli oggetti ActiveX Il linguaggio di scripting e gli oggetti built-in nulla potrebbero in ASP senza poggiarsi su oggetti esterni, che in questo ambito sono definiti secondo la tecnologia ActiveX, in grado di assolvere a quei compiti che non vengono svolti in maniera nativa. Né JScript né VBScript, ad esempio, permettono di per sé di leggere o di scrivere file di testo sul server, così come non forniscono nella loro definizione base la connettività con i database. Questi compiti, quindi, vengono svolti attraverso il ricorso ad oggetti esterni di tipo ActiveX. Questi oggetti sono da considerarsi a se stanti e non facenti parte dell'architettura di ASP. Molti di questi, comunque, sono considerati standard e vengono installati su una macchina insieme al server stesso. Saranno questi gli oggetti ActiveX ai quali faremo riferimento nel corso della guida. Per ogni compito di natura non generale sarà poi sempre possibile rintracciare un componente ActiveX in grado di renderne possibile l'attuazione, o sarà addirittura possibile realizzarne uno apposito in C++ o in Visual Basic, volendo. Questa è l'ideologia su cui si basa ASP. Il linguaggio di scripting altro non è che una manierà di coordinare e rendere efficaci gli oggetti built-in e i componenti ActiveX. Al momento di applicare le nozioni ricevute ci si accerti sempre degli oggetti ActiveX disponibili sul server che si sta utilizzando, non è detto che questi siano quelli standard. 2.5 - Una pagina esemplificativa La pagina ASP qui di seguito rappresentata vuole essere di esempio a quanto detto in questo capitolo della guida. Non si pretende che il lettore conosca l'esatto utilizzo degli oggetti ai quali si farà ricorso, che saranno invece ampliamente trattati di seguito. L'intenzione è quella di realizzare un contatore da includere poi mediante il comando #include di SSI all'interno di una qualsiasi pagina ASP. Si realizzi la seguente pagina ASP, che potrà essere denominata index.asp: <%@ LANGUAGE = JScript %> <html> <head><title>Benvenuto!</title></head> <body> <center> <h1>Benvenuto nel mio sito!</h1> Sei il visitatore numero <b><!--#include file="contatore.inc"--></b> </body> </html> Come si può notare index.asp include in se stessa il file contatore.inc. L'estenzione .inc è quella raccomandata per tutti i file realizzati al solo scopo di essere inclusi e non in grado di essere eseguiti in maniera a se stante. Questa è solo una convenzione alla quale può risultare pratico sottostare. Cambiando l'estensione del file il risultato rimane comunque lo stesso. Si vada ora a realizzare un semplice file di testo nominato conteggio.txt e contenente il solo numero 0. Si salvi infine il codice sotto riportato come contatore.inc: <% var file_di_testo = Server.MapPath("conteggio.txt"); var fobj = new ActiveXObject("Scripting.FileSystemObject"); var in_stream = fobj.OpenTextFile(file_di_testo); var stringa_cifra = in_stream.ReadLine(); in_stream.Close(); var intero_cifra = parseInt(stringa_cifra); intero_cifra++; var out_stream = fobj.CreateTextFile(file_di_testo); out_stream.WriteLine(intero_cifra); out_stream.Close(); Response.Write(intero_cifra); %> Si raccolgano i tre file in una sola cartella sul server che abbia abilitati i permessi in lettura/scrittura. Con IIS o PWS solitamente tutte le cartelle godono di questo vantaggio, ma alcuni spazi gratuiti restringono i permessi ad una sola cartella, nominata il più delle volte dati. Si leggano le FAQ relative al proprio spazio Web per saperne di più in merito. Si carichi quindi con il browser la pagina index.asp e la si aggiorni più volte per vedere il contatore incrementarsi. Si tornerà sull'architettura di questo codice non appena saranno trattati in maniera esaustiva gli oggetti build-in e l'ActiveX denominato Scripting.FileSystemObject. Capitolo 3 - L'oggetto built-in Response 3.0 - Scopo ed utilizzo dell'oggetto Response Tra la famiglia degli oggetti built-in di ASP (cfr. capitolo precedente) Response, come il nome lascia ad intendere, è l'oggetto dedicato alla formattazione e all'invio di un qualche output al client. La sintassi necessaria per utilizzare l'oggetto Response è quella classica degli oggetti di JavaScript, dove il nome dell'oggetto è separato dal nome della proprietà o del metodo che sta venendo invocato con un singolo punto. Response.nomeProprietà Response.nomeMetodo(parametro1,parametro2,...) Response.nomeCollezione("chiave") Ecco schematicamente cosa fornisce l'oggetto Response: Proprietà Buffer Booleano, specifica se il buffer è attivo o meno CacheControl Si utilizza per indicare ai proxy di aggiungere o meno l'output della pagina alla propria cache CharSet Aggiunge all'header della pagina la specifica di un particolare charset indicato ContentType Specifica il content type da inviare secondo le norme del protocollo HTTP al client Expires Indica la durata della pagina all'interno della cache del browser ExpiresAbsolute Indica la data di scadenza della pagina archiviata nella cache del browser Status Specifica la linea di stato restituita al client dal server Metodi AddHeader Aggiunge un header specifico alla pagina, di cui è possibile settare nome e valore AppendToLog Aggiunge una riga al Log del server BinaryWrite Invia all'output il parametro passato senza applicare conversioni di charset Clear Ripulisce l'output, ma solo se bufferizzato End Interrompe l'interpretazione della pagina, e restituisce l'output bufferizzato Flush Invia all'istante l'ouput bufferizzato IsClientConnected Verifica durante l'esecuzione della pagina se il client è ancora connesso Pics Aggiunge un valore al PICS label dell'header Redirect Invia un messaggio di redirect al browser, spostandolo su un differente URL Write Scrive sull'output della pagina Collezioni Cookies Setta e recupera i valori relativi ai cookie Non è intenzione di questa guida prendere in analisi sin da ora tutte le proprietà ed i metodi sopra indicati. Molti di essi saranno trattati separatamente nei capitoli seguenti, come ad esempio la collezione Cookies. Per il momento ci si accontenterà di prendere in analisi solo le proprietà e i metodi di uso più ricorrente. 3.1 - Il metodo Response.Write Nei due capitoli precedenti già si è accennato all'utilizzo di questo metodo, utile per inviare un output di testo al client. La giusta sintassi di utilizzo è la seguente: <% Response.Write(stringa); %> Che comunque rimane del tutto equivalente alla più semplice da "embeddare" tra i codici HTML: <%=stringa%> Il parametro stringa, come indica il nome stesso, può essere una stringa o comunque un tipo di dato che abbia rappresentazione in stringa. Con JScript è possibile concatenare più stringhe con l'operatore + direttamente all'interno di Write: nome = "Pippo"; Response.Write("Ciao " + nome + ", io sono un output!"); 3.2 - Il metodo Response.End Il metodo End conclude l'esecuzione della pagina e invia al client gli output non ancora inviati. Tutto quello che segue consequenzialmente una chiamata a Response.End non verrà preso in considerazione, sia esso codice di scripting o semplice output di altro tipo. Si provi il seguente esempio: <%@ LANGUAGE = JScript %> <% Response.Write("IO VENGO SCRITTO"); Response.End; Response.Write("IO NON VENGO SCRITTO"); %> NEMMENO IO VERRO' SCRITTO! 3.3 - Il metodo Response.Redirect Questo metodo segnala al browser del client di spostarsi su un altro URL. Tutto quello che seguirà consequenzialmente una chiamata a Redirect non verrà né interpretato dal server, né comunque ascoltato dal client che sarà passato a fare richiesta ad un altro URL. I percorsi indicati possono essere relativi o assoluti. La sintassi è semplice. l'URL deve essere espresso come una classica stringa. Si prenda a modello il seguente esempio: Response.Redirect("pagina2.htm"); 3.4 - La proprietà Buffer e gli output bufferizzati La proprietà Buffer di Response gestisce una cosa assai importante nell'utilizzo di una tecnologia server-side come ASP. La proprietà, accessibile sia in lettura che in scrittura, rappresenta un valore Booleano (true o false) che sta ad indicare se l'output dovrà essere bufferizzato o meno. Di default questa proprietà è settata a false. Questo significa che normalmente gli output non saranno bufferizzati, e perciò verranno inviati al client man mano che saranno generati. Impostandola invece su true si attiverà la bufferizzazione dell'output. In questa maniera ogni dato inviato all'output (ad esempio tramite Reponse.Write) non sarà girato al client il più presto possibile, ma sarà conservato in un buffer temporaneo, per essere poi spedito al termine dell'esecuzione oppure su comando esplicito del codice (con il metodo Flush). Questo ha vantaggi e svantaggi. Un output non bufferizzato dà l'impressione di essere molto più veloce, specie relativamente a lunghe pagine HTML, e difatti l'utilizzo di output non bufferizzati è la prassi per la generazione dinamica di pagine in HTML. Attivare la bufferizzazione permette però di poter gestire assai più elegantemente il possibile output, intervenendo più volte su di esso. Di default l'output è spedito non appena generato, per questo non è più possibile intervenire su di esso. Se il codice ha postato la stringa "Ciao" al client non potrà più tornare sui propri passi cancellando quanto fatto: quando il codice tenterà di farlo probabilmente l'utente starà già visualizzando il messaggio sul proprio monitor. Con il buffer attivato, invece, un comando di scrittura su output non implica automaticamente l'invio del medesimo al client. Sarà dunque possibile intervenire a più riprese su header e corpo dell'output, con alcuni metodi speciali che hanno significato sono in questo ambito: Response.Clear - ripulisce l'output accumulato nel buffer Response.Flush - invia all'istante i dati accumulati nel buffer al client, senza attendere la fine dell'esecuzione Alcuni metodi di uso comune in presenza di un output bufferizzato si comportano in maniera leggermente diversa dall'usuale: Response.End - oltre ad interrompere l'esecuzione in questo caso invia al client l'output cumulato nel buffer Response.Redirect - può essere utilizzato anche dopo aver già scritto tutti gli header nell'output. In questo caso ripulirà il buffer e inserirà il comando di redirect al posto dei dati cumulati 3.5 - Le proprietà Status e ContentType Status e ContentType sono altre due proprietà che vanno ad influenzare l'header della risposta inviata dal server al client. Per quanto possibile si è cercato di non addentrarsi nei meandri del protocollo HTTP, la cui conoscenza faciliterebbe la comprensione del capitolo, tuttavia ora diviene necessaria una piccola immersione nelle sue specifiche. Non se ne risentano i lettori più attenti, si sta cercando solamente di essere semplici e facilmente comprensibili. Il protocollo HTTP altro non è che un insieme di regole che descrive come debba avvenire un dialogo tra un client ed un server. L'esempio più sfruttato da manuali e guide che tentano di illustrare le funzionalità di tale protocollo è quello della telefonata, e qui non si vuole venir meno a questa consolidata tradizione. Quando si telefona ad un amico o ad un conoscente si ricorre involontariamente all'uso di un certo "protocollo", o meglio, all'uso di un insieme di regole. Ad esempio chi risponde dice sempre "pronto" per segnalare la sua presenza e la sua disponibilità dall'altra parte del cavo, e immediatamente chi ha effettuato la chiamata risponderà salutando e dichiarando la propria identità. Solo dopo queste operazioni preliminari si passerà al colloquio, ossia al vero scopo della telefonata. Il protocollo descritto per la "telefonata tipo" non è comunque rigido, le cose tra uomini potrebbero svolgersi assai differentemente e probabilmente ci si continuerebbe a capire lo stesso. Sui computer, naturalmente, non esiste flessibilità, per cui tutti i server e tutti i client devono rispondere secondo le regole del protocollo HTTP, che definisce appunto il funzionamento di una "chiamata". Se un client o un server dovessero "rispondere male" la controparte in rete riaggancierebbe subito la linea. Così come per la telefonata tra persone, dove il colloquio vero e proprio viene fatto precedere da un header introduttivo, il protocollo HTTP prevede uno scambio di informazioni iniziali tra il client ed il server. Con le pagine ASP ci si sta occupando della gestione del server, quindi si dovrà fare la parte di colui che risponde. Il client solitamente si collega ad un server mediante protocollo HTTP indicando un URL di suo interesse. Il compito sel server è quello di recuperare, se esiste, il file puntato dall'URL e di spedirlo al client, facendolo però precedere da una serie di informazioni inerenti l'esito della richiesta e la natura del file. Status e ContentType controllano proprio questi ultimi due aspetti citati. Status inserisce nell'header una riga che specifica l'esito della richiesta. Nel protocollo HTTP questo viene identificato da una serie di tre numeri. Ad esempio 200 significa "ok, ho trovato il file, adesso arriva", mentre 404 significa "mi dispiace, il file specificato dall'URL che mi hai passato non esiste". Con Status è possibile alterare questa parte dell'header: Response.Status = "401 Unauthorized"; Questa riga dice al client che non si ha l'autorizzazione per ricevere il file richiesto. In questa sede non saranno trattati tutti i possibili status di un header, per chi fosse interessato all'argomento si consiglia di cercare in rete le specifiche del protocollo HTTP. ContentType definisce la natura del file che si sta per inviare al client. I browser non sono soliti riconoscere i file in base asll'estensione che porta il loro nome, quanto piuttosto rispetto alla descrizione fornita dal server su di essi. ContentType permette di controllare questa descrizione. Con ASP, infatti, non si realizzano solo ed esclusivamente semplici output HTML, ma con l'ausilio di giusti ActiveX esterni è possibilissimo mandare in output ad esempio delle immagini (tipico caso dei contatori ad immagini), dei suoni o comunque altri tipi di output differenti dal classico "text/html" di default. Si provi ad eseguire la seguente pagina ASP: <%@ LANGUAGE = JScript %> <% Response.ContentType = "text/plain"; %> <html> <head> <title>E' una pagina HTML?</title> </head> <body> Cosa vedi? </body> </html> Come è possibile vedere l'HTML compreso nell'output invece di venire interpretato dal browser verrà stampato all'interno della finestra! Questo perchè abbiamo fatto dire al client dal server che la pagina che seguiva era "text/plain", ossia semplice testo, e il browser il semplice testo lo prende e lo stampa senza svolgere ulteriori compiti. Utilizzando Microsoft Internet Explorer non sempre avviene ciò, questo perchè la Microsoft ha scelto di far parlare al suo browser un linguaggio diverso dal resto di Internet, ma non scendiamo in inutili polemiche. Se si voleva lanciare come output un JavaScript era possibile farlo utilizzando la dicitura "application/x-javascript". Sono davvero tanti i possibili MIME TYPE (così vengono chiamate queste stringhe che indicano la tipologia del file) che un browser può riconoscere o che ASP, con le necessarie estensioni, può generare. Qui ne citiamo solo alcuni che potranno tornare utili nelle applicazioni più comuni: Testuali text/html Settato di default, è il comune MIME TYPE di una pagian HTML text/plain File di testo semplice text/rtf File in formato Rich Text Format text/xml File in formato XML Immagini image/gif Immagine GIF image/jpeg Immagine JPEG image/png Immagine PNG Audio audio/midi File MIDI audio/x-wav File audio in formato WAVE audio/x-realaudio File audio in formato RealAudio Altro application/xjavascript File JavaScript application/zip File WinZip Capitolo 4 - utilizzo degli oggetti ActiveX 4.0 - Introduzione agli ActiveX Nelle pagine precedenti si è già accennato agli oggetti basati sulla tecnologia ActiveX (cfr. Capitolo 2). In questo capitolo si intende introdurre il lettore all'utilizzo e alla comprensione di questi oggetti. Alcuni ActiveX, scelti tra i più diffusi e standardizzati, verranno presi in esame all'interno della guida stessa. Come già detto ASP, insieme ai suoi linguaggi di scripting, non fornisce nativamente funzioni avanzate. Con puro codice JScript, infatti, non è possibile accedere ad un database, oppure scrivere un file di testo, oppure ancora inviare una e mail attraverso un server di posta elettronica o collegarsi ad un altro PC sparso per il mondo alla ricerca di informazioni. Per questo motivo molto di ASP è basato sui suoi componenti esterni, l'utilizzo dei quali rende possibile tutte le cose appena citate. Sostanzialmente sotto il nome di ActiveX vanno una serie di tecnologie che permettono ad un software o ad una sua parte di interagire con altre parti esterne, indipendentemente dal linguaggio con il quale esse sono state realizzate. ActiveX, a sua volta, è un sottoinsieme delle classi COM (Component Object Model). In questa guida non sarà illustrata la creazione di un componente ActiveX. Se si è interessati a ciò è bene far riferimento a manuali avanzati di un qualche linguaggio appartenente alla famiglia "Visual", sempre di Microsoft. Gli ActiveX più efficienti sono solitamente realizzati con Visual C++. Qui ci si limiterà solo all'uso di essi all'interno dei documenti ASP. 4.1 - Utilizzo di un ActiveX Con JScript richiamare un ActiveX è estremamente semplice, mediante l'utilizzo dell'oggetto ActiveXObject. Il compito di questo oggetto è quello di attivare e restituire un riferimento proprio ad un componente ActiveX. La sintassi necessaria ricalca la seguente: var rif_oggetto = new ActiveXObject("nomeserver.nometipo"[,"posizione"]); I parametri passati all'oggetto vanno così interpretati: nomeserver è il nome dell'applicazione che fornisce l'oggetto. Questo parametro è obbligatorio nometipo è il nome della classe dell'oggetto da attivare. Anche questo parametro è obbligatorio posizione indica il nome del server nella rete in cui l'oggetto deve venire creato. Questo parametro è facoltativo Una volta creato un oggetto mediante l'utilizzo di ActiveXObject sarà possibile usufruire di esso utilizzando il riferimento restituito: rif_oggetto.proprietà rif_oggetto.metodo(eventuali parametri) 4.2 - Scripting.FileSystemObject Il primo oggetto ActiveX al quale rivolgeremo la nostra attenzione sarà Scripting.FileSystemObject, molto probabilmente l'oggetto ActiveX più diffuso ed utilizzato nelle pagine ASP insieme a quelli della famiglia ADODB. Scripting.FileSystemObject estende il linguaggio di scripting utlizzato fornendolo di una semplice interfaccia utile per interagire con il File System del server. In poche parole grazie a questo ActiveX è possibile leggere e scrivere file sull'hard disk del server, creare cartelle, eliminare file che non servono più, ottenere la lista di tutti i file contenuti in una cartella, gestire file di testo o binari e tanto altro. Inoltre a questo oggetto principale solo collegate anche altre classi di oggetti utili per descrivere i file, le cartelle, i drive e gli stream. Nel corso della guida ci troveremo più volte davanti al loro utilizzo e, dunque, avremo occasione di approfondire l'argomento. Davvero innumerevoli sono i metodi messi a disposizione da Scripting.FileSystemObject: Metodi BuildPath Aggiunge un nome alla fine di un percorso esistente CopyFile Copia uno o più file da una posizione ad un'altra CopyFolder Copia una cartella da una posizione ad un'altra CreateFolder Genera una nuova cartella CreateTextFile Crea un file di testo e restituisce un oggetto TextStream utile per lavorare con il file creato DeleteFile Cancella un file DeleteFolder Cancella una cartella DriveExists Restituisce un booleano che indica se il drive specificato esiste FileExists Restituisce un booleano che indica se il file specificato esiste FolderExists Restituisce un booleano che indica se la cartella specificata esiste GetAbsolutePathName Restituisce un percorso completo a partire da uno ambiguo GetBaseName Restituisce il nome di un file privo dell'estensione GetDrive Restituisce un oggetto Drive relativo all'unità di un percorso specificato GetDriveName Restituisce una stringa contenente il nome dell'unità di un percorso specificato GetExtensionName Restituisce una stringa contenente l'estensione di un file specificato GetFile Restituisce un oggetto File relativo al percorso specificato GetFileName Restituisce l'ultimo componente del percorso specificato che non fa parte della specifica di unità. GetFolder Restituisce un oggetto Folder relativo al percorso specificato GetParentFolderName Restituisce una stringa contenente il nome della cartella principale dell'ultimo componente del percorso specificato. GetSpecialFolder Restituisce la cartella speciale specificata. Cartelle speciali sono ad esempio quelle contenenti file temporanei, file di sistema ecc. GetTempName Restituisce un nome di file o cartella generato in modo casuale, utile per eseguire operazioni che richiedono un file o una cartella temporanea MoveFile Sposta uno o più file in una diversa posizione MoveFolder Sposta una cartella in una diversa posizione OpenTextFile Apre il file specificato e restituisce un oggetto TextStream che può essere utilizzato per leggere il file, scrivere nel file o aggiungere testo alla fine del file Proprietà Drives Un insieme di oggetti Drive disponibili nel computer locale 4.3 - Un esempio di utilizzo del File System Vediamo ora come utilizzare praticamente Scripting.FileSystemObject attraverso un primo semplice esempio: <%@ LANGUAGE = JScript %> <% var fo = new ActiveXObject("Scripting.FileSystemObject"); var text_stream = fo.CreateTextFile(Server.MapPath("prova.txt")); text_stream.WriteLine("Prova di scrittura su file di testo"); text_stream.Close(); %> <a href="prova.txt">Ho appena creato un file di testo! Clicca!</a> Forse non tutti i passaggi effettuati risulteranno chiari sin da subito, in quanto ancora non sono stati visitati l'oggetto TextStream e l'oggetto built-in Server. L'esempio in questione, comunque, utilizza l'ActiveX discusso per generare un file di testo nominato prova.txt che viene salvato nella stessa cartella dove risiede lo pagina ASP. Il prossimo capitolo si occuperà di approfondire la tematica della lettura/scrittura dei file di testo, intimamente legata con il File System. Capitolo 5 - Interazione con i file di testo 5.0 - ASP e i file di testo Grazie all'ActiveX Scripting.FileSystemObject e all'oggetto TextStream ad esso correlato ASP permette la lettura/scrittura di file di testo residenti sul disco rigido del server che mette in esecuzione le pagine realizzate. Questa funzione risulta molto importante ed utile in tantissimi servizi, di pari passo con la possibilità di accesso ai database che sarà analizzata in seguito. Grazie ai semplici file di testo, infatti, è possibile realizzare piccole banche dati per conservare dati ed informazioni, oppure è possibile conservare in maniera disaccoppiata i contenuti e l'impostazione grafica delle pagine di un sito, in maniera tale da rendere più agevole un intervento di qualsiasi tipo su di essi. Sapendo manipolare i file di testo diventa poi un gioco da ragazzi realizzare tipici e diffusi servizi quali i sondaggi, i libri per gli ospiti, i forum e molto altro. Tutto ciò sarà analizzato nel dettaglio nello svolgimento del capitolo prima e del resto della guida poi. 5.1 - OpenTextFile, CreateTextFile e Server.MapPath I due particolari metodi OpenTextFile e CreateTextFile dell'ActiveX Scripting.FileSystemObject, come visto nel corso del capitolo precedente, restituiscono un riferimento ad un oggetto di tipo TextStream, utile per la gestione sequenziale dei file di testo. Prima di passare all'analisi di tale oggetto, quindi, scopriamo anzitutto come utilizzare in maniera corretta questi due metodi. riferimento = oggetto.OpenTextFile(nomefile[, modoIO[, creazione[, formato]]]); Questa è la sintassi "modello" per l'utilizzo di OpenTextFile. Sondiamone i parametri. nomefile - parametro obbligatorio, si tratta di una stringa che identifica in maniera completa il percorso del file di testo da aprire. Il più delle volte capita che non si conosca a priori il persorso completo di un file. Si metta il caso, ad esempio, che si voglia aprire il file test.txt posto nella stessa medesima cartella che contiene la pagina ASP in lavorazione. Poichè non è possibile impostare percorsi relativi è altrettanto impossibile scrivere semplicemente oggetto.OpenTextFile("test.txt"), visto che la stringa "test.txt" è priva di significato per il metodo in questione. La sintassi giusta richiederebbe perciò un'espressione del tipo "C:\inetpub\wwwroot\nomecartella\test.txt". Un tipo di espressione del genere, comunque, sarebbe una vera e propria mina per la portabilità del codice: spostando la pagina ASP da una cartella ad un'altra diverrebbe necessario cambiare ogni riferimento al file, e lo stesso avverrebbe spostandosi da un server ad un altro, ad esempio dal proprio PWS di casa ad un IIS su Internet. Per porre rimedio a ciò verrà usato un particolare metodo dell'oggetto built-in Server, che ancora dobbiamo considerare nel suo insieme. Per ora, quindi, basti sapere che Server.MapPath("test.txt") è in grado di restituire il persorso completo del file test.txt partendo da un riferimento relativo. Per questo la sintassi corretta per aprire il nostro file di testo diviene oggetto.OpenTextFile(Server.MapPath("test.txt")). modoIO - parametro facoltativo, si tratta di un intero (o di una equivalente forma mnemonica costante) in grado di specificare la modalità di apertura del file. Il valore 1 (ForReading) indica che il file di testo viene aperto in sola lettura, il valore 2 (ForWriting) indica che il file di testo viene aperto in sola scrittura, il valore 8 (ForAppending) indica che il file di testo può essere scritto appendendo le nuove righe dettate in coda a quelle già presenti in esso (situazione molto sfruttata nei file di log). Se non specificato è automaticamente 1. creazione - parametro facoltativo, si tratta di un booleano (true o false) che indica come il metodo debba comportarsi qualora il file di testo specificato non risulti esistente. Se il valore è true il file di testo verrà generato automaticamente. formato - parametro facoltativo, si tratta di una forma mnemonica costante in grado di specificare il formato da utilizzare nella lettura/scrittura del file di testo. I valori ammessi sono: TristateTrue, che indica l'utilizzo del formato Unicode, TristateFalse, che indica l'utilizzo del formato ASCII e TristateUseDefault, che apre il file utilizzando l'impostazione predefinita di sistema. Se omesso il file verrà aperto in modalità ASCII. Non ci si deve comunque spaventare davanti a questa immane mole di dati: il più delle volte si ricorre all'utilizzo del solo parametro obbligatorio nomefile. Assai simile è il comportamento di CreateTextFile, che si utilizza secondo il modello sotto fornito: riferimento = oggetto.CreateTextFile(nomefile[, sovrascrittura[, unicode]]); nomefile - parametro obbligatorio, del tutto equivalente a quanto detto rispetto all'omonimo parametro obbligatorio di OpenTextFile. sovrascrittura - parametro facoltativo, si tratta di un valore booleano che indica se è possibile sovrascrivere file di testo già esistenti. unicode - parametro facoltativo, si tratta di un valore booleano che stabilisce il formato di codifica da utilizzare: true sta per il formato Unicode, mentre false per il formato ASCII. Se omesso il file verrà codificato secondo il formato ASCII. 5.2 - L'oggetto TextStream Ambo i metodi appena visti restituiscono un oggetto di tipo TextStream in grado di permettere al meglio l'accesso sequenziale ad un testo (in inglese stream, appunto). Tramite i metodi e le proprietà fornite da questo oggetto sarà possibile leggere, scrivere e muoversi dentro un file di testo. La seguente tabella rappresenta schematicamente quanto offerto: Metodi Close Read Chiude e conclude l'accesso sequenziale ad un file Accetta un parametro intero n Legge e restituisce quindi n caratteri dal file di testo manovrato • dall'oggetto di invocazione ReadAll Legge l'intero contenuto del file e lo restituisce come stringa • ReadLine Legge un'intera riga del file fino al carattere di "nuova riga" escluso e la restituisce come stringa • Skip Accetta un parametro intero n Durante la lettura di un file salta n caratteri e posiziona lo stream sul carattere immediatamente successivo all'ultimo ignorato • SkipLine Salta ed ignora un'intera riga del file • Write Accetta un parametro intero str Stampa quindi il contenuto di str all'interno del file • WriteBlankLines Accetta un parametro intero n Scrive quindi nel file n righe vuote • WriteLine Accetta un parametro intero str Stampa quindi il contenuto di str all'interno del file e lo fa seguire da un carattere di "nuova riga" • Proprietà AtEndOfLine AtEndOfStream Restituisce true se il puntatore dello stream è posizionato immediatamente prima dell'indicatore di fine di riga, restituisce • false in caso contrario Restituisce true se il puntatore dello stream è posizionato immediatamente prima dell'indicatore di fine file, restituisce • false in caso contrario Column Restituisce il numero di colonna della posizione di carattere corrente del file • Line Restituisce il numero di linea della posizione di carattere corrente del file • • METODO O PROPRIETA' UTILIZZABILE SOLO IN FASE DI LETTURA DEL FILE DI TESTO • METODO O PROPRIETA' UTILIZZABILE SOLO IN FASE DI SCRITTURA DEL FILE DI TESTO 5.3 - Lettura di un archivio basato su file di testo Una volta a conoscenza degli "ingredienti" messi a disposizione dall'oggetto TextStream è ora necessario imparare a realizzare delle buone miscele in grado di produrre utili risultati. L'esempio che segue utilizza un file di testo per memorizzare una classica rubrica ed un file ASP per la lettura e l'intabellamento su output HTML di essa. Per prima cosa si realizzi il file di testo rubrica.txt: [email protected]$http://www.pinco.it [email protected]$http://utenti.tripod.it/pallino [email protected]$http://go.to/caio [email protected]$http://www.tizio.net Questo file di testo reca al suo interno quattro righe, che possono essere aumentate o diminuite a proprio piacimento, ognuna delle quali riporta tre informazioni relative ad un singolo utente, secondo la seguente sintassi: nome_utente$email_utente$homepage_utente La seguente pagina ASP attinge i dati da questo archivio e li propone all'interno di una semplice tabella HTML, con tanto di link ipertestuale relativamente alle ultime due informazioni di ogni riga: <%@ LANGUAGE = JScript %> <html> <head> <title>Rubrica in ASP su file di testo</title> </head> <body> <% // Richiama Scripting.FileSystemObject var fso = new ActiveXObject("Scripting.FileSystemObject"); // Apr il file di testo in lettura var stream = fso.OpenTextFile(Server.MapPath("rubrica.txt")); %> <table border="1"> <tr> <td><b>NOME</b></td> <td><b>E MAIL</b></td> <td><b>HOME PAGE</b></td> </tr> <% while (!stream.AtEndOfStream) { // Legge una singola riga aux = stream.ReadLine(); // Divide la riga ad ogni occorrenza di $ arr = aux.split("$"); %> <tr> <td><%=arr[0]%></td> <td><a href="mailto:<%=arr[1]%>"><%=arr[1]%></a></td> <td><a href="<%=arr[2]%>" target="blank"><%=arr[2]%></a></td> </tr> <% } %> </table> <% // Chiude lo stream aperto in precedenza stream.Close(); %> </body> </html> I passi essenziali da comprendere sono 5: 1. Il file di testo rubrica.txt viene aperto ed un riferimento ad esso viene tenuto nella variabile stream 2. Grazie all'utilizzo della proprietà AtEndOfStream (true solo quando il file è stato interamente letto) il contenuto del ciclo while viene ripetuto tante volte quante sono le righe presenti nel file di testo 3. Ogni riga viene sequenzialmente letta e divisa in un Array di tre stringhe grazie al metodo Split("$") dell'oggetto String di JScript 4. I risultati vengono inviati su output HTML 5. Lo stream viene chiuso ricorrendo al metodo Close Qualsiasi intervento si faccia ora nei confronti del file di testo rubrica.txt si vedrà il cambio riflesso nella pagina HTML prodotta dal codice ASP. Gestire un archivio su file di testo è molto più semplice che amministrare le stesse identiche informazioni già intabellate in un file HTML. Sul file rubrica.txt, infatti, si potrebbero ipotizzare con un po' di sapienza delle aggiunte automatiche o delle ricerche altrimenti impossibili o comunque complesse su un file HTML. Torneremo su questi argomenti nei capitoli successivi. Come esercitazione si provi per il momento ad utilizzare i metodi CreateTextFile e WriteLine per generare automaticamente da pagina ASP un file di testo sul proprio hard disk. Capitolo 6 - L'oggetto built-in Request 6.0 - Scopo e utilizzo dell'oggetto Request L'oggeto built-in Request, il secondo di questo tipo sul percorso della guida, è il componente dedicato al recupero delle informazioni di input messe a disposizione di una pagina ASP. Maneggiando questo oggetto è possibile in particolar modo recuperare i contenuti dei moduli HTML inviati in allegato alla richiesta della pagina dal client, leggere il contenuto dei cookie precedentemente impostati, recuperare delle informazioni relative al tipo di connessione remota ed altro. Insomma, con Request è possibile ottenere in fretta tutte quelle informazioni fornite da un utente o relative ad un utente. Alla lettura/scrittura dei cookie sarà dedicato un capitolo a parte, quindi in questo momento si istruirà il lettore all'utilizzo delle altre caratteristiche elencate. La conoscenza e la capacità di buon uso dei form HTML sono prerequisiti richiesti per la comprensione di molte delle cose che verranno dette. Come per il resto degli oggetti bulit-in, l'utilizzo di Request come oggetto precostituito è intuitivo e basato sulla classica nozione puntata: Request.nomeProprietà Request.nomeMetodo(parametro1,parametro2,...) Request.nomeCollezione("chiave") Schematicamente ecco i controlli messi a disposizione dall'oggetto Request: Proprietà TotalBytes Specifica il numero totale di byte inviati dal client nel corpo della richiesta. Di sola lettura Metodi BinaryRead Restituisce i dati inviati dal client come parte della richiesta POST Collezioni ClientCertificate Collezione dei certificati inviati dal client Cookies Collezione dei valori relativi ai cookie di libero accesso da parte della pagina Form Collezione che riporta i valori degli elementi di un form inviato insieme alla richiesta HTTP con utilizzo del metodo POST QueryString Collezione che riporta i valori delle variabili rappresentat nella query string ServerVariables Collezione che riporta i valori di alcune predefinite variabili d'ambiente 6.1 - La collezione Request.Form Le pagine ASP sono in grado di leggere i dati a loro inviati dall'utente con metodo POST mediante i semplici form HTML. Si consideri il seguente modulo: <form action="recupera.asp" method="POST"> <input type="text" name="text1" value=""> <input type="submit" value="invia il testo"> </form> Nella pagina di destinazione dei dati, recupera.asp nel caso dell'esempio, i dati possono essere recuperati mediante la scansione della collezione Request.Form. Ciò avviene in maniera molto semplice: per leggere il testo inserito nel campo di nome text1 è sufficiente chiamare var testo = Request.Form("text1"); utilizzando quindi come chiave di lettura della collezione lo stesso nome scelto per il corrispondente campo presente nel form della pagina HTML di partenza. Una tecnica utilizzata per individuare se si è ricevuto o meno un certo dato è basata sulla conversione in stringa del dato che si ricerca. Un Request.Form assente convertito in tale forma darà origine al valore di testo undefined: var testo = String(Request.Form("text1")); if (testo=="undefined") // non è stato inviato alcun campo nominato text1! Tutti i campi restituiscono solitamente la stringa value a loro associata. Nei campi di tipo text il valore è direttamente immesso dall'utente, mentre ad esempio in quelli di tipo hidden è scelto direttamente dall'autore della pagina e non più modificabile. In presenza di bottoni di tipo radio viene restituito il value associato alla scelta fatta. Ad es. <input type="radio" name="r1" value="primo"> <input type="radio" name="r1" value="secondo"> <input type="radio" name="r1" value="terzo"> Nel caso l'utente abbia selezionato il secondo tasto il contenuto di Request.Form("r1") sarà proprio "secondo", similmente negli altri casi. Interessante è il comportamento dei checkbox. A differenza dei pulsanti radio, infatti, i checkbox ammettono la selezione multipla. Qualora l'utente selezionasse un solo tasto tra tutti quelli aventi lo stesso nome si recupererebbe in forma di stringa il value ad esso associato. Qualora invece i pulsanti selezionati fossero due o più si otterrebbe indietro un Array di stringhe di tanti elementi quante sono le caselle spuntate. Ogni stringa dell'Array rappresenterà il value di un differente pulsante tra quelli scelti. Comportamenti simili avvengono per i campi select a selezione multipla. Basta sapere un po' maneggiare i form HTML per esplorare in breve tutte le possibili combinazioni. Nel capitolo successivo si metteranno fattivamente in pratica tali nozioni. 6.2 - La collezione Request.QueryString La collezione Request.QueryString è per molti versi analoga a Request.Form, con la particolare differenza che i dati passati alla pagina devono essere appesi in una query string (cioè accoppiati alla richiesta dell'URL e divisi da esso per mezzo di un punto di domanda "?"). Un tipico esempio di una query string appesa ad un URL è il seguente: http://www.dominio.com/dir1/pagina.asp?testo=ciao%20a%20tutti I browser generano automaticamente dati in query string inviando dei moduli con metodo GET. Si prenda a modello il segiente form: <form action="recupera.asp" method="GET"> <input type="hidden" name="h1" value="testo di prova"> <input type="submit"> </form> L'invio di tale modulo appende all'URL di recupera.asp la seguente query string: ?h1=testo%20di%20prova Il recupero dei dati avviene poi in maniera naturale e simile a quella analizzata con Request.Form: var testo = Request.QueryString("h1"); // testo sarà uguale a "testo di prova" La convenienza dell'utilizzo di una query string consiste nel fatto che si possono concedere alla pagina di destinazione dei parametri anche senza dover necessariamente ricorrere all'ausilio dei form. Possono benissimo essere creati, infatti, dei collegamenti ipertestuali già impostati con una query string: <a href="apri.asp?id=56">Messaggio numero 56</a> Si pensi un momento alla potenza di questa tecnica che permette di gestire automaticamente grossi archivi di dati in continua modifica con un insieme di poche pagine che compiono azioni differenti a seconda della query string a loro passata. Nel caso di un forum per discussioni, ad esempio, ogni singolo messaggio potrebbe essere memorizzato in un database o in un file di testo in maniera tale che ad esso corrisponda univocamente in qualche modo un identificatore numerico. Si potrebbe realizzare così un'unica pagina ASP che mostri il contenuto del messaggio il cui identificatore è proprio quello specificato dalla query string. Si eviterebbe così di dover creare un differente documento HTML per ogni messaggio, con notevole risparmio di risorse. Gli aspetti particolari derivanti da questa tecnica saranno esaminati a livello pratico nei tutorial che si incontreranno nel percorso di questa guida. Per ora però si tenga presente un aspetto fondamentale. Nelle query string molti caratteri considerati non ammissibili in una richiesta di URL (come gli spazi bianchi o le lettere accentate) devono essere sostituiti con il loro corrispondenti codici ASCII nel formato %xx, dove xx è un valore esadecimale. I browser effettuano automaticamente la conversione dei dati verso questo formato se essi sono veicolati attraverso un form inviato con metodo GET. Per questo motivo il value dell'esempio precedente "testo di prova" diventava automaticamente in query string "testo%20di%20prova". I dati restituiti dalla collezione Request.QueryString subiscono automaticamente la conversione inversa. Nel caso in cui si voglia però appendere "manualmente" una query string ad un URL, come nell'esempio del link ipertestuale di poche righe sopra, bisogna porre attenzione a questo aspetto, in quanto il compito di effettuare la conversione non ricadrebbe più sul browser. JScript, relativamente a tale questione, fornisce due funzioni native per la conversione delle stringhe: escape(stringa) e unescape(stringa). L'utilizzo è semplice e lo si deduce dal seguente esempio: var testo1 = "testo di prova"; var testo2 = escape(testo1); Response.Write(testo2); // stampa: testo%20di%20prova var testo3 = unescape(testo2); Response.Write(testo3); // stampa: testo di prova 6.3 - La collezione Request.ServerVariables La collezione Request.ServerVariables si occupa del fornire delle informazioni relative all'utente connesso ad una pagina o del restituire i valori di alcune variabili d'ambiente. L'utilizzo è classico, basato sulla nozione puntata e sulle chiavi: var variabile = Request.ServerVariables("nome_chiave"); Le possibili chiavi sono molteplici ed alcune di esse dipendono strettamente dal server che elabora la pagina. In questa sede si farà una cernita solo di quelle più ricorrenti e di quelle più frequentemente utilizzate: Chiave Utilizzo ALL_HTTP Restituisce tutti gli header HTTP associati alla richiesta del client CONTENT_LENGTH Restituisce la lunghezza dei contenuti inviati dal client CONTENT_TYPE Restituisce il tipo di dato dei contenuti inviati dal client. Si utilizza con le richieste che trasmettono informazioni a partire dal client, come con i form HTML inviati con metodo POST o GET HTTP_<HEADER> Sostituendo <HEADER> con il nome di uno specifico header appartenente alla richiesta HTTP è possibile conoscerne il contenuto HTTPS Restituisce ON se la richiesta arriva attraverso canali sicuri (SSL), OFF in caso contrario LOCAL_ADDR Restituisce l'indirizzo del Server QUERY_STRING Restituisce l'intera query string associata all'URL REMOTE_ADDR Restituisce l'indirizzo IP dell'host che ha inviato la richiesta REMOTE_HOST Restituisce il nome dell'host che ha inviato la richiesta SCRIPT_NAME Restituisce il percorso virtuale dello script in esecuzione SERVER_NAME Restituisce il nome del server, il suo alias DNS o il suo indirizzo IP SERVER_PORT Restituisce il numero della porta alla quale la richiesta è stata inviata SERVER_SOFTWARE Restituisce il nome e la versione del server software che ha risposto alla richiesta 6.4 - Esercitazione sull'oggetto Request Il seguente esempio è utile per mettere meglio a fuoco quanto appreso nei paragrafi precedenti. Verrà realizzato un modulo HTML per l'invio dei dati attraverso metodo GET e una pagina ASP in grado di riceverli, interpretarli e maneggiarli al fine di produrre un output dimostrativo delle potenzialità dell'oggetto Request. Si cominci allora proprio dal modulo, che può essere racchiuso in una semplice pagina HTML chiamata form.htm: <html> <head> <title>Esercitazione sull'oggetto Request</title> </head> <body> Compila ed invia il seguente modulo:<br> <form action="elabora.asp" method="get"> Introduci il tuo nome:<br> <input type="text" name="nome" value=""><br> Introduci il tuo cognome:<br> <input type="text" name="cognome" value=""><br><br> Specifica il tuo sesso:<br> <input type="radio" name="sesso" value="M"> M <input type="radio" name="sesso" value="F"> F <br><br> <input type="submit" value="Invia il modulo"> </form> </body> </html> La pagina ASP in grado di ricevere i dati deve ora essere posizionata nella medesima cartella di form.htm e deve essere nominata elabora.asp: <%@ LANGUAGE = JScript %> <% // Recupero del modulo var nome = Request.QueryString("nome"); var cognome = Request.QueryString("cognome"); var sesso = Request.QueryString("sesso"); var dizione = (sesso=="F") ? "una femminuccia" : "un maschietto"; var SCRIPT = Request.ServerVariables("SCRIPT_NAME"); var SERVER_NAME = Request.ServerVariables("SERVER_NAME"); var QUERY_STRING = Request.ServerVariables("QUERY_STRING"); var IP = Request.ServerVariables("REMOTE_ADDR"); %> <html> <head> <title>Esercitazione sull'oggetto Request</title> </head> <body> <b>GRAZIE PER AVER SPEDITO IL MODULO!</b><br><br> Ecco i dati che mi hai inviato:<br><br> Nome: <b><%=nome%></b><br> Cognome: <b><%=cognome%></b><br><br> Stando a quanto dici dovresti essere <%=dizione%><br><br> In più io aggiungo:<br><br> Nome di questa pagina: <b><%=SCRIPT%></b><br> Nome del server: <b><%=SERVER_NAME%></b><br> Tuo indirizzo IP: <b><%=IP%></b><br><br> L'intera query string che mi hai passato è: <b><%=QUERY_STRING%></b><br><br> </body> </html> Come ulteriore esercitazione si provi a produrre un secondo form utilizzando però il metodo POST per il suo invio, ed implementando magari l'uso di campi di input quali checkbox, textarea e select. Capitolo 7 - Un libro degli ospiti in ASP 7.0 - Introduzione al tutorial E' finalmente giunto il momento di mettere a frutto le conoscenze acquisite nel corso dei capitoli precedenti, realizzando una prima applicazione ASP di una certa entità. Lavorando con JScript, oggetti built-in, oggetti ActiveX e accesso al File System realizzeremo un semplice guestbook, in Italia meglio noto come "libro degli ospiti". Si tratta sostanzialmente di una pagina Web all'interno della quale un visitatore può inserire un messaggio, in maniera abbinata ad alcune informazioni, quali il proprio nome, l'indirizzo e mail e l'URL della propria Home Page. Insomma, il classico servizio che molti siti amatoriali ospitano per dare voce ai propri visitatori. Il codice e le regole che esamineremo, comunque, avranno ancora una volta valore generale, per cui con un po' di maestria sarà possibile ottenere tantissime varianti sul tema, realizzando ad esempio delle bacheche per scambio annunci e altri servizi basati fondamentalmente sullo stesso meccanismo del guestbook. Il progetto sarà costituito da 4 files distinti in rapporto tra di loro, lo schema sarà quello rappresentato qui di seguito: I FILE DEL PROGETTO "LIBRO DEGLI OSPITI" 1. messaggi.txt - file di testo utilizzato per conservare i messaggi degli utenti. Da questo file verranno letti i messaggi da visualizzare, e in questo file sarà introdotto ogni nuovo messaggio. 2. guestbook.asp - pagina ASP in grado di prelevare i messaggi contenuti in messaggi.txt e di formattarli in un opportuno output HTML da inviare al browser del visitatore. 3. nuovo_messaggio.htm - pagina di semplice HTML contenente un form da utilizzare per l'invio di un nuovo messaggio alla pagina inserisci_messaggio.asp, con metodo POST 4. inserisci_messaggio.asp - pagina ASP che raccoglie i dati inviati dall'utente, li controlla, li formatta e li inserisce in cima a messaggi.txt. E' bene stabilire ora la struttura di un singolo messaggio, analizzando quali campi possano di fatto costituirlo. Selezioneremo ora alcuni campi esemplificativi, alcuni dei quali dovranno essere inseriti obbligatoriamente, mentre altri potranno essere anche tralasciati. Ecco uno schema relativo alla struttura di un singolo messaggio: NOME IDENTIFICATIVO (campo obbligatorio) E MAIL (campo facoltativo) HOMEPAGE URL (campo facoltativo) CORPO DEL MESSAGGIO (campo obbligatorio) Nei paragrafi successivi vedremo come trasformare questa concettualizzazione del problema in una forma appetibile per un computer, attraverso appunto la tecnologia ASP. 7.1 - Conservazione e lettura dei messaggi Il fulcro dell'intero progetto è rappresentato da messaggi.txt, il semplice file di testo che utilizzeremo come supporto e come contenitore dei messaggi inseriti dagli utenti. Vediamo allora come organizzare, o per meglio dire formattare, questo file affinché possa ospitare una rappresentazione del messaggio teorizzato nel paragrafo precedente. La cosa più ovvia che ci viene in mente è quella di inserire ogni campo su righe distinte, ed è una soluzione piuttosto accettabile per questo nostro primo e semplice tutorial. Potrebbe poi darsi che in certe occasioni ci venga utile agire manualmente sul file di testo, quindi per pura chiarezza in fase di lettura da parte dell'uomo divideremo ogni messaggio con un invio, ossia con una riga bianca. Ecco un esempio di un file di testo in grado di soddisfare le regole appena annunciate: --- INIZIO --Sauron [email protected] http://www.sauronsoftware.it Ciao a tutti i lettori! Tizio Io non ho messo e mail e url... tanto sono facoltativi! --- FINE --Questo piccolo esempio presenta un file di testo contenente due messaggi. Le scritte INIZIO e FINE sono da considerarsi escluse e sono state utilizzate solo per mettere in risalto come, anche alla fine del secondo ed ultimo messaggio, sia stata introdotta comunque una riga bianca. Una volta in possesso di questi dati realizzare la pagina guestbook.asp rimane abbastanza semplice. Si realizzi per prima cosa il file messaggi.txt inserendo al suo interno i due messaggi di poche righe sopra, in modo da predisporlo a divenire "cavia" della pagina ASP che stiamo per andare a creare. Ecco cosa dovremo dire al server tramite la pagina guestbook.asp: 1. tramite l'oggetto ActiveX denominato Scripting.FileSystemObject apri uno stream verso messaggi.txt 2. se non sei alla fine dello stream (raggiungimento dell'ultima riga del file di testo) procedi al passo 3, altrimenti chiudi lo stream e concludi 3. leggi una riga, questo è il nome di un messaggio, stampalo nell'output 4. leggi una riga, questa è l'e mail. Il campo è facoltativo. In caso dovesse essere vuoto non fare nulla, altrimenti stampa l'indirizzo e mail nell'output HTML generando un collegamento ipertestuale di tipo mailto: ad esso 5. leggi una riga, questo è l'url per la home page. Il campo è facoltativo. In caso dovesse essere vuoto non fare nulla, altrimenti stampa l'indirizzo nell'output HTML generando un collegamento ipertestuale 6. leggi una riga, questo è il messaggio, stampalo nell'output 7. salta una riga nello stream 8. torna al passo 2 e reitera il ciclo a partire da lì Questi 8 passi descrivono alla perfezione l'algoritmo che abbiamo intenzione di utilizzare. Nella concettualizzazione di esso, ovviamente, si è esclusa ogni forma di ritocco grafico e ci si è concentrati solo sul meccanismo principale. Questa è una maniera molto efficiente di procedere, le aggiunte grafiche possono poi essere fatte indipendentemente in un secondo momento. Ecco come viene ora formulata la pagina guestbook.asp: <%@ LANGUAGE = JScript %> <% // Apertura dello stream verso il file di testo var fso = new ActiveXObject("Scripting.FileSystemObject"); var stream = fso.OpenTextFile(Server.MapPath("messaggi.txt")); %> <html> <head><title>Libro degli ospiti</title></head> <body> <h1>Libro degli ospiti</h1> <h4><a href="nuovo_messaggio.htm">Firmalo anche tu!</a></h4> <% while (!stream.AtEndOfStream) { Response.Write("<hr>"); aux = stream.ReadLine(); Response.Write("<b>Nome:</b> " + aux + "<br>"); aux = stream.ReadLine(); if (aux!="") Response.Write("<b>E mail:</b> <a href='mailto:" + aux + "'>" + aux + "</a><br>"); aux = stream.ReadLine(); if (aux!="") Response.Write("<b>Home Page:</b> <a href='" + aux + "'>" + aux + "</a><br>"); aux = stream.ReadLine(); Response.Write("<b>Messaggio:</b><br>" + aux + "<br>"); stream.SkipLine(); } stream.Close(); %> <hr> <p><font size="2">Powered by "ASP: la guida introduttiva"</font></p> </body> </html> Con questo codice già è possibile sperimentare la prima parte del libro degli ospiti. Qualora dovesse insorgere un errore il consiglio è di controllare attentamente ciò che è stato inserito in messaggi.txt, in particolare la presenza della sola ed unica riga vuota finale. Svuotando completamente messaggi.txt potrete ottenere un guestbook totalmente "vergine". 7.2 - Form di invio del messaggio La seconda parte del progetto è costituita da quei due file che permettono l'inserimento di un nuovo messaggio nell'archivio. Si parte dalla pagina HTML nuovo_messaggio.htm, contenente il form da compilare ed inviare per portare a compimento l'operazione. Non ci si soffermerà più di tanto su di esso, data la banalità della cosa: <html> <head><title>Firma il libro degli ospiti</title></head> <body> <h1>Libro degli ospiti</h1> Introduci il tuo messaggio ed invia il modulo <hr> <form action="inserisci_messaggio.asp" method="post"> <b>Nome: (*)</b><br> <input type="text" name="nome"><br> <b>E mail:</b><br> <input type="text" name="email"><br> <b>Home Page:</b><br> <input type="text" name="homepage" value="http://"><br> <b>Messaggio: (*)</b><br> <textarea rows="5" cols="30" name="messaggio"></textarea> <br> <input type="submit" value="Invia"> <input type="reset" value="Cancella"> </form> <hr> I campi contrassegnati da (*) sono <b>obbligatori</b> </body> </html> Come è possibile vedere i dati sono inviati con metodo POST alla pagina inserisci_messaggio.asp, oggetto di studio del sucessivo paragrafo. 7.3 - Inserimento di un nuovo messaggio Si è finalmente giunti alla parte conclusiva, ma anche più laboriosa, del progetto. Il file inserisci_messaggio.asp dovrà agire in base ai seguenti passi: 1. 2. 3. recupera i dati inviati tramite il form controlla questi dati, in caso di problemi interrompi la procedura e dai notifica dell'accaduto esamina i dati, correggili e ottimizzali per la loro archiviazione 4. apri uno stream in lettura verso messaggi.txt 5. leggi e conserva l'intero contenuto di messaggi.txt 6. chiudi lo stream in lettura 7. crea un nuovo messaggi.txt vuoto sovrascrivendo quello precedente e instaura uno stream in scrittura con esso 8. inserisci in esso il nuovo messaggio 9. inserisci in esso la lista dei messaggi precedenti 10. chiudi lo stream in scrittura Questo algoritmo illustra quindi le operazioni necessarie. I singoli passi sono stati raggruppati in tre diverse classi, in modo da rendere ancora più chiare le tre operazioni base: leggere e validare il form, prelevare il contenuto del file di testo, generare una nuova lista di messaggi contenente in cima ad essa quanto appena inviato dall'utente. Questa doppia operazione di lettura/scrittura di messaggi.txt è dettata dall'esigenza di inserire in testa alla lista il nuovo messaggio, in modo da tale che accedendo a guestbook.asp questo risulti il primo ad essere letto. Qualora si fosse invece optato per un inserimento in coda alla lista sarebbe allora bastato accedere a messaggi.txt una sola volta, in modalità ForAppending. Il messaggio, in questo caso, verrebbe però visualizzato come l'ultimo della lista. Il codice del documento può essere ora presentato: <%@ LANGUAGE = JScript %> <% function togliInvio(str) { var cars; var str2 = ""; for (c=0;c<str.length;c++) { cars = str.charCodeAt(c); if (cars!=13 && cars!=10) { str2 += str.charAt(c); } else { if (cars==13) str2 += " "; } } return str2; } // Ricezione del form var nome = String(Request.Form("nome")); if (nome=="undefined") nome = ""; var email = String(Request.Form("email")); if (email=="undefined") email = ""; var homepage = String(Request.Form("homepage")); if (homepage=="undefined" || homepage=="http://") homepage = ""; var messaggio = String(Request.Form("messaggio")); if (messaggio=="undefined") messaggio = ""; // Controllo campi obbligatori if (nome=="" || messaggio=="") { Response.Write("Non hai compilato tutti i campi obbligatori! Torna indietro e correggi."); Response.End; } // Elimina eventuali "invii" dai vari campi // Evita così che un campo possa andare su più di una riga nome = togliInvio(nome); email = togliInvio(email); homepage = togliInvio(homepage); messaggio = togliInvio(messaggio); var fso = new ActiveXObject("Scripting.FileSystemObject"); // Legge i messaggi vecchi var in_stream = fso.OpenTextFile(Server.MapPath("messaggi.txt")); var vecchi = in_stream.ReadAll(); in_stream.Close(); // Crea il nuovo file var out_stream = fso.CreateTextFile(Server.MapPath("messaggi.txt")); out_stream.WriteLine(nome); out_stream.WriteLine(email); out_stream.WriteLine(homepage); out_stream.WriteLine(messaggio); out_stream.WriteLine(); out_stream.Write(vecchi); out_stream.Close(); Response.Redirect("guestbook.asp"); %> La particolare funzione togliInvio fornita nel codice si occupa di eliminare ogni eventuale andata a capo presente nei vari campi. Un invio, infatti, verrebbe riportato come tale all'interno dello stesso file di testo e perciò il nostro modello di messaggio non verrebbe più rispettato. Davanti alle "conquiste" fatte nel corso dei capitoli precedenti la comprensione del codice dovrebbe risultare piuttosto agevole. In ogni caso i blocchi di codice sono commentati e divisi tra di loro seguendo la stessa logica utilizzata per raggruppare i diversi passi dell'algoritmo utilizzato. Si lascia ora al lettore la facoltà di migliorare e di arricchire il progetto secondo la propria fantasia. Ecco alcune possibili tracce di lavoro sulle quali esercitarsi: disabilitare la possibilità da parte dell'utente di inserire codice HTML nei messaggi creare una console di amministrazione del guestbook, attraverso la quale sia possibile cancellare messaggi indesiderati limitare il numero di messaggi presenti nel file di testo ad un numero finito, ad esempio facendo in modo che all'inserimento del ventunesimo messaggio venga automaticamente eliminato il primo Buon lavoro! Capitolo 8 - L'oggetto built-in Server 8.0 - Proprietà e metodi di Server L'oggetto built-in Server, il terzo oggetto di questo tipo presentato nel corso della guida, si occupa di fornire il controllo di certi metodi e proprietà del server, alcuni dei quali davvero utili nella realizzazione di un'applicazione ASP. L'interfaccia dell'oggetto built-in Server è costituita da quattro metodi ed una proprietà. Metodi CreateObject Genera un'istanza di una componente del server HTMLEncode Applica le regole della codifica HTML ad una stringa MapPath Trasforma un percorso relativo nel corrispondente percorso assoluto URLEncode Applica le regole della codifica URL ad una stringa Proprietà ScriptTimeout Specifica in secondi la durata massima di esecuzione dello script 8.1 - Il metodo Server.CreateObject Il metodo CreateObject(progID) genera un'istanza della componente installata sul server identificata tramite la stringa progID. Per certi versi l'uso di questo metodo può risultare equivalente al costruttore della classe ActiveXObject, fornita nativamente da JScript. Negli esempi incontrati sinora, infatti, ogni oggetto ActiveX è stato istanziato mediante la sintassi var riferimentoOggetto = new ActiveXObject(idOggetto); E' dunque possibile fare altrettanto attraverso la sintassi var riferimentoOggetto = Server.CreateObject(idOggetto); Ad esempio si potrebbe istanziare Scripting.FileSystemObject alla seguente maniera: var fso = Server.CreateObject("Scripting.FileSystemObject"); Gli oggetti istanziati in questa maniera godono di quello che è generalmente definito il "page scope", ovvero restano in vita tanto quanto dura il processo di interpretazione e di esecuzione della pagina che li dichiara, al termine del quale vengono distrutti. ASP, comunque, permette anche di istanziare anche oggetti con scope differenti, ad esempio con un ciclo di vita equivalente alla durata della sessione di un utente, oppure della durata dell'intera applicazione. Si tornerà su questo argomento nei capitoli a venire, quando saranno presi in esami i due rimanenti oggetti built-in: Session e Application. Molte componenti vengono distribuite insieme al Web Server stesso, per questo alcune di esse sono considerate standard e praticamente parte integrante di ASP. I componenti più diffusi per ASP MSWC.AdRotator Oggetto utilizzato per la gestione automatica dei banner in rotazione sulle pagine Web MSWC.BrowserType Oggetto in grado di fornire allo script delle informazioni sul browser del client Scripting.FileSystemObject Oggetto che fornisce accesso al file system ADODB.Connection Sfruttatissimo oggetto che fornisce accesso ai database MSWC.NextLink Oggetto utilizzato per indicizzare diversi URL e gestirli come si trattasse di un libro MSWC.Tools Oggetto che raccoglie alcune funzioni di piccola utilità MSWC.Counters Oggetto utilizzato per gestire dei contatori di accesso Prima di far ricorso ad una qualsiasi componente esterna, comunque, è sempre bene verificare che essa sia installata all'interno del Web Server che dovrà ospitare il lavoro una volta completato. 8.2 - Il metodo Server.HTMLEncode Il metodo HTMLEncode(stringa) applica ad una stringa la codifica HTML. Si tratta di un metodo che può tornare utile in certe occasioni, ad esempio quando si vogliono disabilitare dei marcatori HTML. Il comando Response.Write(Server.HTMLEncode("<b>grassetto</b>")); restituisce un output come il seguente &lt;b&gt;grassetto&lt;/b&gt; I due tag così convertiti non vengono più riconosciuti tali dal parser del browser, eppure &lt; e &gt; vengono mostrati a video esattamente come i caratteri di minore e maggiore. 8.3 - Il metodo Server.MapPath Il metodo MapPath(stringa), che già è stato presentato al lettore nel corso dei capitoli precedenti, si occupa del desumere un percorso assoluto per un file o una cartella a partire dal suo percorso relativo. Poichè molte componenti necessitano di percorsi assoluti per interagire con il file system il ruolo ricoperto dal metodo Server.MapPath è di vitale importanza, e permette sostanzialmente la creazione di applicazioni indipendenti dall'organizzazione delle cartelle del server che le ospita. Tanto per portare un esempio, se la root del server fosse c:\inetpub\wwwroot il codice Response.Write(Server.MapPath("file.txt")); se incluso in un documento ASP posto esattamente in tale cartella produrrebbe l'output c:\inetpub\wwwroot\file.txt 8.4 - Il metodo Server.URLEncode Il metodo URLEncode(stringa), applica ad una stringa le regole della codifica utilizzata nella formulazione degli URL. Sostanzialmente questa stessa funzione è già offerta nativamente da JScript, tramite il comando escape(). JScript, inoltre, è in grado di effettuare anche la codifica inversa, attraverso l'uso di unescape(). Il codice Response.Write("cerca.asp?words=" + Server.URLEncode(risorse per il webmaster)); fornirebbe l'output cerca.asp?words=risorse%20per%20il%20webmaster 8.5 - La proprietà Server.ScriptTimeout Il valore dell'intero ScriptTimeout può essere modificato per determinare il tempo di esecuzione massimo concesso ad uno script. Può infatti capitare che, per svariati fattori, una pagina impegni troppo a lungo il server, senza riuscire a completare la propria esecuzione. Per questo esiste un "tetto massimo" di secondi concesso ad uno script entro il quale tutti i compiti devono essere ultimati. Superato questo limite l'interpretazione della pagina viene bloccata e al client viene restituito un errore. Il valore di default di questa proprietà è settato solitamente a 90 secondi. E' possibile quindi modificare tale arco di tempo in base alle proprie necessità, soprattutto per prolungarlo quando di sa a priori che la pagina che si sta realizzando necessiterà di un tempo superiore al limite imposto. La modifica è semplice: Server.ScriptTimeout = 120; Questo esempio porta il tempo massimo di esecuzione a due minuti (120 secondi) per la pagina corrente. Capitolo 9 - ASP e database 9.0 - Introduzione Uno delle peculiarità che ha portato ASP ad una vasta diffusione consiste nella facilità con la quale è possibile interfacciare uno script ad una fonte di dati che rispetti lo standard detto ODBC (Open Database Connectivity). La connettività verso fonti di dati ODBC permette di realizzare con il minimo sforzo un innumerevole quantitativo di applicazioni, che spazia dal semplice guestbook fino ad arrivare a potenti interfacce di ricerca su cataloghi online. In pratica grazie ai database diventa semplice la catalogazione di lunghe moli di dati e facile l'accesso ad essi grazie all'oggetto ActiveX Data Object (abbreviato ADO), che fornisce connettività verso tutti i tipi di database che supportino ODBC, mediante un driver apposito. In ASP, a livello elementare, è praticamente prassi l'utilizzo dei database di Access, non a caso prodotto sempre da Microsoft. Per addentrarsi nello studio della connettività verso fonti di dati ODBC non è ora richiesto alcun prerequisito particolare, anche se magari una praticità già acquisita con i database manager e con SQL potranno certamente aiutare nella comprensione dei paragrafi e dei capitoli successivi dedicati all'argomento. Tutti gli esempi riportati in questa sede si baseranno, per amore dello standard di ASP, sui database di Access. Sarà dunque necessario disporre di una versione abbastanza recente di questo software (97 o preferibilmente 2000) per procedere. 9.1 - Primo approccio ad ADO Come appena detto ADO è il tramite necessario per operare, sia in lettura che in scrittura, su una fonte di dati ODBC. Il primo oggetto che si incontra analizzando questo componente è Connection, che, come il nome lascia ad intuire, si occupa di fornire la connettività verso un database. L'oggetto Connection va dichiarato come un qualsiasi ActiveX, mediante la seguente sintassi: var ADOConn = new ActiveXObject("ADODB.Connection"); Tramite il riferimento ADOConn è ora possibile accedere a metodi e proprietà di tale oggetto. Prima di procedere oltre, al fine di seguire gli esempi presentati, è necessario ora realizzare un piccolo database Access di prova. Si vada ad avviare la propria copia di Access, si scelga dall'interfaccia iniziale l'operazione di creazione di un nuovo database vuoto e lo si vada a riporre, con un nome arbitrario, ad esempio prova.mdb, nella cartella che ospiterà i codici ASP analizzati di seguito. Da Access ora si crei una nuova tabella in visualizzazione struttura ed in essa si vadano ad inserire i seguenti campi: id, di tipo contatore, da selezionare come chiave primaria mediante click sull'iconcina a forma di chiave (la riga di questo record nella visualizzazione struttura deve essere selezionata mentre si clicca sull'icona) nome, di tipo testo cognome, di tipo testo e_mail, di tipo testo Chiudendo la finestra di visualizzazione struttura si scelga di salvare tali aggiunte e si assegni un nome alla tabella appena creata, ad esempio nominativi. Tornando alla schermata precedente sarà ora possibile visualizzare nell'elenco delle tabelle quella appena creata. Si esegua un doppio click sul suo nome per accedere alla finestra che permetterà di immettere dati all'interno della tabella. Per testare gli esempi si generino due rige (record) con dati arbitrari e fantasiosi, ad esempio Mario Rossi [email protected] e Giuseppe Bianchi [email protected]. Come è possibile notare il campo id, che è un contatore nonché la chiave primaria, si incrementa da solo ad ogni inserimento di record, generando una relazione univoca tra l'insieme dei record inseriti e i numeri naturali. A questo punto si salvi tutto e si chiuda pure Access, il database di esempio è pronto. Si utilizzi ora in maniera dogmatica il seguente codice, tanto per vedere in pratica sin dall'inizio il risultato cercato: <%@ LANGUAGE = JScript %> <% // Istanzio l'oggetto Connection var ADOConn = new ActiveXObject("ADODB.Connection"); // Creo la STRINGA DI CONNESSIONE var strConn = ""; strConn += "driver={Microsoft Access Driver (*.mdb)};"; strConn += "dbq=" + Server.MapPath("prova.mdb"); // Apro la connessione al database selezionato ADOConn.Open(strConn); // Interrogo il database var sql = "SELECT * FROM nominativi"; var ris = ADOConn.Execute(sql); // Stampo i dati contenuti nel database while (!ris.EOF) { Response.Write("Record n° " + ris("id") + "<br>"); Response.Write("Nome: " + ris("nome") + "<br>"); Response.Write("Cognome: " + ris("cognome") + "<br>"); Response.Write("E mail: " + ris("e_mail") + "<br><br>"); ris.MoveNext(); } // Chiudo la connessione ADOConn.Close(); %> Non si meravigli il lettore se l'esempio non dovesse risultare da subito di facile compresione, questo stesso codice servirà ora come appoggio per analizzare nel dettaglio le diverse fasi dell'accesso ad un database. 9.2 - Metodi e proprietà di Connection All'interno dell'esempio di poche righe sopra si è fatto ricorso a tre distinti metodi dell'oggetto ADODB.Connection: Open, Execute e Close. In buona sostanza già questi soli tre metodi, il cui utilizzo però non è immediato, permettono l'accesso ad una fonte di dati ODBC. Ad essi si aggiungono poi altri elementi di interfaccia meno sfruttati, ma comunque buoni da conoscere. Per questo è presentata anzitutto una cernita dell'interfaccia base messa a disposizione dall'oggetto Connection. Si tenga presente che anche in questo caso non sono riportati né tutti i metodi né tutte le proprietà dell'oggetto, ma si è scelto scartando i meno utili nell'apprendimento iniziale dell'argomento. Qualunque approfondimento può essere svolto documentandosi su http://www.microsoft.com/data/ado/ Metodi Cancel Annulla l'esecuzione di una chiamata dei metodi Execute e Open in attesa Close Chiude un oggetto aperto e i relativi oggetti dipendenti Execute Esegue la particolare istruzione SQL passata al metodo mediante un parametro stringa Open Apre una connessione a una fonte dati Proprietà ConnectionString Contiene le informazioni utilizzate per stabilire una connessione a una fonte dati ConnectionTimeout Indica in secondi il tempo di attesa da considerare durante il tentativo di stabilire una connessione prima di terminare il tentativo e generare un errore CursorLocation Imposta o restituisce la posizione del motore del cursore Version Indica il numero di versione di ADO Il particolare metodo Open si occupa dell'aprire una connessione verso la fonte di dati specificata in una stringa, detta stringa di connessione, passata al metodo sotto forma di parametro. Le stringhe di connessione sono basate su una particolare sintassi. Ogni stringa di connessione è costituita da una serie di coppie chiave-valore, separate tra di loro con un punto e virgola. Le chiavi sono invece separate dai rispettivi valori mediante il simbolo di uguale. Nel più tipico dei casi è necessario inserire in tale stringa almeno due coppie, atte ad indicare il formato di database utilizzato e la sua locazione fisica nel file system del server. Ad esempio: var strConn = ""; strConn += "driver={Microsoft Access Driver (*.mdb)};"; strConn += "dbq=C:\\Inetpub\\wwwroot\\dati\\database.mdb;"; Di solito il valore associato alla chiave dbq è ricavato mediante l'utilizzo del metodo Server.MapPath, in modo da ottenere automaticamente la conversione di un percorso relativo in un percorso assoluto. Così è stato fatto nell'esempio precedente. All'interno della stringa di connessione possono poi essere specificati ulteriori dati, ad esempio un'opzionale password associata al database (senza la quale non sarebbe possibile l'accesso ai dati in esso contenuto), alla seguente maniera: strConn += "psw=vgh564;"; Una volta stabilita la stringa di connessione è dunque possibile avviare l'accesso alla fonte di dati specificata: ADOConn.Open(strConn); Da questo momento in poi sarà possibile dunque operare sul database specificato attraverso i metodi di Connection e degli oggetti ad esso collegati. Una volta concluse tutte le operazioni preventivate è buona norma chiudere la connessione, utilizzando il metodo Close: ADOConn.Close(); 9.3 - Il metodo Execute e le istruzioni SQL Tipicamente il fulcro del lavoro viene svolto dal metodo Execute, a cui è data la possibilità di eseguire ricerce, effettuare modifiche o cancellare dei record presenti in una tabella. Tale metodo accetta un parametro di tipo stringa, contenente un'istruzione SQL da eseguire. In buona sostanza all'interno di questa stringa viene detto esplicitamente al metodo Execute cosa fare. SQL (Structured Query Language, si dovrebbe pronunciare sequel, ma quasi tutti in Italia dicono esseccuelle) è un linguaggio che rappresenta lo standard per la definizione, l'interrogazione e la modifica dei database. Per sfruttare la connettività ai database fornita da ADO non occorre essere dei guru di SQL, tuttavia è necessario apprendere la sintassi e l'uso di base di questo linguaggio. Nonostante un piccolo sforzo iniziale ci si accorgerà presto di quanto la struttura di SQL sia facile da apprendere e persino vicina al linguaggio naturale, e per questo semplice o addirittura piacevole da sfruttare. Si torni all'ora all'esempio già presentato: var sql = "SELECT * FROM nominativi"; var ris = ADOConn.Execute(sql); Si incotra qui per la prima volta nel corso di questa guida un'istruzione SQL. Le parole chiave utilizzate sono state enfatizzate mediante il grassetto. Letteralmente "SELECT * FROM nominativi" può essere tradotto in "SELEZIONA ogni campo DALLA TABELLA nominativi". Il metodo Execute, eseguendo questo comando, restituisce un riferimento ad un oggetto di tipo Recordset. Come ancora una volta il nome lascia intuire, un oggetto di tipo Recordset rappresenta una collezione di record, estratti dal datadase dal metodo Execute rispettando ciò che è stato domandato dall'istruzione SQL. In questo caso, infatti, l'oggetto restituito colleziona tutti i campi di tutti i record presenti nella tabella nominativi del database. Un diverso comando SQL avrebbe ritornato un diverso Recordset, ad esempio eseguendo una selezione basata su qualche particolare regola o ispezione dei campi di ogni singolo record. Una volta estratti i dati di proprio interesse dal database, tramite l'utilizzo della sintassi di SQL e dell'oggetto Connection, è quindi possibile gestirli attraverso l'interfaccia pubblica dell'oggetto Recordset. Andando avanti nel corso di questa guida saranno approfonditi gli aspetti relativi sia ad SQL che all'utilizzo dei Recordset. Per il momento si è voluto esclusivamente avvicinare il lettore all'ideologia che permea l'accesso alle fonti di dati ODBC. Le applicazioni pratiche e l'approfondimento delle tematiche saranno dunque presentati passo passo da qui in poi. 9.4 - Approccio agli oggetti Recordset Il Recordset ricavato dal codice dell'esempio contiene dunque tutti i record che costituiscono la tabella nominativi del database utilizzato. E' ora possibile muoversi all'interno dei dati acquisiti semplicemente sfruttando i metodi e le proprietà degli oggetti Recordset, qui presentati in maniera non ufficiale. In particolar modo, per ottenere una stampa a video di tutti i dati ricevuti dalla fonte, è stato messo in atto un ciclo while basato sulla proprietà EOF e sul metodo MoveNext di Recordset. I Recordset possono essere immaginati come vere e proprie tabelle di dati, dove ogni riga corrisponde ad un record, un po' come nella rappresentazione visuale di Access. Tale ideologica tabella può essere letta mediante l'utilizzo di un cursore. Appena l'oggetto Recordset è stato creato il cursore è posizionato all'inizio della prima riga di tale tabella. In questa situazione i singoli campi del record evidenziato dal cursore sono leggibili tramite l'utilizzo della sintassi ris("nome_campo") ed infatti si osservi questa porzione estratta dall'esempio: Response.Write("Record n° " + ris("id") + "<br>"); Response.Write("Nome: " + ris("nome") + "<br>"); Response.Write("Cognome: " + ris("cognome") + "<br>"); Response.Write("E mail: " + ris("e_mail") + "<br><br>"); Il contenuto dei quattro campi disponibili nella tabella nominativi viene inviato all'output utilizzando proprio la sintassi di accesso ai campi appena mostrata. Il cursore può inoltre essere spostato in avanti lungo i vari record posseduti mediante il metodo MoveNext(). Una chiamata a tale metodo, insomma, causa l'avanzamento alla riga successiva dell'ideale tabella dei dati ricavati dal database. Naturalmente il numero di record che costituisce un Recordset è per forza di cose finito, e non sarà possibile spostarsi in avanti su di esso per più di tot volte. Se si tenta un avanzamento del cursore quando questo ha già scavalcato l'ultimo record della collezione, infatti, viene lanciato un errore e l'esecuzione dell'intero script viene interrotta. E' qui che torna dunque utile il valore booleano della proprietà EOF dell'oggetto Recordset. EOF è true solo quando il cursore è posizionato al termine dell'ultima riga disponibile, false altrimenti. Inglobando dunque gli strumenti acquisiti in un ciclo while diventa possibile il controllo e la scansione del Recordset ricavato, indipendentemente dalla lunghezza di esso. Il tipo di interfaccia utilizzato da Recordset è piuttosto diffusa in diversi ambienti, Java su tutti, ed è semplice da assimilare proprio per una somiglianza con la naturale maniera di pensare dell'uomo. La parte conclusiva di questo script, infatti, può essere così tradotta in linguaggio naturale: fino a quando non raggiungi la fine della tabella dei dati { stampa i dati presenti nella riga attuale; avanza di una riga; } L'interfaccia di Recordset, comunque, non termina qui e quella esaminata non è che una delle tante pratiche nelle quali è possibile sfruttare un Recordset. Altre situzioni simili o meno a questa verranno presentate nel corso dei capitoli successivi. Capitolo 10 - Interagire con i database 10.0 - SQL per eseguire ricerche Nel corso del capitolo precedente sono state approcciate le tecniche di accesso ad una fonte di dati ODBC tramite il componente denominato ADO. Si è perciò notato quale importante ruolo giochi la sintassi SQL anche nelle più basilari operazioni di ricerca. Come già è stato ribadito la conoscenza di SQL esula dagli scopi di questa guida, tuttavia ci si occupa in questo capitolo dell'analisi delle più elementari e necessarie tipologie di ricerche che è possibile effettuare su un database. Tutto questo al fine di rendere produttivo lo studio compiuto sinora relativamente all'accesso alle fonti ODBC. Si riprenda allora l'esempio più recente riportato dalla guida rispetto ad SQL (cfr. Capitolo 9): SELECT * FROM nominativi Si tratta con tutta probabilità dell'esempio di più semplice formulazione possibile. Traducendo in maniera letterale in italiano l'istruzione suonerebbe come: SELEZIONA tutti i campi DALLA TABELLA nominativi La prima cosa che si nota, perciò, è come siano determinanti in una ricerca le due istruzioni SELECT e FROM, ambedue seguite da un parametro. La prima indica quali campi dei record estratti debbano essere restituiti. Il particolare simbolo * sta ad indicare la restituzione di tutti i campi appartenenti ai singoli record. La clausula FROM, invece, è necessaria per determinare da dove estrarre tali record, e per questo viene fatta seguire dal nome della tabella che si intende considerare. Come già è stato visto questo tipo di ricerca restituisce un oggetto Recordset rappresentante tutti i record, comprensivi di tutti i campi, appartenenti alla tabella nominativi. Di certo non si tratta di una ricerca troppo utile. Lo scopo di una ricerca, infatti, è spesso quello di utilizzare un certo criterio per restringere il campo dei risultati. Supponiamo allora di voler estrarre dalla tabella nominativi un solo record, ossia quello con id pari a 3. Ecco come fare: SELECT * FROM nominativi WHERE id = 3 In linguaggio naturale, oramai dovrebbe essere facile capirlo, l'istruzione si formula come: SELEZIONA tutti i campi DALLA TABELLA nominativi DOVE il campo id è pari a 3 E se non dovesse essere presente un record che soddisfi tale condizione? Semplicemente il valore della proprietà EOF dell'oggetto Recordset ricavato sarà true da subito. Da questo punto di vista la sintassi per eseguire ricerche risulta piuttosto di facile intuizione. Se si fosse voluto ricercare quei record con id maggiore a 3 sarebbe bastato scrivere: SELECT * FROM nominativi WHERE id > 3 Si tralascia la trasposizione in linguaggio naturale. E' anche possibile utilizzare degli operatori per specificare delle condizioni più complesse. Ad esempio ecco come si ottengono i record con id compreso tra 3 e 15, estremi esclusi: SELECT * FROM nominativi WHERE id > 3 AND id < 15 L'uso di tali operatori è ancora una volta intuitivo, e del tutto analogo alle espressioni booleane di JScript. Gli operatori principalmente usati per combinare più espressioni sono ovviamente AND e OR. Fino ad ora ci si è limitati a specificare condizioni di ricerca sul campo id, ossia un contatore, un campo numerico. Le ricerche più interessanti avvengono però sui campi contenenti del testo, come nome o cognome nell'esempio del capitolo precedente. Come delimitatore di stringhe SQL usa, in maniera analoga al Pascal, il singolo apice '. Se si volessero allora estrarre dalla tabella nominativi quei record corrispondenti a tutti le persone aventi nome Carlo la giusta istruzione sarebbe: SELECT * FROM nominativi WHERE nome = 'Carlo' Anche qui naturalmente si può ricorrere agli operatori AND ed OR per la costruzione di query più complesse. Le ricerche su campi di testo, comunque, presentano problematiche molto più estese rispetto alle più banali ricerche su campi numerici. Una questione piuttosto importante, ad esempio, riguarda le ricerche case-insensitive, ossia quelle ricerce eseguite su campi di testo senza considerare le differenze che corrono tra lettere minuscole e lettere maiuscole. Secondo l'esempio precedente, infatti, è possibile ottenere tutti i record con campo nome pari esattamente a 'Carlo'. Se nella tabella dovesse presentarsi un record con campo nome pari a 'carlo' questo non verrebbe restituito. Con l'operatore =, infatti, è possibile eseguire ricerche solo ed esclusivamente case-sensitive. SQL fornisce perciò un altro operatore di confronto utile per lo scopo: l'operatore LIKE. Si consideri la seguente riga: SELECT * FROM nominativi WHERE nome LIKE 'carlo' Questo comando restituirebbe tutti i record con campo nome pari a 'Carlo', 'carlo', e anche volendo 'cArlo' o 'CARLO' e così via. I "prodigi" di SQL, comunque, non si fermano qui. Grazie ad una serie di caratteri speciali, infatti, è anche possibile esprimere in maniera semplice delle ricerche su campi testuali molto più complesse. Potrebbe infatti essere necessario fare delle ricerche su campi che devono contenere una certa parola, e non per forza equivalere ad essa. Si supponga che nella tabella nominativi sia presente anche una persona chiamata Giancarlo, e si supponga di voler ottenere con una semplice ricerca anche questo record. SELECT * FROM nominativi WHERE nome LIKE '%carlo' Il particolare carattere %, quindi, rappresenta una qualsiasi combinazione di caratteri. Nel caso specifico, quindi, vengono restituiti tutti quei record di cui il campo nome termini con la sequenza di lettere 'carlo', e quindi anche 'Giancarlo'. Il simbolo % può essere utilizzato perciò in svariati modi. Si potrebbe pensare di farsi restituire tutti quei record con il campo nome che cominci per 'carlo'. SELECT * FROM nominativi WHERE nome LIKE 'carlo%' Se dovesse esistere un record con campo nome pari a 'Carlo Ambrogio' verrebbe in questo caso incluso tra i risultati restituiti dalla query. Una ricerca molto interessante è la seguente: SELECT * FROM nominativi WHERE nome LIKE '%carlo%' Tradotta in linguaggio naturale verrebbe: SELEZIONA tutti i campi DALLA TABELLA nominativi DOVE il campo nome CONTIENE la parola 'carlo' Con questa ricerca verrebbero perciò restituiti tutti i Carlo, ma anche tutti gli eventuali composti del nome, tra cui Giancarlo e Carlo Ambrogio degli esempi precedenti. SQL permette inoltre di mettere in ordine i risultati restituiti in base ad un particolare criterio, grazie alla clausula SORT BY. Si immagini di voler ordinare la ricerca appena esaminata secondo l'ordine alfabetico dei cognomi. La cosa è semplice: SELECT * FROM nominativi WHERE nome LIKE '%carlo%' ORDER BY cognome In questo caso l'oggetto Recordset restituito può essere sfogliato in maniera ordinata. Nel caso fossero presenti solo due record in grado di soddisfare la ricerca, ad esempio corrispondenti a Carlo Aquilini e a Carlo Rossi, si otterrebbe che il primo record restituito sarà certamente quello di Carlo Aquilini, indipendentemente dall'ordine di inserimento nel database. SQL permette anche l'ordinamento in senso inverso: SELECT * FROM nominativi WHERE nome LIKE '%carlo%' ORDER BY cognome DESC SORT BY può essere utilizzato rispetto ad ogni tipo di campo. Nel caso di un ordinamento svolto in base ad un campo testuale questo verrà eseguito sul normale ordine alfabetico, nel caso di campi numerici avverrà in base alla normale successione dei numeri. Molto interessante e sfruttato anche l'ordinamento svolto in base ai campi data/ora. 10.1 - Aggiornamento dei record Formulare delle query per eseguire delle ricerche e delle estrazioni ragionate da una fonte di dati non è l'unica cosa resa possibile da SQL. Sempre mediante il metodo Execute dell'oggetto Connection di ADO, e sempre attraverso un comando SQL passato a tale metodo sotto forme di normale stringa di JScript, è anche possibile aggiornare il contenuto di una tabella e dei suoi record. Anche in questo caso si partirà da un esempio concreto. Supponiamo di aver indicizzato all'interno della tabella nominativi il seguente record: id = non ci interessa saperlo nome = Carlo cognome = Rossi e_mail = [email protected] Come ci si dovrebbe comportare un domani se Carlo Rossi ci comunicasse di aver cambiato indirizzo e mail? La risposta è semplice, ed è contenuta tutta nel seguente comando. Nel caso la nuova casella e mail da registrare fosse [email protected] basterebbe formulare quanto segue: UPDATE nominativi SET e_mail = '[email protected]' WHERE nome = 'Carlo' AND cognome = 'Rossi' In linguaggio naturale: AGGIORNA la tabella nominativi PONENDO e_mail uguale a '[email protected]' IN QUEI RECORD DOVE il nome è 'Carlo' E il cognome è 'Rossi' Si fa qui la conoscenza di due nuove istruzioni SQL: UPDATE e SET, che vengono usate in maniera concomitante. La prima specifica quale tabella verrà coinvolta dall'aggiornamento specificato dalla seconda. La clausula WHERE rimane del tutto identica all'utilizzo che se ne è fatto precedentemente. Questa volta però i record inquadrati dalla ricerca verranno aggiornati secondo quanto specificato da SET, anziché essere semplicemente restituiti. E possibile aggiornare in un colpo solo anche più di un campo, utilizzando la virgola come separatore. Nel caso si fosse inserito nel database di esempio anche un campo contenente l'url della home page degli utenti registrati sarebbe ora possibile aggiornare anche questo dato: UPDATE nominativi SET e_mail = '[email protected]', home_page = 'http://www.rossi.it' WHERE nome = 'Carlo' AND cognome = 'Rossi' 10.2 - Inserimento di un nuovo record Un po' più laboriosa risulta invece la creazione di un nuovo record da aggiungere ad una tabella esistente. Invece di ricorrere alla sola sintassi SQL e alla sua esecuzione tramite Connection.Execute conviene sfruttare l'interfaccia messa a disposizione dall'oggetto Recordset, sempre appartenente alla famiglia di ADO. Si supponga di dover realizzare una pagina che consenta ad un utente di inserire il proprio nominativo all'interno del database in questione. Per prima cosa è meglio disporre una semplice pagina HTML necessaria per l'invio dei dati, tramite un form: <html> <head><title>Inserisci il tuo nome!</title></head> <body> <form action="inserisci.asp" method="post"> Nome:<br> <input type="text" name="nome"><br> Cognome:<br> <input type="text" name="cognome"><br> E mail:<br> <input type="text" name="e_mail"><br><br> <input type="submit" value="Inserisci"> </form> </body> </html> Come è possibile vedere dal codice i dati inseriti vengono passati alla pagina inserisci.asp, il cui codice è il seguente: <%@ LANGUAGE = JScript %> <% // Ricevo i dati dal form var nome = Request.Form("nome"); var cognome = Request.Form("cognome"); var e_mail = Request.Form("e_mail"); // Istanzio l'oggetto Connection var ADOConn = new ActiveXObject("ADODB.Connection"); // Creo la STRINGA DI CONNESSIONE var strConn = ""; strConn += "driver={Microsoft Access Driver (*.mdb)};"; strConn += "dbq=" + Server.MapPath("prova.mdb"); // Apro la connessione al database selezionato ADOConn.Open(strConn); // Istanzio Recordset var RecSet = new ActiveXObject("ADODB.Recordset"); // Collego Recordset alla connessione aperta RecSet.Open("nominativi",ADOConn,3,3); // Formulo il nuovo record RecSet.AddNew(); RecSet("nome") = nome; RecSet("cognome") = cognome; RecSet("e_mail") = e_mail; // Aggiorno la tabella RecSet.Update(); // Chiudo Recordset RecSet.Close(); // Chiudo la connessione ADOConn.Close(); %> <html> <head><title>Inserimento effettuato</title></head> <body> Inserimento effettuato! </body> </html> Si focalizzi ora la propria attenzione sulle seguenti righe estratte dal codice: RecSet = new ActiveXObject("ADODB.Recordset"); RecSet.Open("nominativi",ADOConn,3,3); RecSet.AddNew(); RecSet("nome") = nome; RecSet("cognome") = cognome; RecSet("e_mail") = e_mail; RecSet.Update(); RecSet.Close(); E' tramite questa serie di operazioni che l'inserimento di un nuovo record nella tabella nominativi è reso possibile. Una volta istanziato l'oggetto Recordset viene avviata una connessione alla tabella nominativi della fonte di dati giù puntata da ADOConn. Il metodo AddNew() inserisce un nuovo record, inizialmente vuoto, all'interno del Recordset. E' sufficiente quindi riempire uno ad uno i singoli campi del record, ad esclusione del contatore chiave primaria id, che viene incrementato automaticamente. Una volta riempiti i singoli campi è sufficiente lanciare il metodo Update() per far sì che l'aggiunta si rifletta sulla fonte di dati specificata. A questo punto la connessione può concludersi, in quanto l'operazione desiderata è stata portata a termine. 10.3 - Conclusioni Come già ribadito più volte non si può credere che la comprensione di questi paragrafi esaurisca la conoscenza di SQL. L'argomento è anzi così vasto da fornire sufficienti spunti per la realizzazione di intere guide dedicate, alcune delle quali tranquillamente reperibili in rete. Quello che si è visto tra queste righe è solo la base necessaria ed indispensabile per poter comprendere gli esempi futuri e per poter maneggiare con un minimo di indipendenza le proprie applicazioni basate sull'accesso ODBC. I prossimi capitoli chiariranno in tal senso quanto descritto sinora, attraverso tutorial e pratici esempi. Capitolo 11 - Un motore di ricerca con ASP (Parte 1) 11.0 - Introduzione al tutorial Con questo capitolo, e con il successivo, si intende guidare il lettore alla realizzazione di un motore di ricerca, mettendo a frutto le nozioni acquisite nel corso delle lezioni precedenti, ed in particolare dei capitoli 9 e 10 relativi all'utilizzo delle fonti di dati di Access tramite ADO e SQL. Per seguire e realizzare quanto suggerito da questo tutorial è necessario disporre, oltre della solita postazione windows munita di un server come IIS o PWS, di una copia di Access, preferibilmente la versione 2000, anche se le versioni come la 97 o la 98 sono ugualmente valide. Quello che si intende realizzare è un servizio di ricerca costruito sulla consultazione di una base di dati (ossia un database) all'interno della quale sono indicizzate una serie di pagine Web consultabili tramite un comune browser. Non si intende certo analizzare le tecniche di realizzazione dei più complessi motori di ricerca, come quelli sfruttati quotidianamente in rete dagli utenti di internet, che indicizzato milioni e milioni di pagine. Si intende invece riprodurre quel servizio che va comunemente sotto il nome di "motore di ricerca interno", utilizzato soprattutto da siti dagli ampi contenuti per aiutare il visitatore nel localizzare velocemente mediante l'utilizzo di alcune parole chiave la risorsa cercata. Si tratta perciò di un servizio efficiente finché le pagine memorizzate nel database non superino diverse migliaia, numero più che sufficiente anche per un sito di ampie dimensioni! 11.1 - Concettualizzazione del problema Come al solito è necessario partire chiarendo le proprie idee sul da farsi, concettualizzando il problema proposto e predisponendo una schema per la successiva soluzione del medesimo. Come già annunciato l'intero servizio fa perno sul fatto di aver precedentemente indicizzato una serie di pagine Web. Ma come realizzare tale opera? L'utilizzo di un database risulta in casi come questo estremamente utile, sia per l'efficienza del servizio stesso, sia per rendere più facile il compito dello sviluppatore. Stabilito quindi che l'archiviazione dei dati avverrà tramite un database resta da decidere una volta per tutte in che maniera questo dovrà avvenire. Quello che interessa nel problema è la possibilità di ricercare nell'intero archivio una serie di pagine attinenti con i concetti espressi dall'utente mediante l'utilizzo di alcune parole chiave. Sarà quindi necessario dotare la tabella creata allo scopo all'interno del database almeno dei seguenti quattro campi: Titolo della pagina Web URL della pagina Web Descrizione dei contenuti della pagina Web Una serie di parole chiave associate alla pagina Web Successivamente è necessario realizzare uno script, integrato ovviamente in una o più pagine ASP, in grado di accedere alla fonte di dati al fine di estrarne delle informazioni che soddisfino la richiesta dell'utente. Come visto nei due capitoli precedenti questo diventa molto semplice grazie all'utilizzo di ADO (ActiveX Data Object), oggetto ActiveX utilizzabile per accedere con semplicità a database di varia natura. Per coerenza con lo standard di ASP il database utilizzato per l'esempio sarà generato con Access 2000. 11.2 - Costruzione del database Una volta chiarito quanto detto sinora è possibile procedere alla realizzazione del servizio, partendo ovviamente dall'allestimento del database necessario. Si apra la propria copia di Access e dal menù iniziale si selezioni la voce "Database di Access vuoto". Nella finestra seguente verrà chiesto dove si intende posizionare il file e che nome dargli. Si scelga la propria cartella di lavoro interna al Web Server utilizzato, e si nomini il database come archivio.mdb. Fatto ciò il lettore disporrà di un database completamente vuoto, con la possibilità di interagire con esso tramite gli strumenti visuali messi a disposizione da Access. A questo software va dato il merito dell'estrema semplicità d'uso. Bastano pochi tentativi per acquisire la pratica necessaria alla creazione di database anche piuttosto complessi. Per prima cosa bisogna procedere ora inserendo nel database una nuova tabella. Le tabelle sono elementi cardine nei database come quelli prodotti da Access, ed è al loro interno che tutte le informazioni vengono archiviate. Si selezioni allora l'opzione "Crea una tabella in visualizzazione struttura", uno dei tool più efficaci di questo software. Da qui è ora possibile gestire i campi che faranno parte della tabella, specificandone il nome ed il tipo. Come primo campo si inserisca il campo id, di tipo contatore. Questo tipo di dato permette di associare in maniera automatica un numero intero positivo ad ogni record della tabella. Successivamente selezionare il campo id come chiave primaria. In Access è semplice selezionare un campo come chiave primaria: basta evidenziare in visualizzazione struttura il campo stesso e quindi utilizzare l'icona raffigurante una chiave presente nella barra degli strumenti del programma. A questo punto è possibile dotare la tabella dei quattro campi sopra discussi, seguendo questo schema: campo titolo, di tipo testo. Tra i valori del riquadro sottostante regolare la voce "Dimensione campo" su 100. Questo farà sì che i titoli delle pagine Web che indicizzeremo in seguito possano avere lunghezza compresa tra 1 e 100 caratteri. campo url, di tipo testo. Si regoli anche qui su 100 la dimensione del campo. In questa maniera si è previdenti nei confronti di URL molto lunghi ed articolati. campo descrizione, di tipo memo. Il tipo memo consente l'archiviazione di dati dalla lunghezza non determinata a priori. In questo caso, perciò, le descrizioni che associeremo alle singole pagine Web potranno avere lunghezza variabile. campo keywords, di tipo memo. Vale lo stesso discorso di sopra. Grazie al tipo memo sarà possibile inserire quante parole chiave si ritiene necesario. Adesso è necessario salvare quanto fatto. Access chiederà di assegnare un nome alla tabella. Per questo esempio si scelga il nome pagineweb. Il lavoro di costruzione del database è completo. E' anche possibile chiudere la finestra di visualizzazione struttura. Si inseriscano ora, allo scopo di effettuare dei test in seguito, una serie di pagine Web dimostrative. Per farlo basta un doppio click sul nome della tabella nella finestra principale del database. Ad esempio: titolo: Sauron Software - Risorse per il webmaster url: http://www.sauronsoftware.it descrizione: Una serie di raccolte utili a tutti i tipi di webmaster keywords: risorse webmaster HTML ASP JavaScript icone fonts gif animate mailing list Come è possibile osservare il campo keywords è rappresentato da una serie di parole separate tra di loro da uno spazio. Questa sono le famose parole chiave associate alla pagina Web. Ciò significa che se un utente digiterà in fase di ricerca la parola "HTML" vedrà certamente tra i risultati la pagina "Sauron Software - Risorse per il webmaster", mentre non vedrà altre eventuali pagine archiviate che non contengano nel campo keywords questa parola chiave. Seguendo il modello proposto si inseriscano perciò una serie di pagine Web per testare poi l'esempio. Dopo aver salvato nuovamente il lavoro è anche possibile chiudere Access. Si passa ora alla realizzazione della pagina di consultazione della fonte di dati realizzata. 11.3 - Eseguire la ricerca Dopo aver predisposto il database che si intende utilizzare è necessario implementare uno script in grado di fare da tramite tra l'utente e i dati immagazzinati nell'archivio. Poiché tale script per funzionare correttamente necessita di una serie di dati in input da parte dell'utente bisognerà senz'altro predisporre un form HTML. Questo è piuttosto semplice da realizzare. Le interrogazioni al database, quindi, partiranno dal seguente documento Web, che per praticità può essere battezzato cerca.htm: <html> <head> <title>Motore di ricerca</title> </head> <body> <form action="risultati.asp" method="POST"> Parole chiave:<br> <input type="text" name="keywords"> <input type="submit" value="CERCA"> </form> </body> </html> Le parole inserite dall'utente vengono perciò inviate, con metodo POST, al documento risultati.asp. Come è facile intuire sarà questa pagina a dover inglobare tanto lo script in grado di eseguire la ricerca, quanto quello in grado di produrre il giusto output corrispondente. Ci si dedicherà perciò ora alla realizzazione di questo importante file. Come prima cosa è necessario recuperare le parole inviate tramite il form di cerca.htm: var testo = String(Request.Form("keywords")); if (testo=="" || testo=="undefined") Response.Redirect("cerca.htm"); Nel caso l'utente avesse inviato il contenuto del form senza inserire alcuna parola chiave allora il processo di ricerca nemmeno sarà avviato, e l'utente sarà rimandato direttamente a cerca.htm, in maniera a lui invisibile. Tutte le parole chiave inserite saranno naturalmente suddivise tra di loro mediante uno o più spazi. E' allora necessario separare tra di loro le singole parole inviate, facendo ricorso al metodo split dell'oggetto String di JScript: var keywords = testo.split(" "); keywords sarà dunque un'Array dove ogni elemento rappresenterà una parola distinta. Fatto ciò è possibile aprire la connessione verso la fonte di dati che si intende consultare: var stringaDiConnessione = "driver={Microsoft Access Driver (*.mdb)};dbq="; stringaDiConnessione += Server.MapPath("archivio.mdb"); var conn = new ActiveXObject("ADODB.Connection"); conn.Open(stringaDiConnessione); Il passo successivo consiste nel formulare correttamente una query, ossia una richiesta, basata sulla sintassi di SQL, da sottoporre alla fonte di dati verso la quale la connessione è stata aperta. Si faccia particolare attenzione alla seguente porzione di codice: var sql = "SELECT * FROM pagineweb WHERE"; for (i=0;i<keywords.length;i++) { if (i>0) sql += " AND"; sql += "keywords LIKE '%" + keywords[i] + "%'"; } Se ad esempio l'utente avesse digitato la coppia di parole "gif animate" la query risultante sarebbe: SELECT * FROM pagineweb WHERE keywords LIKE '%gif%' AND keywords LIKE '%animate%' Formulata correttamente la query sarà possibile chiedere tramite ADO la restituzione dei record competenti: var ris = conn.Execute(sql); La variabile ris, quindi, conterrà un riferimento ad un oggetto Recordset rappresentante tutti i record ospitati dalla tabella pagineweb in grado di soddisfare la richiesta fatta. Non resta che scorrere tale Recordset inserendo nell'output una rappresentazione HTML dei suoi contenuti: if (ris.EOF) Response.Write("<b>NESSUN DOCUMENTO TROVATO</b>"); else while (!ris.EOF) { Response.Write("<p><a href='" + ris("url") + "'>"); Response.Write(ris("titolo")); Response.Write("</a><br>"); Response.Write(ris("descrizione")); Response.Write("</p>"); ris.MoveNext(); } La connessione al database può quindi essere chiusa, la pagina ASP è completa. conn.Close(); Il codice che ne risulta è il seguente: <%@ LANGUAGE = JScript %> <% var testo = String(Request.Form("keywords")); if (testo=="" || testo=="undefined") Response.Redirect("cerca.htm"); var keywords = testo.split(" "); var stringaDiConnessione = "driver={Microsoft Access Driver (*.mdb)};dbq="; stringaDiConnessione += Server.MapPath("archivio.mdb"); var conn = new ActiveXObject("ADODB.Connection"); conn.Open(stringaDiConnessione); var sql = "SELECT * FROM pagineweb WHERE"; for (i=0;i<keywords.length;i++) { if (i>0) sql += " AND"; sql += " keywords LIKE '%" + keywords[i] + "%'"; } var ris = conn.Execute(sql); if (ris.EOF) Response.Write("<b>NESSUN DOCUMENTO TROVATO</b>"); else while (!ris.EOF) { Response.Write("<p><a href='" + ris("url") + "'>"); Response.Write(ris("titolo")); Response.Write("</a><br>"); Response.Write(ris("descrizione")); Response.Write("</p>"); ris.MoveNext(); } conn.Close(); %> Questo breve e conciso script è in grado di eseguire la basilare ricerca sul database che si voleva soddisfare. Al solito si tratta di un puro esempio, che lascia spazio ad ampi margini di miglioramento. Si propongono quindi al lettore delle possibili migliorie, da eseguire come esercizio: possibilità da parte dell'utente di stabilire diverse modalità di ricerca, come "tutte le parole", "almeno una parola" e "frase esatta". L'esempio appena visto soddisferebbe il caso "tutte le parole". Basta modificare la parte di codice che assembla la query, in base alla scelta fatta dall'utente. Nel caso "almeno una parola" basterebbe infatti sostituire nella query l'operatore OR all'operatore AND. Nel caso "frase esatta", invece, non occorre far ricorso ad alcun operatore, basta specificare nella quary keywords LIKE '%parola1 parola2 parola3%'. Neanche lo split sarebbe più necessario. aumentare la robustezza dello script. Qualora l'utente inserisse infatti un carattere di apostrofo nel campo di ricerca lo script restituirebbe un errore. Questo perchè il carattere apostrofo ha particolare significato nella sintassi SQL. Bisogna quindi accertarsi di sostituire ogni carattere di apostrofo ' con un doppio apostrofo ''. E' questo lo stratagemma usato da SQL per inserire nei campi stringa delle query il carattere riservato. fondere le pagine cerca.htm e ricerca.asp in un unico documento, per maggiore praticità. il metodo split, per quanto di facile utilizzo, non ha la potenza, ad esempio, di uno StringTokenizer di Java, o degli equivalenti metodi di Perl. Se l'utente dovesse inserire consecutivamente due spazi, infatti, il metodo split inserirebbe un elemento totalmente vuoto nell'Array risultante e la ricerca sarebbe in questo caso falsata. E' però possibile lavorare intorno a split per ottenere un Array più accurato e privo di questo inconveniente. Il prossimo capitolo concluderà il tutorial, proponendo la realizzazione di un pannello di controllo per l'inserimento via interfaccia Web di nuove pagine all'interno dell'archivio. Capitolo 12 - Un motore di ricerca con ASP (Parte 2) 12.0 - Sui pannelli di controllo... Dopo aver realizzato un basilare servizio di archiviazione, indicizzazione e ricerca di pagine Web (cfr. Capitolo 11) si conclude in questa seconda parte il tutorial relativo all'approfondimento delle tecniche di utilizzo del componente ADO (ActiveX Data Object). Nel corso del capitolo precedente, infatti, sono state prese in esame le tematiche relative alla possibilità di eseguire ricerce tra le informazioni archiviate all'interno di una comune fonte di dati di Access, e lo studio era legato soprattutto all'utilizzo del particolare oggetto di ADO denominato Connection, in grado appunto di stabilire connessioni con un database e di estrarre da esso, seguendo un criterio dettato mediante SQL, una serie di dati ritenuti interessanti per l'applicazione. In questo capitolo lo studio si incentrerà maggiormente sull'oggetto Recordset, sempre di ADO, utile per effettuare inserimenti di nuove informazioni all'interno della stessa fonte di dati utilizzata per l'esempio precedente. In parole più spicciole quello che sarà realizzato nei paragrafi successivi altro non sarà che un pannello di controllo, funzionante su interfaccia Web, utile all'ipotetico amministratore del motore di ricerca per aggiornare i record presenti nell'archivio precedentemente creato. Si tratta di una funzionalità molto importante per i servizi di questo genere, in quanto permette di aggiornare i dati originariamente specificati in maniera remota, ossia senza dover disporre di una copia del database sulla propria macchina di lavoro, interagendo perciò direttamente con quanto disponibile in rete. Senza tale pannello di controllo l'aggiunta di una nuova pagina all'interno del database utilizzato risulterebbe un'operazione piuttosto laboriosa: bisognerebbe infatti inserire il nuovo record agendo direttamente con Access su una copia locale di archivio.mdb, per poi sovrascrivere la copia in rete tramite, ad esempio, un accesso FTP. Si tratta di un metodo altamente sconveniente, in quanto per aggiungere pochi caratteri è necessario modificare, trasportare e sovrascrivere un archivio che potenzialmente può avere dimensioni rilevanti, causando anche dei momentanei malfunzionamenti del servizio nel momento in cui si sovrascrive la copia in rete. Un pannello di controllo via Web, invece, rende notevolmente più semplice questa serie di operazioni. L'inserimento di un nuovo record, in questo caso, può essere eseguito semplicemente collegandosi ad una pagina Web, tramite un normale browser, specificando quindi mediante un form HTML le specifiche del nuovo record e quindi aggiornando in tempo reale direttamente la copia disponibile in rete, senza causare disagi, senza dover utilizzare altri software aggiuntivi, e senza che siano richieste particolari conoscenze tecniche. Il più delle volte, infatti, capita che chi gestisca un servizio non sia la stessa persona che lo ha realizzato. Si immagini ad esempio che il motore di ricerca oggetto di questo studio stia venendo realizzando per un ipotetico committente, che di programmazione Web, di ASP, di ADO e di tutte le tematiche collegate è completamente digiuno. Tramite un pannello di controllo il cliente potrà aggiornare il database realizzato dallo sviluppatore senza doversi rivolgere allo sviluppatore stesso. Inoltre un pannello di controllo Web permette l'aggiornamento dei dati da una qualsiasi postazione che abbia accesso ad Internet e disponga di un comune Web browser, mentre l'aggiornamento in locale richiede almeno l'utilizzo di una copia di Access e di un client FTP, strumenti solitamente non in mano all'utente medio. Per concludere: quando si sviluppa un'applicazione Web basata sull'accesso ad una fonte di dati che necessiti di continui aggiornamenti la realizzazione di un pannello di controllo via Web è quasi un passo obbligato. 12.1 - Il form HTML Premesso quanto detto nel paragrafo precedente ci si accinge ora allo sviluppo di un basilare pannello di controllo in grado di aggiornare archivio.mdb aggiungendo in esso un nuovo record alla volta, specificato dal gestore del servizio attraverso una semplice pagina Web. Si torni quindi a lavorare nella stessa cartella utilizzata per testare i codici presentati nel Capitolo 11, in quanto in questo momento si sta completando l'esempio precedente, e non si sta realizzando perciò un codice di per sé indipendente. Come già visto più volte un utente è in grado di passare dei dati ad una pagina Web sfruttando il costrutto FORM messo a disposizione da HTML. Come prima cosa, quindi, sarà necessario realizzare un semplice modulo in grado di consentire all'amministratore del servizio di specificare i dati che insieme costituiscono il record che si intende accodare a quelli già presenti nell'archivio. Si realizzi quindi la pagina pannello.htm come segue: <html> <head> <title>Pannello di controllo</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body bgcolor="#FFFFFF"> <div align="center"> <p><b><font size="2" face="Verdana, Arial, Helvetica, sans-serif">BENVENUTO NEL PANNELLO DI CONTROLLO!</font></b></p> <p><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Grazie a questo pannello è possibile inserire nuovi documenti all'interno del motore di ricerca. Utilizza il modulo sottostante.</font></p> <p> </p> <form name="" method="post" action="inserisci_record.asp"> <table width="350" border="0" cellspacing="1" cellpadding="0"> <tr> <td align="left"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b>Password:</b></font></td> <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type="password" name="password" size="30"> </font></td> </tr> <tr> <td> </td> <td> </td> </tr> <tr> <td align="left"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b>Titolo:</b></font></td> <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type="text" name="titolo" size="30"> </font></td> </tr> <tr> <td align="left"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b>URL:</b></font></td> <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type="text" name="url" size="30" value="http://"> </font></td> </tr> <tr> <td align="left"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b>Descrizione:</b></font></td> <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type="text" name="descrizione" size="30"> </font></td> </tr> <tr> <td align="left"><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b>Keywords*:</b></font></td> <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type="text" name="keywords" size="30"> </font></td> </tr> <tr> <td> </td> <td> </td> </tr> <tr align="center"> <td colspan="2"> <font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <input type="submit" value="AGGIUNGI IL RECORD"> </font></td> </tr> </table> </form> <p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b>(*)</b> le parole chiave devono essere separate tra di loro tramite uno spazio</font></p> </div> </body> </html> Analizzando il codice sopra riportato è possibile estrarre le seguenti informazioni relativamente al form proposto. action="inserisci_record.asp" - questo significa che i dati inseriti verranno passati al documento inserisci_record.asp, del quale ci si occuperà nel prossimo paragrafo method="post" - il metodo utilizzato per l'invio dei dati è POST. All'interno di inserisci_record.asp, quindi, potranno essere recuperati utilizzando la sintassi Request.Form("nomeCampo") campo password - naturalmente il pannello di controllo non deve essere sfruttato da tutti, ma solo dalle persone autorizzate. Per questo è prevista l'autentificazione mediante passoword. campo titolo - qui andrà specificato il titolo della pagina Web da indicizzare campo url - qui andrà specificato l'url della pagina Web da indicizzare campo descrizione - qui andrà specificata una breve descrizione per la pagina Web da indicizzare campo keywords - qui andrà specificata la lista delle parole chiave da associare alla pagina Web che si deve indicizzare Si osservi inoltre come gli ultimi quattro campi descritti (titolo, url, descrizione e keywords) corrispondano esattamente ai quattro campi scelti per rappresentare una pagina Web all'interno del database. Come è facile intendere il contenuto di questi campi verrà dunque prelevato ed inserito così come è all'interno dell'archivio. 12.2 - Inserimento del nuovo record I compiti di inserisci_record.asp, a questo punto, possono essere riassunti nei seguenti passi: recupero dei dati presentati nel form validazione della password inserita validazione dei quattro campi che costituiscono il nuovo record da inserire nel caso i precedenti controlli abbiano dato buon esito, inserimento del nuovo record all'interno dell'archivio visualizzazione di un responso dell'operazione nell'output Tenendo a mente quanto detto nel capitoli precedenti relativamente all'utilizzo di ADO se ne ricava il seguente codice: <%@ LANGUAGE = JScript %> <% // SPECIFICA QUI LA PASSWORD var password_di_accesso = "pannello"; // RECUPERO DEL CONTENUTO DEL FORM var password = String(Request.Form("password")); var titolo = String(Request.Form("titolo")); var url = String(Request.Form("url")); var descrizione = String(Request.Form("descrizione")); var keywords = String(Request.Form("keywords")); // VARIABILI DI UTILITA' var errore = 0; var fatto = false; // CONTROLLO PASSWORD if (password!=password_di_accesso) errore = 1; else { // CONTROLLO CAMPI if (titolo.length<1 || titolo.length>100) errore = 2; if (url.length<1 || url.length>100) errore = 2; if (descrizione.length<1) errore = 2; if (keywords.length<1) errore = 2; if (errore==0) { // INSERIMENTO DEL NUOVO RECORD stringaDiConnessione = "driver={Microsoft Access Driver (*.mdb)};dbq="; stringaDiConnessione += Server.MapPath("archivio.mdb"); conn = new ActiveXObject("ADODB.Connection"); conn.Open(stringaDiConnessione); rs = new ActiveXObject("ADODB.Recordset"); rs.Open("pagineweb",conn,3,3); rs.AddNew(); rs("titolo") = titolo; rs("url") = url; rs("descrizione") = descrizione; rs("keywords") = keywords; rs.Update(); rs.Close(); conn.Close(); fatto = true; } } %> <html> <head> <title>Inserimento di un nuovo record</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body bgcolor="#FFFFFF"> <div align="center"> <% if (errore==1) { %> <p><font face="Verdana, Arial, Helvetica, sansserif" size="2" color="#660000"><b>ERRORE #1</b>: PASSWORD NON CORRETTA</font></p> <% } %> <% if (errore==2) { %> <p><font face="Verdana, Arial, Helvetica, sansserif" size="2" color="#660000"><b>ERRORE #2</b>: CAMPI NON VALIDI</font></p> <p><font face="Verdana, Arial, Helvetica, sans-serif" size="2">Si ricordano le lunghezze massime previste:</font></p> <p><font face="Verdana, Arial, Helvetica, sans-serif" size="2"> <b>TITOLO</b>: compreso tra 1 e 100 caratteri<br> <b>URL</b>: compreso tra 1 e 100 caratteri<br> <b>DESCRIZIONE</b>: almeno un carattere<br> <b>KEYWORDS</b>: almeno un carattere </font></p> <% } %> <% if (errore!=0) { %> <p><font face="Verdana, Arial, Helvetica, sansserif" size="2">< <a href="javascript:history.back()">TORNA INDIETRO</a> ></font></p> <% } %> <% if (fatto) { %> <p><font face="Verdana, Arial, Helvetica, sansserif" size="2" color="#006600"><b>OPERAZIONE ESEGUITA CON SUCCESSO!</b></font></p> <p><font face="Verdana, Arial, Helvetica, sansserif" size="2">< <a href="pannello.htm">INSERISCI ALTRO RECORD</a> ></font></p> <% } %> </div> </body> </html> Tralasciando i commenti relativi alla forma scelta per realizzare il documento e alle tecniche sfruttate per eseguire i controlli si passi all'analisi dei due punti chiave di inserisci_record.asp. Il primo è relativo all'autentificazione della password. Si è scelta una via molto semplice: la password corretta è stata memorizzata direttamente all'interno di una variabile JScript (password_di_accesso) facente parte del codice stesso. Si tratta di una soluzione poco pratica nei termini del servizio (se l'amministratore volesse modificare la propria password dovrebbe mettere mano al codice, con risultati spesso catastrofici!), ma comunque conveniente per l'esempio in fase di studio, il cui obiettivo non è quello di descrivere come si realizzino zone ad accesso limitato. Un tema del genere sarà argomento di studio nei capitoli 15 e 16. La seconda parte interessante è quella relativa all'accesso al database e all'inserimento del nuovo record: stringaDiConnessione = "driver={Microsoft Access Driver (*.mdb)};dbq="; stringaDiConnessione += Server.MapPath("archivio.mdb"); conn = new ActiveXObject("ADODB.Connection"); conn.Open(stringaDiConnessione); rs = new ActiveXObject("ADODB.Recordset"); rs.Open("pagineweb",conn,3,3); rs.AddNew(); rs("titolo") = titolo; rs("url") = url; rs("descrizione") = descrizione; rs("keywords") = keywords; rs.Update(); rs.Close(); conn.Close(); La parte evidenziata in verde è identica a quella già descritta nel Capitolo 11, il cui scopo è quello di utilizzare l'oggetto ADODB.Connection per connettersi ad archivio.mdb. La parte in arancione, invece, evidenzia l'inserimento del nuovo record all'interno della tabella pagineweb, mediante l'utilizzo dell'oggetto ADODB.Recordset. 12.3 - Conclusioni e spunti Si conclude qui questo tutorial dedicato all'approfondimento delle tecniche di accesso alle fonti di dati supportate da ADO. E' stato anche fornito un approccio alla realizzazione dei pannelli di controllo per interfaccia Web, strumenti da sempre apprezzati dai clienti delle applicazioni. Si invita il lettore ad approfondire l'argomento completando il pannello proposto con l'aggiunta di altri servizi, quali ad esempio la possibilità di cancellare record precedentemente inseriti (comando DELETE di SQL) e quella di modificare i contenuti di un record già esistente (comando UPDATE di SQL). Capitolo 13 - L'oggetto built-in Session 13.0 - Scopo e utilizzo dell'oggetto Session Session, il quarto oggetto built-in sul percorso di questa guida, consente di gestire le informazioni relative alla sessione di un utente. Si supponga di utilizzare una normale pagina ASP contenente la seguente riga di codice JScript: var stringa = "Ciao, come stai?"; La variabile stringa appena dichiarata ha un ciclo di vita che termina nell'esatto momento in cui l'interprete raggiunge la fine del codice scritto. In poche parole questo significa che, tendando di richiamare la variabile citata all'interno di un secondo documento, si otterrà un errore, in quanto risulterà non definita. Questo perchè ciascuno script non è in grado di vedere le variabili utilizzate da altri script, e quindi nemmeno di condividerle con essi. Molto spesso, però, alcuni particolari tipi di applicazione Web richiedono che alcuni dati possano essere trasportati da una pagina ad un'altra. Si immagini, ad esempio, il classico "carrello della spesa" utilizzato in tutti i negozi virtuali. L'utente che stabilisce di acquistare un prodotto lo inserisce in maniera figurata all'interno del proprio carrello e quindi abbandona la pagina utilizzata allo scopo, magari per andare alla ricerca di altri prodotti. Quando il suo giro sarà completo, l'utente accederà al documento utile per ordinare quanto scelto precedentemente. Come fare a questo punto per sapere cosa sia stato inserito pochi minuti prima all'interno del carrello? Proprio a questo tipo di problema risponde l'oggetto Session, che si fa carico di trasportare dei dati, unici e diversi per ogni utente, da una pagina ASP ad un'altra. Si faccia una prova creando il documento session1.asp come segue: <%@ LANGUAGE = JScript %> <% Session("stringa") = "Ciao, come stai?"; %> <html> <head> <title>Prova variabili Session, pag. 1</title> </head> <body> <a href="session2.asp">Pagina successiva</a> </body> </html> Si carichi il documento con il proprio browser, sfruttando il Web Server locale, e quindi si acceda a session2.asp, il cui codice deve essere: <%@ LANGUAGE = JScript %> <html> <head> <title>Prova variabili Session, pag. 2</title> </head> <body> <%=Session("stringa")%> </body> </html> Il messaggio "Ciao, come stai?" verrà stampato nell'output inviato al browser. Se ne deduce che la variabile Session("stringa") è ancora viva e vegeta, e il suo contenuto non è stato distrutto al termine dell'esecuzione della pagina precedente. E' anche possibile evincere quale tipo di sintassi debba essere utilizzata per sfruttare in maniera basilare questo tipo di oggetto. E' possibile accedere, in lettura o in scrittura, ad una particolare variabile Session semplicente usando il riferimento: Session("nomeVariabile") L'oggetto built-in Session, inoltre, dispone anche della seguente serie di proprietà, metodi e collezioni: Proprietà CodePage Un intero che specifica quale codifica sia applicata al testo (ad esempio il codice corrispondente alla codifica ANSI è 1252). LCID Sta per "locale identifier", si tratta di una sigla che indica quale identificatore locale debba essere usato per visualizzare le pagine. Le sigle nazionali sono le stesse adoperate negli standard internazionali. SessionID Proprietà di sola lettura, restituisce l'id assegnato alla sessione dal Web Server. Timeout Indica, in minuti, quanto tempo di inattività debba trascorrere prima che il Server distrugga automaticamente l'oggetto Session. Metodi Abandon Distrugge l'oggetto Session su cui viene richiamato, e libera le risorse occupate. Collezioni Contents Collezione di tutte le variabili Session attive per un dato utente. StaticObjects Collezione di tutti gli oggetti che hanno ricevuto lo scope "session". 13.1 - Funzionamento interno di un oggetto Session Prima di proseguire oltre è necessario comprendere quale meccanismo si celi dietro al funzionamento di Session. Quando un oggetto di questo tipo viene creato, il server si fa carico di associargli automaticamente un codice identificativo (solitamente costituito da una lunga serie di numeri e caratteri), unico e distinto per ogni istanza ottenuta. Tale ID, oltre ad essere conservato nella memoria del server, viene contemporaneamente inviato anche al browser del visitatore che, se abilitato alla ricezione dei cookie, è in grado di ricordarlo per tutta la durata della visita al sito. Ad ogni nuova richiesta HTTP inviata dallo stesso utente allo stesso host, il Web Server può riconoscere il visitatore proprio attraverso tale ID posizionato precedentemente nel suo browser. Quando in una particolare pagina ASP viene creata una variabile Session, quindi, questa viene conservata dal server in coppia con il codice identificativo assegnato al visitatore. Nel corso di richieste successive è perciò possibile riaccostare al determinato utente le variabili Session definite precedentemente. La proprietà Session.SessionID permette proprio di venire a conoscenza del codice identificativo associato ad un utente. Il lettore può divertirsi ad utilizzarla per scoprire in che maniera sono strutturati gli ID del proprio Web Server. 13.2 - La proprietà Session.Timeout Le variabili Session, benchè sopravvivano alla morte delle singole pagine, non sono comunque eterne, ed hanno anche loro durata temporanea. Solitamente un oggetto Session viene distrutto (e quindi il suo contenuto perduto) dopo 20 minuti dall'ultima richiesta HTTP effettuata dal visitatore al server. Tale misura di tempo può essere modificata in diversi modi. Prima di tutto è in grado di farlo l'amministratore del server attraverso il proprio pannello di controllo. Tale modifica riguarderà tutti gli oggetti Session creati da quel momento in poi, ma è anche possibile variare la durata di una singola istanza dell'oggetto direttamente tramite codice ASP, senza essere perciò amministratori del server. La proprietà utile per far ciò è Session.Timeout, che rappresenta in minuti il tempo di inattività massimo consentito prima che una variabile Session muoia. Ponendo il timeout pari a 10, ad esempio, accadrà che l'oggetto Session associato verrà distrutto trascorsi 10 minuti dal momento in cui l'utente interessato dalla modifica abbia richiesto al server per l'ultima volta una pagina appartenente al sito. Se si lavora con pagine dai contenuti lunghi, che solitamente vengono lette in maniera approfondita, è quindi d'uopo prolungare la durata della sessione. Viceversa, se il proprio sito contiene pochi elementi per pagina e se si ha la certezza che il visitatore passi velocemente da un documento ad un altro, allora si può pensare di diminuire il timeout in modo tale da rilasciare le risorse del server in maniera più veloce e pratica. 13.3 - Il metodo Session.Abandon In molti casi può essere utile distruggere un oggetto Session anzitempo rispetto a quanto previsto dal valore di timeout impostato. Per questo è stato fornito il metodo Session.Abandon, che termina all'istante una sessione. Questa funzione trova particolare utilità quando ad esempio bisogna compiere delle operazione di "logout". Si pensi ad un'area ad accesso riservata, che utilizza una variabile Session per tenere traccia del fatto che l'utente abbia eseguito o meno l'operazione di login. Nel momento in cui l'utente decide di abbandonare l'area attraverso l'apposita opzione, è necessario distruggere all'istante le variabili Session utilizzate. In tale maniera si è certi che per riaccedere all'area sarà necessario eseguire nuovamente il login. In questa maniera, inoltre, si mantiene anche un occhio di riguardo per il server, che viene liberato all'istante dal peso di informazioni che si conoscono per certo come non più utili. Per testare il metodo si crei la pagina session3.asp: <%@ LANGUAGE = JScript %> <% Session.Abandon(); %> <html> <head> <title>Prova variabili Session, pag. 3</title> </head> <body> <a href="session2.asp">Pagina precedente</a> </body> </html> A questo punto si visitino i documenti creati nel corso di questo capitolo nel seguente ordine: session1.asp, session2.asp, session3.asp, session2.asp. Il risultato sarà che la prima volta che session2.asp viene visitata verrà mostrato il messaggio "Ciao, come stai?", mentre quando si ripasserà per la seconda volta su di essa si scorgerà la scritta "undefined". Questo perché session3.asp, tramite il metodo Abandon, ha chiuso la sessione e demolito ogni suo contenuto. Per tornare a rivedere il messaggio "Ciao, come stai?" è pertanto necessario transitare nuovamente su session1.asp. Attenzione: per questo esperimento non debbono essere usati i tasti "indietro" ed "avanti" del browser, in quanto in questo caso i documenti non vengono richiesti nuovamente al server, ma vengono pescati dalla cache. Per provare in maniera corretta il tutto bisogna aggiornare effettivamente ogni documento richiesto. Capitolo 14 - Costruire un'area ad accesso riservato 14.0 - Presentazione del tutorial Le conoscenze acquisite in materia di ActiveX, accesso ai database e oggetti built-in verranno ora messe a frutto realizzando una semplice, quanto funzionale, area ad accesso riservato. Con questo termine si è soliti indicare una sezione di un sito Web la cui visualizzazione, per diversi motivi, non è pubblica, ma riservata ad un'utenza più ristretta. Ogni utente che vanti il permesso di poter accedere a sezioni di questo tipo dispone solitamente di una coppia di valori username-password, che può utilizzare per farsi riconoscere dal sistema e per farsi garantire l'accesso alla porzione protetta del Web. I motivi che possono spingere alla realizzazione di un servizio del genere sono molteplici, distinti da situazione in situazione. Almeno due sono comunque i casi più tipici. Il primo di questi è rappresentato da alcune aziende che necessitino di mettere a disposizione dei propri dipendenti delle informazioni riservate, comodamente accessibili via Web, anche quando si è a diversi chilometri di distanza dalla sede. Il secondo caso, invece, riguarda quei siti che richiedono una certa garanzia dalla propria utenza per rendere loro un servizio migliore, o comunque per fornire un'applicazione la cui funzionalità sia basata proprio sul fatto di saper distinguere un utente da un altro. La guida farà riferimento a questo secondo caso, realizzando una pagina di iscrizione pubblica, grazie alla quale chiunque potrà ottenere la propria coppia di valori username-password per accedere all'area protetta. Nulla toglie, comunque, che l'esempio resti valido anche nel primo caso. Basterebbe infatti eliminare la pagina di iscrizione pubblica e affidare la gestione e lo smistamento dell'utenza ad un amministratore appositamente designato. In ogni caso il tutorial, come al solito, non vuole rendere al lettore un servizio bello e pronto, quanto piuttosto guidarlo alla scoperta dei meccanismi che devono essere appresi per affrontare un problema del genere, in maniera tale che ognuno, in base alle differenti esigenze, possa poi essere in grado di realizzare un'applicazione analoga, pensata però in maniera specifica per un particolare caso. 14.1 - Creazione del database Dovendo fornire a numerosi utenti delle distinte coppie username-password, è facile comprendere come sussista l'esigenza di memorizzare ciascuna di queste coppie all'interno del Web server, in maniera riservata, affinché il programma di validazione dati sia in grado di analizzare i dati immessi dall'utente e determinare in base ad essi se dover concedere o meno l'accesso all'area protetta. La prima mossa necessaria consiste quindi nello stabilire in quale maniera tali valori debbano essere memorizzati. Per l'esempio qui presentato si adotterà un database di Access, protetto tramite password. Creare il database necessario per l'esercitazione risulta abbasta semplice: 1. Si apra la propria copia di Access. 2. Si selezioni dal menù iniziale la voce "Database di Access vuoto". 3. Si nomini il nuovo database come utenza.mdb e lo si vada a posizionare all'interno della cartella che si intende utilizzare per l'esperimento. 4. Dalla finestra di gestione del database si selezioni la voce "Crea una tabella in visualizzazione Struttura". 5. Si definiscano ora i 3 seguenti campi: id, di tipo "Contatore"; username, di tipo "Testo", password, di tipo "Testo" (opzionalmente nella maschera di input è possibile specificare la dicitura "Password", per ottenere una visualizzazione in asterischi del campo quando il database viene aperto in Access). 6. Si selezioni la riga corrispondende al campo id e si utilizzi il bottone raffigurante una chiave per definire questo campo come chiave primaria. 7. Si salvi la tabella nominandola utenti. Il database risulta già in questa forma adatto per l'utilizzo all'interno dell'esempio. Tuttavia è bene proteggere con una password ciascun database che possa contenere informazioni riservate, come nel caso in analisi. Tenendo aperto il database all'interno di Access, è possibile compiere tale operazione alla seguente maniera: 1. 2. 3. 4. Dal menù si selezioni la voce "Strumenti > Protezione > Imposta password database...". Si digiti la password desiderata, ad esempio "prova". Si digiti la stessa password nel campo "Verifica". Si confermi con il tasto "Ok" l'intera operazione. Da questo momento in poi il database utenza.mdb risulterà protetto da password. Chiudendo Access e tentando di riaprire il file, infatti, se tutto è andato a buon fine, si dovrebbe ricevere la richiesta di inserimento della password. 14.2 - La pagina di iscrizione A questo punto è necessario realizzare anzitutto uno script in grado di consentire agli utenti di registrarsi, ottenendo una coppia di valori username-password utilizzabili per accedere successivamente all'area protetta. Servirà anzitutto una comune pagina HTML (iscrizione.htm) contenente il modulo da compilare per richiedere l'adesione al servizio: <html> <head> <title>Iscrizione al servizio</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body bgcolor="#FFFFFF" text="#000000"> <div align="center"> <p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="2">ISCRIZIONE AL SERVIZIO</font></b></p> <form method="post" action="iscrivimi.asp"> <p> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">USERNAME:</font><br> <input type="text" name="username" maxlength="20" size="25"> </p> <p> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">PASSWORD:</font><br> <input type="password" name="password1" size="25" maxlength="20"> </p> <p> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">CONFERMA PASSWORD:</font><br> <input type="password" name="password2" size="25" maxlength="20"> </p> <p><input type="submit" value="ISCRIVIMI"></p> </form> </div> </body> </html> A ricevere e processare i dati ci penserà quindi iscrivimi.asp, come mostrato di seguito: <%@ LANGUAGE = JScript %> <% function visualizzaErrore(messaggioDiErrore) { // Mostra il messaggio di errore e interrompe il programma Response.Write('<html>\n'); Response.Write('<head>\n'); Response.Write('<title>Iscrizione al servizio</title>\n'); Response.Write('<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">\n'); Response.Write('</head>\n'); Response.Write('<body bgcolor="#FFFFFF" text="#000000">\n'); Response.Write('<div align="center">\n'); Response.Write('<p> </p>\n'); Response.Write('<p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="2" color="#660000">'); Response.Write('ERRORE: ' + messaggioDiErrore); Response.Write('</font></b></p>\n'); Response.Write('<p><font face="Verdana, Arial, Helvetica, sansserif" size="2">[<a href="#" onClick="history.back()">TORNA INDIETRO</a>]</font></p>\n'); Response.Write('</div>\n'); Response.Write('</body>\n'); Response.Write('</html>\n'); Response.End; } // PASSO 1: si riceve e si valida il modulo username = String(Request.Form("username")); password1 = String(Request.Form("password1")); password2 = String(Request.Form("password2")); if (username=="" || username=="undefined") visualizzaErrore("SPECIFICARE LO USERNAME"); if (username.charAt(0)==" ") visualizzaErrore("HAI INSERITO UNO SPAZIO DI TROPPO PRIMA DELLO USERNAME"); if (username.charAt(username.length1)==" ") visualizzaErrore("HAI INSERITO UNO SPAZIO DI TROPPO DOPO LO USERNAME"); if (username.length<3 || username.length>20) visualizzaErrore("LO USERNAME DEVE ESSERE COMPRESO TRA 3 E 20 CARAT TERI"); if (password1=="" || password1=="undefined") visualizzaErrore("SPECIFICARE LA PASSWORD"); if (password1!=password2) visualizzaErrore("LE DUE PASSWORD NON COINCIDONO"); if (password1.length<3 || password1.length>20) visualizzaErrore("LA PASSWORD DEVE ESSERE COMPRESA TRA 3 E 20 CARA TTERI"); // PASSO 2: viene aperta la connessione verso la fonte di dati ADOConn = new ActiveXObject("ADODB.Connection"); strConn = ""; strConn += "driver={Microsoft Access Driver (*.mdb)};"; strConn += "dbq=" + Server.MapPath("utenza.mdb") + ";"; strConn += "pwd=prova"; // SELEZIONARE QUI LA PASSWORD DI ACCESSO AL DATABASE SCELTA! ADOConn.Open(strConn); // PASSO 3: VIENE VERIFICATO CHE LO USERNAME NON SIA GIA' IN USO tempUsername = username.replace(/'/g,"''"); sql = "SELECT * FROM utenti WHERE username LIKE '" + tempUsername + "'"; ris = ADOConn.Execute(sql); if (!ris.EOF) { ris.Close(); ADOConn.Close(); visualizzaErrore("LO USERNAME SPECIFICATO GIA' RISULTA IN USO. MODIFICARE LA PROPRIA SCELTA."); } ris.Close(); // PASSO 4: IL NUOVO UTENTE VIENE REGISTRATO RecSet = new ActiveXObject("ADODB.Recordset"); RecSet.Open("utenti",ADOConn,3,3); RecSet.AddNew(); RecSet("username") = username; RecSet("password") = password1; RecSet.Update(); RecSet.Close(); ADOConn.Close(); %> <html> <head> <title>Iscrizione al servizio riuscita!</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body bgcolor="#FFFFFF" text="#000000"> <div align="center"> <p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="2">COMPLIMENTI, ORA SEI ISCRITTO!</font></b></p> </div> </body> </html> I passaggi utilizzati dal codice non sono di difficile comprensione. E' stata realizzata la funzione visualizzaErrore, utile per mandare in output ogni eventuale messaggio di errore e per interrompere l'esecuzione dello script. Il modulo viene ricevuto e validato in base a dei criteri piuttosto generici, quindi la connessione al database viene stabilita. Per prima cosa si verifica che lo username specificato non sia già stato registrato da un altro utente, mediante una ricerca case-insensitive (operatore LIKE). Una volta superato tale controllo un nuovo record, costituito dai dati appena ricevuti, viene inserito nella tabella utilizzata. Se tali operazioni non dovessero risultare di immediata comprensione si torni allora a rivedere i Capitoli dal 9 al 12, interamente dedicati all'utilizzo dei database all'interno delle pagine ASP. 14.3 - La pagina di "login" Per accedere all'area riservata è ora necessario realizzare una pagina che consenta all'utente di farsi riconoscere. Ancora una volta il processo non risulta complicato. Viene qui presentato il codice di login.asp: <%@ LANGUAGE = JScript %> <% errore = false; username = String(Request.Form("username")); password = String(Request.Form("password")); from = String(Request.QueryString("from")); if (username!="undefined" && username!="") { from = Request.Form("from"); ADOConn = new ActiveXObject("ADODB.Connection"); strConn = ""; strConn += "driver={Microsoft Access Driver (*.mdb)};"; strConn += "dbq=" + Server.MapPath("utenza.mdb") + ";"; strConn += "pwd=prova"; // SELEZIONARE QUI LA PASSWORD DI ACCESSO AL DATABASE SCELTA! ADOConn.Open(strConn); tempUsername = username.replace(/'/g,"''"); tempPassword = password.replace(/'/g,"''"); sql = "SELECT * FROM utenti WHERE username LIKE '" + tempUsername + "' AND password = '" + tempPassword + "'"; ris = ADOConn.Execute(sql); if (ris.EOF) errore = true; else { Session.Timeout = 15; Session("username") = String(ris("username")); ris.Close(); ADOConn.Close(); if (from!="undefined") Response.Redirect(from); else Response.Redirect("main.asp"); } ris.Close(); ADOConn.Close(); } %> <html> <head> <title>Accesso all'area riservata</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body bgcolor="#FFFFFF" text="#000000"> <div align="center"> <p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="2"> ATTENZIONE! STAI PER ACCEDERE AD UN'AREA RISERVATA. DEVI AUTENTIFICARTI.</font></b></p> <% if (errore) { %> <p><b><font face="Verdana, Arial, Helvetica, sansserif" size="2" color="#FF0000">DATI NON TROVATI, ACCESSO NEGATO</font></b></p> <% } %> <form method="post" action="login.asp"> <input type="hidden" name="from" value="<%=from%>"> <p> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">USERNAME:</font><br> <input type="text" name="username" maxlength="20" size="25"> </p> <p> <font face="Verdana, Arial, Helvetica, sans-serif" size="2">PASSWORD:</font><br> <input type="password" name="password" size="25" maxlength="20"> </p> <p><input type="submit" value="ACCEDI"></p> </form> </div> </body> </html> Si osservi il meccanismo utilizzato. Qualora il processo di autentificazione dovesse andare a buon fine, lo script si curerebbe allora di creare la variabile Session("username"), inserendo al suo interno lo username utilizzato dall'utente. Il perché dell'utilizzo dell'oggetto built-in Session verrà illustrato nel corso del paragrafo successivo. Ci si soffermi ora ad osservare invece l'utilizzo della particolare variabile from e del meccanismo di reindirizzamento ad essa collegato. In buona sostanza l'utente può finire su login.asp in due distinti casi: quando vi accede direttamente con il proprio browser e quando tenta di accedere ad un documento riservato senza essersi prima autentificato. Il primo caso è il più semplice. Nessuna querystring viene appesa all'URL della pagina, pertanto la variabile from perde il suo significato. Una volta effettuato il login l'utente verrà reindirizzato alla pagina main.asp, qui non trattata, che dovrebbe ipoteticamente rappresentare l'home page dell'intera area ad accesso limitato. Quando l'utente tenterà invece di accedere direttamente ad uno dei documenti protetti dal metodo che tra poco sarà descritto, senza essersi però autentificato in precedenza, allora verrà rimandato a login.asp, con l'aggiunta dell'indirizzo della pagina di provenienza appeso in querystring. In questa maniera, una volta autentificato, l'utente verrà rispedito direttamente alla pagina alla quale aveva tentato originariamente di accedere. Ovviamente essendosi autentificato potrà in questo caso visualizzarne il contenuto senza problemi. Si tratta di un'ottima funzionalità aggiuntiva! 14.4 - I documenti riservati Far appartenere un particolare documento all'area riservata significa, di fatto, configurarlo in maniera adeguata secondo le seguenti indicazioni. 1. Il documento dovrà avere sempre e comunque estensione .asp, in quanto includerà il controllo necessario sotto forma di script. 2. Le prime righe del documento dovranno necessariamente essere: <%@ LANGUAGE = JScript %> <% if (Session("username")==null) { from = escape(String(Request.ServerVariables("SCRIPT_NAME"))); Response.Redirect("login.asp?from=" + from); } %> L'oggetto built-in Session è stato descritto nel Capitolo 13. Nel caso le pagine dell'area non fossero tutte contenute nella stessa cartella di login.asp, allora sarà necessario adattare di volta in volta l'istruzione Response.Redirect, in modo da puntare sempre alla giusta locazione della pagina utile per il proprio riconoscimento. Per testare la funzionalità dello script si realizzi la pagina prova.asp: <%@ LANGUAGE = JScript %> <% if (Session("username")==null) { from = escape(String(Request.ServerVariables("SCRIPT_NAME"))); Response.Redirect("login.asp?from=" + from); } %> <html> <head> <title>Pagina interna all'area riservata</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body bgcolor="#FFFFFF" text="#000000"> <div align="center"> <p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="2">BENVENUTO <%=Session("username")%>!</font></b></p> </div> </body> </html> Si registri quindi uno username passando per iscrizione.htm, quindi si inserisca l'URL di prova.asp senza prima essersi autentificati. 14.5 - La pagina di "logout" Una volta che l'utente si è autentificato gli sono concessi, secondo l'esempio utilizzato, 15 minuti di inattività prima che la sua sessione scada. Tuttavia in diversi casi è necessario che la sessione, su comando dell'utente stesso, possa concludersi senza dover attendere lo scorrere dei 15 minuti dall'ultima operazione eseguita. Pertanto è oppurtuno inserire all'interno di ognuno dei documenti protetti un collegamento verso logout.asp: <%@ LANGUAGE = JScript %> <% Session.Abandon(); %> <html> <head> <title>Uscita dall'area riservata</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body bgcolor="#FFFFFF" text="#000000"> <div align="center"> <p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="2">ARRIVEDERCI!</font></b></p> </div> </body> </html> 14.6 - Conclusioni Si conclude qui questo tutorial dedicato ai metodi di realizzazione, tramite ASP, di un'area ad accesso riservato. Si lascia ora alla fantasia e alle esigenze del lettore il compito di adattare quanto appreso ai più disparati casi pratici. Capitolo 15 - L'oggetto built-in Application 15.0 - Scopo e utilizzo dell'oggetto Application Application, quinto ed ultimo degli oggetti built-in di ASP, permette di gestire informazioni condivise tra tutti gli utenti connessi ad un sito. La comprensione dell'utilizzo delle variabili Application è molto semplice se già si ha manualità con le controparti Session (cfr. Capitolo 13). La sintassi che consente l'attivazione di una variabile di applicazione, infatti, è identica a quella che si utilizza di norma per le variabili di sessione o variabili utente: Application("nomevariabile") = contenuto; Anche il recupero del valore memorizzato conserva la stessa semplicità del caso precedente: <%=Application("nomevariabile")%> I dati che possono essere memorizzati in variabili di questo tipo, inoltre, sono i tipi semplici supportati dai linguaggi di scripting, primi fra tutti quindi i valori numerici e le stringhe. Non possono essere assegnati a delle variabili di applicazione i riferimenti ad oggetti propri del linguaggio di scripting usato (come d'altronde non è possibile farlo per le variabili Session), in quanto tali oggetti vengono distrutti automaticamente al termine dell'esecuzione del codice. Tuttavia possono essere inseriti in Application riferimenti ad oggetti ActiveX di varia natura, tra i quali spicca ad esempio ADO. Non sempre è conveniente, però, ricorrere a questa tecnica. Per il momento ci limiteremo ad osservare l'uso dell'oggetto Application rispetto ai tipi di dati primitivi. Si è osservato quale grande analogia corra tra gli oggetti Session e Application, ma non si è ancora precisato in cosa differiscano. Le variabili di applicazione permettono la condivisione di dati tra gli utenti, pertanto là dove le variabili Session sono distinte e separate per ogni utente connesso, le variabili Application sono uniche per tutti. Questa peculiarità rende le variabili di applicazione utilissime ogni qual volta un'informazione debba essere velocemente condivisa tra tutte le persone collegate ad un sito. E' il caso dei famosi contatori di utenti connessi, oppure quello delle chat per interfaccia Web, dove i messaggi postati dai partecipanti vengono inseriti in variabili di questo tipo per divenire fruibili pubblicamente. In poche parole inserire il valore "ciao" all'interno di una variabile Application è come inserirlo, ad esempio, in un file di testo. Il file di testo, infatti, è unico, e qualsiasi utente vorrà leggerlo in un dato momento vi individuerà lo stesso contenuto. La differenza sostanziale tra le variabili Application e i dati conservati fisicamente sul disco è comunque lampante, e quello fornito vuole essere solo un aiuto alla comprensione. Per prima cosa l'accesso ai contenuti di una variabile di questo tipo è molto più veloce, e non si basa sull'utilizzo di componenti esterni da inizializzare ogni volta. Inoltre i file di testo (o i database gestiti da ADO) conservano i dati perennemente, almeno fino a quando non si comunica esplicitamente la loro eliminazione, mentre le variabili Application sono mantenute in memoria dal Web server, e per questo motivo il loro contenuto va perso ogni volta che un reset viene eseguito. Un'altra differenza essenziale che corre tra Session e Application è che le variabili di quest'ultimo tipo non scadono dopo un determinato tempo di inattività, ed il loro ciclo vitale si conclude solo quando vengono esplicitamente distrutte, o con il reset del server, come già è stato detto. In definitiva, quindi, l'oggetto built-in Application è la maniera migliore per condividere dati semplici ed immediati, la cui conservazione perenne non sia comunque richiesta. 15.1 - Metodi e collezioni dell'oggetto Application L'oggetto built-in Application mette a disposizione dello sviluppatore due metodi e due collezioni, che verranno ora passati velocemente in rassegna. I paragrafi successivi saranno destinati ad approfondimenti ed esempi in merito. Metodi Lock Impedisce ad altri script di modificare il contenuto delle variabili Application. UnLock Consente ad altri script di modificare il contenuto delle variabili Application. Collezioni Contents Collezione di tutte le variabili Application attive sul server. StaticObjects Collezione di tutti gli oggetti che hanno ricevuto lo scope "application". 15.2 - I metodi Lock e UnLock Il fatto che le variabili Application presentino natura globale nel confronto di tutti gli utenti espone le applicazioni Web basate su di esse a potenziali problemi. Essendo ogni valore condiviso è probabile che più di uno script, la cui richiesta di esecuzione è stata lanciata da utenti differenti, tenti di modificare il contenuto di una variabile Application nello stesso istante. Questo è fonte di grandi problemi, in quanto si rischia la perdita e la malformazione dei dati precedentemente accumulati. Per ovviare a tale inconveniente sono stati introdotti i metodi Lock e UnLock, grazie ai quali solo uno script alla volta ha accesso in scrittura sui valori di applicazione. La prassi generale che si deve seguire quando si inseriscono valori condivisi, quindi, ricalca il seguente caso: Application.Lock(); Application("nomevariabile") = contenuto; Application.UnLock(); Nel momento in cui viene lanciato Lock, si impedisce automaticamente ad ogni altro processo di intervenire sul contenuto delle variabili Application. L'unico script che può scrivere tranquillamente dei valori al loro interno resta quello che ha richiamato per primo il metodo. Tutti gli altri processi, quindi, resteranno in attesa e, a turno, interverranno singolarmente in scrittura nelle variabili di loro interesse. Per questo motivo, ogni volta che il codice completi il lavoro richiesto su delle variabili di applicazione, è bene lanciare il metodo UnLock, che permetterà agli altri processi di terminare l'attesa e compiere il loro dovere. 15.3 - Un pratico esempio Verrà ora analizzato un esempio tanto buono dal punto di vista didattico, quanto inutile da quello delle applicazioni concrete. Sarà realizzata una pagina ASP che permetterà ad un utente qualsiasi l'inserimento di un messaggio di testo all'interno di Application. Ogni volta che un nuovo messaggio sarà postato questo rimpiazzerà il precedente. In contemporanea un secondo script mostrerà in output il messaggio correntemente accumulato nella variabile utilizzata. Il documento (pagina1.asp) utile per l'inserimento di un messaggio è il seguente: <%@ LANGUAGE = JScript %> <% var msg = String(Request.Form("msg")); if (msg!="undefined") { Application.Lock(); Application("messaggio") = msg; Application.UnLock(); Response.Redirect("pagina2.asp"); } %> <html> <head> <title>Inserisci il tuo messaggio!</title> </head> <body> <form method="post" action="pagina1.asp"> <input type="text" name="msg" value=""> <input type="submit" value="VAI!"> </form> </body> </html> La visualizzazione sul browser (pagina2.asp) è estremamente semplice: <%@ LANGUAGE = JScript %> <html> <head> <title>Ecco il messaggio!</title> </head> <body> <p>Messaggio corrente: <b><%=Application("messaggio")%></b></p> </body> </html> Ogni volta che un utente qualsiasi si servirà di pagina1.asp per esternare un proprio pensiero, tutto il resto del mondo potrà venirne a conoscenza collegandosi a pagina2.asp! Nel corso del prossimo capitolo sarà preso in esame un uso molto più concreto ed utile delle variabili Application. Capitolo 16 - Il file Global.asa 16.0 - Global.asa Il file Global.asa, nell'ambito di ASP, è un documento opzionale, che può essere utilizzato per determinati scopi nello sviluppo di un'applicazione Web. Si tratta comunque di un documento non convenzionale: il suo contenuto non verrà mai mostrato all'utente, ne il suo scopo sarà produrre un certo output. Al suo interno, semplicemente, possono essere dichiarati degli oggetti o dei gestori di evento che abbiano a che fare con l'ambito globale delle sessioni e delle applicazioni. Global.asa, per divenire attivo, deve essere posto nella root dell'applicazione che si sta realizzando, e ad ogni applicazione può corrispondere un solo file di questo tipo. Nel corso delle prossime righe saranno analizzati gli scopi e la giusta maniera di utilizzo di questa risorsa. 16.1 - Avvio e termine di una sessione All'interno di un file Global.asa possono essere associate delle azioni ai due eventi di inizio e fine di una sessione utente. E' possibile fare ciò definendo le due funzioni Session_OnStart e Session_OnEnd. Si osservi l'esemplificativo codice seguente: <script language="JScript" runat="Server"> function Session_OnStart() { // Inizio di una nuova sessione } function Session_OnEnd() { // Fine di una sessione } </script> Come l'esempio lascia ad intendere, il codice contenuto all'interno di un file Global.asa deve essere racchiuso all'interno dei tag <script>...</script>. Non può essere utilizzata la direttiva <%@ LANGUAGE = JScript %> per dichiarare l'uso di JScript come linguaggio di scripting, in quanto i tag <% e %> in questo caso non hanno valore. In alternativa, però, è possibile specificare l'intenzione di scrivere codice JScript sfruttando l'attributo language del tag <script>. Sono state definite due funzioni: Session_OnStart e Session_OnEnd. Il codice compreso all'interno della prima verrà eseguito ogni volta che una nuova sessione verrà istanziata dal server, in pratica ogni volta che un nuovo utente si collegherà ad una delle pagine del sito nella cui root sia conservato il file Global.asa corrispondente. Viceversa, la funzione Session_OnEnd verrà eseguita al termine di ciascuna sessione, ovvero quando verrà esplicitamente lanciato il metodo Session.Abandon, oppure quando la durata di una sessione scadrà a causa del Timeout impostato (cfr. Capitolo 13). 16.2 - Avvio e termine di un'applicazione Simili alle funzioni appena viste risultano Application_OnStart e Application_OnEnd: <script language="JScript" runat="Server"> function Application_OnStart() { // Inizio di una nuova applicazione } function Application_OnEnd() { // Fine di un'applicazione } </script> In questo caso, però, ad essere gestita è l'intera applicazione, unica e condivisa tra tutti gli utenti collegati. Questo significa che la funzione Application_OnStart verrà eseguita solo all'avvio dell'intera applicazione. Nella filosofia di ASP e dei Web server che supportano questa tecnologia, le applicazioni sono insiemi di script suddivise in cartelle differenti. Si supponga di aver realizzato, ad esempio, un Forum di discussione, contenuto nella cartella \forum del proprio Web server. Ponendo un file Global.asa all'interno di questa cartella, e associando del codice all'evento Application_OnStart, sarà possibile compiere una determinata azione quando l'applicazione prenderà il via. La funzione Application_OnStart, infatti, verrà eseguita la prima volta che un utente qualsiasi richiamerà una delle pagine contenute all'interno della cartella \forum. Application_OnEnd, invece, verrà eseguita immediatamente prima della chiusura dell'applicazione, che corrisponde spesso anche alla chiusura del Web server. Questa funzione, quindi, risulta il veicolo migliore per fare in modo che ogni risorsa usata in ambito globale venga rilasciata (una connessione ad un database perennemente aperta, tanto per citare un esempio, dovrebbe venire qui chiusa). 16.3 - Conteggio degli utenti connessi Un esempio concreto è quanto di più utile possa esserci in questo momento. Verranno ora messe a frutto le conoscenze relative al file Global.asa appena analizzate, e quelle acquisite nel capitolo precedente rispetto l'utilizzo dell'oggetto built-in Application. Si supponga di voler conteggiare quanti utenti siano connessi ad un sito Web in un determinato istante, in modo da visualizzare poi il risultato di tale elaborazione sulla Home Page, o anche all'interno di altre pagine. Tutto questo, in parole spicciole, significa conteggiare quante sessioni (si ricordi sempre che ad ogni utente viene associata una ed una sola sessione) siano contemporaneamente attive in ogni singolo istante. Si dovrà allora conservare un dato in maniera globale, in modo da poterlo poi condividere universalmente tra tutti gli utenti connessi. A permettere ciò, come deducibile, è una variabile Application. Il trucco risulta allora semplice, e può essere illustrato in tre brevi passi: 1. All'avvio dell'applicazione (Application_OnStart) verrà definita una variabile intera Application("q_utenti"), il cui valore iniziale sarà zero. 2. Ogni volta che una nuova sessione risulti al nastro di partenza (Session_OnStart), il valore contenuto in Application("q_utenti") verrà incrementato di una unità. 3. Ogni volta che una sessione precedentemente creata sia giunta al termine del proprio ciclo vitale (Session_OnEnd), il valore contenuto in Application("q_utenti") verrà decrementato di una unità. Tradotto in codice JScript, il ragionamento appena illustrato si trasforma nel seguente enunciato Global.asa: <script language="JScript" runat="Server"> function Application_OnStart() { Application("q_utenti") = 0; } function Session_OnStart() { Application("q_utenti") = Application("q_utenti") + 1; } function Session_OnEnd() { Application("q_utenti") = Application("q_utenti") - 1; } </script> Fatto ciò, per visualizzare il numero degli utenti connessi, è sufficiente aggiungere da qualche parte nella Home Page del sito la seguente riga: In questo momento ci sono <%=Application("q_utenti")%> utenti connessi Naturalmente la Home Page deve avere estensione .asp, altrimenti la parte compresa tra i tag <% e %> non sarà processata dal server. 16.4 - Aggiornare Global.asa Un file Global.asa può essere aggiunto o modificato anche mentre il Web server è attivo. In questo caso, però, una particolare politica viene adottata. Una volta salvati i cambi al file in questione, il server continuerà a soddisfare tutte le richieste HTTP in corso, rifiutando con un messaggio di errore tutte le altre che gli verranno contemporaneamente presentate. Conclusa la propria attività con le richieste in corso, l'applicazione gestita dal file Global.asa verrà resettata (e verrà pertanto eseguito il codice di Application_OnEnd incluso nel vecchio Global.asa, se presente) e tutte le sessioni in corso verranno concluse (con l'esecuzione del Session_OnEnd del vecchio Global.asa, come prima). A questo punto l'applicazione verrà riavviata (con il lancio dell'eventuale nuova versione di Application_OnStart), e le richieste HTTP verranno nuovamente accettate, stabilendo così delle nuove sessioni (con il conseguente avvio del nuovo Session_OnStart). Questo almeno il meccanismo dichiarato nella documentazione ufficiale, anche se durante "prove su strada" i risultati riscontrati con le diverse versioni di IIS e del PWS sono stati differenti. In alcuni casi, infatti, le direttive del Global.asa sono state aggiornate dopo diversi minuti, mentre in altri casi ancora si è reso necessario resettare il Web server per ottenere il risultato desiderato. Capitolo 17 - Inviare e-mail con CDONTS 17.0 - Posta elettronica su macchine Windows NT/2000 Il funzionamento di un servizio di posta elettronica è garantito dalla collaborazione di tre distinti software: un server che si occupa della consegna dei messaggi (protocollo SMTP), un server che riceve i messaggi, li archivia e li trasmette, su richiesta, al titolare della casella (protocollo POP3 o IMAP) e un client che, dal PC dell'utente, è in grado di inviare e ricevere email collegandosi e dialogando con le due parti server del servizio. I Web server non offrono funzionalità di posta elettronica, tuttavia le macchine che ospitano servizi Web, spesso e volentieri, permettono anche l'invio delle e-mail, tramite un apposito server SMTP. Su Windows NT/2000, il server di posta elettronica più diffuso è Microsoft Exchange. Insieme ad esso viene installata anche una libreria ActiveX, chiamata CDONTS (Collaboration Data Object for Windows NT Server), che permette ad applicazioni esterne di collegarsi al server per inviare o ricevere la posta elettronica. Anche una pagina ASP, quindi, può sfruttare il server SMPT presente nel sistema, inviando e-mail generate dinamicamente. 17.1 - CDONTS.NewMail CDONTS comprende diversi oggetti che incapsulano funzionalità legate, in un modo o nell'altro, al mondo della posta elettronica. Una pagina ASP, salvo rare eccezioni, necessiterà al massimo di interagire con un server SMTP per il semplice invio di una e-mail. L'oggetto utile per compiere questa operazione si chiama NewMail. Proprietà Bcc Specifica la lista dei destinatari invisibili in copia carbone. Di sola scrittura. Body Specifica il corpo della missiva. Di sola scrittura. BodyFormat Specifica il formato del corpo della e-mail (0 = HTML, 1 = testo semplice). Di sola scrittura. Cc Specifica la lista dei destinatari in copia carbone. Di sola scrittura. ContentBase Specifica il percorso di base degli URL che saranno contenuti nella missiva. Di sola scrittura. ContentLocation Specifica il percorso di base, assoluto o relativo, degli URL che saranno contenuti nella missiva. Di sola scrittura. From Specifica il mittente. Di sola scrittura. Importance Specifica l'importanza della missiva (0 = bassa, 1 = media, 2 = alta). Di sola scrittura. MailFormat Specifica il formato della mail (0 = MIME, 1 = testo semplice). Di sola scrittura. Subject Specifica l'oggetto della missiva. Di sola scrittura. To Specifica la lista dei destinatari. Di sola scrittura. Version Restituisce la versione di CDONTS. Di sola lettura. Metodi AttachFile Aggiunge un allegato. AttachURL Aggiunge un allegato, associandogli un URL da poter utilizzare nel corpo della mail. Send Invia la mail. SetLocaleIDs Imposta l'identificatore locale per la mail. Collezioni Value Permette di impostare intestazioni aggiuntive. L'oggetto NewMail può essere istanziato alla seguente maniera: var mail = new ActiveXObject("CDONTS.NewMail"); Da questo momento in poi è possibile specificare i valori che costituiscono la missiva, come il destinatario, il mittente, l'oggetto e il corpo: mail.To = "[email protected]"; mail.From = "[email protected]"; mail.Subject = "Oggetto della missiva"; mail.Body = "Corpo della missiva"; Si può quindi procedere con l'invio: mail.Send(); 17.2 - Applicazione "form-mail" Molti siti chiedono ai propri visitatori di esternare pareri e consigli sui servizi offerti. Di norma questi vengono raccolti in un modulo HTML, per essere poi spediti, tramite e-mail, al webmaster del sito. Si realizzerà ora una semplice applicazione di questo tipo, al fine di dimostrare l'utilizzo dell'oggetto NewMail. E' necessario predisporre un modulo HTML per l'invio dei dati: <html> <head> <title>Invia il tuo parere!</title> </head> <body> <form action="formmail.asp" method="post"> <b>Il tuo nome:</b><br> <input type="text" name="nome"><br> <b>Il tuo parere sul sito:</b><br> <textarea name="parere" cols="40" rows="10"></textarea> <br><br> <input type="submit" value="Invia"> <input type="reset" value="Cancella"> </form> </body> </html> I campi del modulo vengono passati al documento formmail.asp: <%@ LANGUAGE = JScript %> <% var nome = new String(Request.Form("nome")); var parere = new String(Request.Form("parere")); var mail = new ActiveXObject("CDONTS.NewMail"); // Immettere un destinatario esistente! mail.To = "[email protected]"; mail.From = "[email protected]"; mail.Subject = "Parere da " + nome; mail.Body = parere; mail.Send(); %> <html> <head> <title>Grazie!</title> </head> <body> Il tuo parere è stato inviato! </body> </html> Si modifichi anzitutto il destinatario della missiva, introducendo il proprio indirizzo e-mail per il test, quindi ci si colleghi al modulo e si verifichi il risultato. 17.3 - Invio di allegati NewMail permette l'invio di allegati, purché i file da allegare siano presenti sull'hard disk del server. Un allegato può essere aggiunto con il metodo AttachFile(): mail.AttachFile(percorso,nome); Il parametro percorso indica, sull'hard disk del server, il percorso completo del file da allegare. Se questo non è noto, può sempre essere ricavato da un percorso relativo con Server.MapPath(). Il parametro nome, invece, indica il nome che il file riceverà sul client del destinatario. Il seguente esempio invia una mail allegando il file immagine.jpg, che deve essere inserito nella stessa cartella dello script: <%@ LANGUAGE = JScript %> <% var mail = new ActiveXObject("CDONTS.NewMail"); mail.MailFormat = 0; // Mail MIME mail.AttachFile(Server.MapPath("immagine.jpg"),"immagine.jpg"); // Immettere un destinatario esistente! mail.To = "[email protected]"; mail.From = "[email protected]"; mail.Subject = "Con allegato..."; mail.Body = "Ti piace l'immagine?"; mail.Send(); %> <html> <head> <title>Inviata!</title> </head> <body> La mail con allegato è stata inviata! </body> </html> Le e-mail con allegato devono essere in formato MIME. 17.4 - Conclusioni CDONTS, oltre all'oggetto NewMail, comprende anche una lunga serie di altri componenti. Questi, di norma, non sono utilizzati in pagine ASP, poiché le funzionalità offerte non trovano posto in una generica applicazione Web. Inviare una mail, invece, è operazione abbastanza semplice e comune. Si è visto velocemente quali sono le principali caratteristiche di NewMail, confermando gli schemi tecnici con alcuni esempi pratici. Capitolo 18 - Gestione dei cookie 18.0 - Cookie Un cookie (biscotto) è un pacchetto di dati inviato al browser dal Web server, che viene conservato sull'hard disk del visitatore, per essere poi restituito al mittente nel corso degli accessi successivi. Nonostante siano stati a lungo al centro di una controversa polemica, che li accusava di minare la privacy degli utenti, i cookie sono utili ed indispensabili per il funzionamento di numerose applicazioni Web. E' anche vero, comunque, che alcuni programmi di affiliazione utilizzano i cookie, con la complicità di diversi siti, per carpire i gusti dell'utente, in modo tale da effettuare campagne pubblicitarie mirate. I browser moderni pongono un freno al problema, riportando finalmente i cookie al loro ruolo originario. Ciascun cookie è formato da un insieme di sei differenti valori: Nome, il nome del cookie. Dati, i dati conservati nel cookie. Expires, la data di scadenza del cookie. Domain, il dominio a cui il cookie si riferisce. Path, il percorso a cui il cookie si riferisce. Secure, può essere true/false, ed indica se il cookie debba essere ritenuto sicuro. 18.1 - Invio di un cookie Il campo Response.Cookies permette di inviare un cookie al browser di un visitatore: Response.Cookies("CookieProva")("DATO1") = "prova1"; Response.Cookies("CookieProva")("DATO2") = "prova2"; Possono essere inoltre specificate le seguenti quattro proprietà: Response.Cookies("NomeCookie").Expires, indica la data di scadenza del cookie. Se omessa, il cookie sarà di tipo temporaneo. Response.Cookies("NomeCookie").Domain,indica il dominio di riferimento. Il dominio deve essere espresso come stringa. Se omessa, il dominio di riferimento sarà lo stesso dell'applicazione. Response.Cookies("NomeCookie").Path, indica il percorso di riferimento. Il percorso deve essere espresso come stringa, a partire dalla root del sito (ad esempio "/cartella/sottocartella"). Se omessa, il percorso di riferimento sarà la radice del sito ("/"). Response.Cookies("NomeCookie").Secure, indica se il cookie è sicuro o meno. La condizione viene espressa con un semplice valore booleano true/false. Se omessa, il cookie sarà non sicuro. In questa forma, la data di scadenza del cookie può essere impostata facendo riferimento ad un oggetto Date di JScript. Per far scadere "CookieProva" nel Natale del 2010 bisogna scrivere: var d = new Date(2010,11,25); Response.Cookies("CookieProva").Expires = d.getVarDate(); Si ricorda che, con JScript, il conteggio dei mesi comincia da zero, che equivale a Gennaio, mentre undici è Dicembre. Un cookie già esistente può essere aggiornato semplicemente effettuando una sovrascrizione, come se lo si stesse creando per la prima volta. 18.2 - Recupero di un cookie La collezione Request.Cookies permette il recupero dei cookie precedentemente inviati al browser, senza doversi curare della loro forma nell'intestazione HTTP ricevuta. Il recupero di un dato è immediato: var variabile = Request.Cookies("NomeCookie")("NomeDato"); Se il dato richiesto non esiste, il valore restituito è null. Ecco un esempio di recupero con verifica condizionale sull'esistenza: var mioCookie = Request.Cookies("CookieProva")("DATO1"); if (mioCookie==null) { Response.Write("CookieProva.DATO1 non esiste!"); } else { Response.Write("CookieProva.DATO1 esiste e vale: " + mioCookie); } 18.3 - Rimozione di un cookie Si può comandare la rimozione di un cookie semplicemente impostando la sua data di scadenza su un momento già trascorso, ad esempio quello di elaborazione della pagina: var d = new Date(); Response.Cookies("NomeCookie").Expires = d.getVarDate();