Da quando ho iniziato a lavorare con ASP.NET e maggiormente oggi, quando ASP.NET AJAX è ormai prepotentemente entrato nelle mie applicazioni, mi sono spesso scontrato con un problema che credo che molti altri abbiano  riscontrato e affrontato nei modi più disparati. Il problema in questione deriva dalla modalità con cui i server controls di ASP.NET generano gli id degli elementi che poi vengono resi nella pagina. Questi identificatori, che tipicamente corrispondono almeno nella parte finale all'id che viene utilizzato lato server, quando arrivano al client sono spesso arricchiti di informazioni allo scopo di renderli realmente univoci all'interno del DOM html. Mi riferisco a quello che noi tutti conosciamo con il nome di ClientID, che spesso e volentieri ci costringe a generare dei piccoli pezzetti di codice javascript per riuscire ad operare su di essi come segue:

 

var myControl = document.getElementById('<%= myControl.ClientID %>');

Personalmente ho sempre trovato questa operazione stucchevole e inutilmente laboriosa e finalmente, con l'avvento di AJAX e della Microsoft AJAX Library mi sono deciso a metter insieme una serie di idee e a trovare una soluzione dignitosa. La soluzione in realtà riprende ed estende un che ho fatto poco tempo fa. Il principio che allora proposi era quello di creare una sorta di client-codebehind in Javascript che permettesse di incapsulare e in qualche modo meglio organizzare tutto il codice che siamo abituati a scrivere per attivare sul client le nostre pagine. E' stato allora che ho realizzato che tale modello con un passo ulteriore poteva diventare una valida soluzione al problema dei ClientID. Creando infatti una classe base per questo codice e iniettando al suo interno i riferimenti Javascript ai controlli, il modello di programmazione client-side diventa pressochè analogo a quello del server liberandoci così dei voli pindarici cui siamo ormai assuefatti.

Per ottenere questo modello, ho pensato di estendere sua signoria lo ScriptManager. Dato che è proprio di codice di Script che stiamo parlando mi è sembrato il punto naturale per dare vita alla mia soluzione. Inoltre, avendo comunque bisogno di un riferimento alla Microsoft AJAX Library l'uso dello ScriptManager diviene pressochè obbligatorio. Ecco quindi che ho creato un ExtendedScriptManager che cela al suo interno le seguenti cose:

  1. Una collection di ClientReference che consente di specificare quali dei controlli server-side è necessario portare sul client. Questo è dovuto innanzitutto alla scelta di indicare esplicitamente tali controlli per evitare l'inutile proliferazione del codice di script oltre alla necessità di gestire diverse modalità di referenziazione atte a gestire diverse tipologie di controlli e diverse esigenze.
  2. Una proprietà PageClass che indica quale classe istanziare, inserita in un apposito file js referenziarto per rappresentare il codebehind della pagina. Questa classe dovrà estendere la classe Javascript Elite.Page inclusa nell'assembly e referenziata automaticamente dal codice dell'ExtendedScriptManager.

Inoltre ho dovuto estendere anche lo ScriptManagerProxy per mantenere questo tipo di modello anche nei casi in cui si faccia uso di questo controllo. Lo script manager esteso in mancanza di una PageClass si comporta in modo del tutto analogo ad uno ScriptManager classico perciò è possibile usarlo in tutta tranquillità in una masterpage e impostare la PageClass solo nello ExtendedScriptManagerProxy ove ve ne sia la necessità.

L'impostazione della PageClass fa sì che lo ExtendedScriptManager produca qualche riga di codice javascript, legata all'onload dell'applicazione, che si occupa di istanziare la classe specificata e di iniettare al suo interno tutte le ClientReference esplicitate. Ecco un breve esempio di pagina che fa uso del mio modello:

 

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Elite.Library.Web.DefaultPage" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Test Page</title> </head> <body> <form id="form1" runat="server"> <elite:ExtendedScriptManager ID="ScriptManager1" runat="server" PageClass="Elite.DefaultPage"> <ClientScriptReferences> <elite:ClientScriptReference ClientInstanceName="_bTest" TargetControlID="bTest" /> </ClientScriptReferences> <Scripts> <asp:ScriptReference Path="/Default.aspx.js" /> </Scripts> </elite:ExtendedScriptManager> <div> <asp:Button runat="server" ID="bTest" /> </div> </form> </body> </html>

In questa brevissimo pagina si fa uso dell'ExtendedScriptManager per creare una referenza ad un pulsante (bTest) e per istanziare la classe Elite.DefaultPage. A scopo dimostrativo ho volutamente lasciato in biano il pulsante omettendo di valorizzare la proprietà Text per settare questo valore sul client con la data corrente. Ecco il codice della classe:

 

Type.registerNamespace('Elite'); Elite.DefaultPage = function() { Elite.DefaultPage.initializeBase(this); } Elite.DefaultPage.prototype = { load : function() { this._tick$handle = setInterval( Function.createDelegate(this, this._tick), 1000); }, unload : function() { if (this._tick$handle) { clearInterval(this._tick$handle); delete this._tick$handle; } }, _tick : function() { this._bTest.value = new Date(); } } Elite.DefaultPage.registerClass('Elite.DefaultPage', Elite.Page);

In questa classe, realizzata con il Type System della Microsoft AJAX Library si fa uso della referenza introdotta dall'ExtendedScriptManager per valorizzare il testo del pulsante con la data e l'ora corrente, aggiornate ogni 1000 millisecondi. Il codice è banale, ma quello che vorrei evidenziare è la presenza dei metodi load e unload che hanno il medesimo significato degli omonimi eventi server-side. Essi sono completati dai consueti initialize e dispose che derivano da Sys.UI.Control, classe base di Elite.Page. Infine è da nostare anche che il campo membro _bTest non è mai valorizzato; esso infatti viene introdotto dal codice generato dul server:

 

<script type="text/javascript"> //<![CDATA[ Elite.registerServerControls = function(pageInstance, args) { pageInstance._bTest = $get('bTest'); }; Sys.Application.add_init( function() { Elite.Page.Current = $create( Elite.DefaultPage, { }, { beforeLoad : Elite.registerServerControls }, null, window); }); Sys.Application.initialize(); //]]> </script>

In particolare la referenza iniettata nella pageInstance è quella che poi verrà utilizzata dal nostro codice.

Personalmente sto utilizzando questo sistema da un po' di tempo, e al momento posso dirmi soddisfatto perchè finalmente ho trovato il modo di razionalizzare il lavoro e di ricondurlo ad un modello molto simile a quello che ASP.NET ci mette normalmente a disposizione. Mi interessa conoscere anche la vostra opinione perciò ho deciso di condividere con voi questo codice. Spero che qualcuno trovi utile la solzione e mi dia qualche consiglio su come mgliorare ulteriormente la piccola libreria. Il codice è stato scritto sul framework 3.5 e richiede l'uso di Visual Studio 2008.

Download: http://blog.boschin.it/download/Elite.Web.rar

Technorati tags: , , ,

Aggiungi Commento