Alla nascita di ASP.NET (versione 1.0), l'introduzione della logica ad oggetti nella programmazione di siti e applicazioni web è avvenuta proprio grazie all'invenzione dei controlli web lato server, dei particolari tipi di TAG, da mescolare a quelli propri dell'HTML, con proprietà ed eventi che possono essere gestiti lato server.
Questi controlli, che in inglese sono chiamati Server Controls, stanno alla base della logica di separazione tra lo strato di presentazione e quello di computazione lato server proprio di ASP.NET.
Proprio il loro nome, infatti, deriva dal fatto che ognuno di questi elementi, una volta inserito in una pagina .aspx, viene trattato come una vera e propria istanza di un oggetto dal motore di ASP.NET e risulta così in grado di fornire un insieme di proprietà, metodi ed eventi che ne modificano il comportamento ad ogni post della pagina al server.
In questa maniera, è stata abbandonata del tutto la tecnica denominata "spaghetti-code" che vedeva pezzi di codice inseriti tra i tag HTML della pagina (tecnica propria di classic ASP e delle versioni meno recenti di PHP), in favore del paradigma ad oggetti, sicuramente meglio gestibile e stilisticamente più appropriato.
Ora quindi abbiamo a che fare con proprietà, utili a leggere o impostare valori per modificare l'aspetto e il comportamento dell'oggetto, e con eventi, in modo tale da eseguire operazioni personalizzate in seguito a determinati comportamenti sia del controllo che dell'utente.
Questi controlli, sin dalla prima versione del .NET Framework, sono stati divisi in due grandi filoni:
- Gli HTML Server Controls
- I Web Server Controls
Nonostante la netta divisione però, tutti gli elementi di entrambe le famiglie derivano dalla medesima classe, la classe Control
del namespace System.Web.UI
. Questo per fare in modo che comunque tutti fossero dotati di proprietà di base comuni (come la proprietà id
) e che tutti seguissero lo stesso ciclo di vita. Esaminiamo in dettaglio le caratteristiche principali di entrambe le categorie.
HTML Server Controls
Questa famiglia contiene al suo interno tutti quei controlli che rappresentano i tag definiti dall'HTML, che in questo modo possono essere trattati anche "lato server".
In una pagina web ASP.NET tutti i tag HTML vengono trattati dal parser come delle semplici stringhe. Per far in modo che questi diventino degli HTML Server Controls e che possano quindi essere "visti" lato server, bisogna aggiungere e valorizzare l'attributo ID
, che serve ad identificare l'elemento nella parte di "code-behind" della pagina ed aggiungere l'attributo runat="server"
. Il tag eredita tutte le proprietà base dei controlli della famiglia degli HTML Server Controls.
Quindi, per fare un esempio, il classico link:
<a href="http://www.peppedotnet.it">peppedotnet.it</a>
per entrare a far parte della famiglia degli HTML Server Controls, diventa:
<a href="http://www.peppedotnet.it" id="lnkPeppeDotNet" runat="server">peppedotnet.it</a>
In questo modo, nella parte di code-behind della pagina, sarà possibile utilizzare l'oggetto denominato "lnkPeppeDotNet" come istanza della classe HtmlAnchor
:
lnkPeppeDotNet.Title = "PeppeDotNet.it - Blog";
lnkPeppeDotNet.HRef = "http://www.peppedotnet.it/Blog";
Tutti gli altri controlli HTML assieme al controllo HtmlAnchor
, sono disponibili sotto il namespace System.Web.UI.HtmlControls
.
L'unico vincolo che abbiamo riguardo l'utilizzo degli HTML Server Controls è che questi devono essere inseriti all'interno del tag <form runat="server">
, elemento che deve essere presente almeno e solo una volta all'interno della pagina.
Web Server Controls
I Web Server Controls sono una famiglia di controlli più avanzati che non necessariamente corrispondono ad un singolo elemento HTML, ma possono includere in un singolo oggetto elementi e funzionalità molteplici. Anche questi controlli necessitano dell'attributo runat="server"
per poter essere compilati correttamente assieme alla pagina che li contiene e per essere trattati anche lato server.
Si differenziano dagli altri tipi di controlli, in quanto i nomi devono essere preceduti dal prefisso "asp:". Come abbiamo detto, quindi, esistono Web controls che possono rappresentare un singolo elemento HTML e Web controls che invece racchiudono funzionalità più complesse. Vediamo due esempi:
<!-- il controllo HyperLink rappresenta il tag "a" -->
<asp:HyperLink id="hyperlink" runat="server"
NavigateUrl="http://www.peppedotnet.it"
Text="peppedotnet.it" />
<!-- il controllo Calendar, visualizza un calendario -->
<asp:Calendar id="calendar" runat="server"
NextPrevFormat="ShortMonth" />
In questo modo si lascia da parte la sintassi HTML per andare ad approfondire tematiche riguardanti la programmazione ad oggetti.
I web control che abbiamo esaminato sono tra quelli che il .NET Framework mette a disposizione sotto il namespace System.Web.UI.WebControls
. Anche questi controlli devono essere aggiunti all'interno della pagina come figli dell'elemento <form runat="server">
.
I Web Server Controls offrono delle funzionalità in più rispetto ai controlli HTML:
- i dati sono mantenuti in ogni richiesta (postback), all'interno del viewstate,
- le proprietà sono strumenti di controllo lato sever del look and feel del controllo e non semplici mappature di attributi HTML,
- possiedono dei meccanismi per la rilevazione automatica delle proprietà del browser, che gli permette di comportarsi in maniera diversa a seconda del browser utilizzato dall'utente e delle sue caratteristiche (questo per la localizzazione automatica delle etichette e per il rendering in caso di dispositivi mobili).
All'interno di questa categoria di controlli web, troviamo vari tipi di sotto-famiglie:
- i controlli DataSource - controlli web che fungono da fonte di dati per altri controlli,
- i controlli compositi - controlli web composti da più controlli figli.
- i controlli Data-Bound - controlli web in grado di rappresentare informazioni prelevate da una fonte di dati.
- i Templated-Controls - controlli web in grado di fornire più template di visualizzazione.
La maggior parte di queste tipologie di controlli, saranno oggetto dei prossimi articoli, comunque è facile capire che questi controlli vengono privilegiati rispetto agli "HTML controls", in quanto offrono un set di funzionalità maggiori e rispecchiano completamente il paradigma ad oggetti. Inoltre è possibile estendere il set di controlli web esistente con dei nuovi controlli, ereditando dalla classe padre WebControl.
Ciclo di vita di un controllo web lato server
Il motore di ASP.NET carica (load) una pagina ogni volta che questa viene richiesta e la "scarica" (unload) quando la pagina viene chiusa.
All'interno della pagina, ogni controllo viene attivato a sua volta con e segue un proprio ciclo di vita, che porta dalla inizializzazione e gestione delle proprietà alla visualizzazione del codice HTML. Questo ciclo di vita è scandito dal succedersi di diverse fasi.
- Fase di inizializzazione - è la fase in cui vengono letti particolari settaggi che servono durante le fasi successive (come stringhe di connessione o variabili di applicazione). Questa fase è rappresentata dall'evento Init e dal relativo gestore OnInit.
- Caricamento del ViewState - è la fase in cui viene popolato il ViewState con le informazioni relative al controllo stesso, quindi i valori delle proprietà, lo stato … Questa fase viene eseguita dal metodo LoadViewState (che può essere sovrascritto in modo tale da personalizzare il salvataggio del ViewState).
- Elaborazione dei dati del postback - lettura dei dati proveniente da una ricaricamento della form e aggiornamento delle proprietà relative del controllo. Questa fase viene eseguita dal metodo LoadPostData.
- Caricamento - in questa fase il controllo viene caricato all'interno dell'oggetto Page che rappresenta la relativa pagina che lo contiene. Questa fase è rappresentata dall'evento Load o dal relativo gestore OnLoad.
- Notifica di cambiamenti nel postback - vengono scatenati gli eventi di aggiornamento in modo tale da segnalare le differenze tra un postback e l'altro. Questa fase viene eseguita dal metodo RaisePostDataChangedEvent.
- Gestione degli eventi di postback - gestione degli eventi che vengono scatenati lato client e relativo scatenamento degli appropriati eventi di postback. Fase eseguita dal metodo RaisePostBackEvent.
- Pre-Render - fase in cui effettuare gli ultimi aggiornamenti alle proprietà del controllo, prima che questo venga rende rizzato. Fase rappresentata dall'evento PreRender e dal relativo gestore OnPreRender.
- Salvataggio dello stato - fase in cui i valori di ogni singola proprietà del controllo vengono salvati all'interno del ViewState della pagina. Questa fase viene eseguita dal metodo SaveViewState.
- Render - fase in cui viene effettuato effettivamente il rendering del controllo all'utente. Fase, questa, rappresentata dall'omonimo evento Render.
- Dispose - fase in cui viene effettuata una pulizia delle risorse utilizzate dal controllo (in questa fase, per esempio, devono essere chiuse eventuali connessioni a database). Questa fase viene eseguita dal metodo Dispose.
- Scaricamento (unload) - ultima fase in cui possono essere liberate le risorse in uso dal controllo. Questa fase è rappresentata dall'evento UnLoad e dal relativo gestore OnUnLoad.
Lo sviluppatore può intervenire in ognuna di queste fasi nel corso dello sviluppo di controlli server personalizzati.
Creare un controllo web personalizzato
Come la stragrande maggioranza delle funzionalità del sistema su cui si basa ASP.NET, è possibile estendere il set di controlli web pre-esistenti sviluppando dei controlli personalizzati in grado di soddisfare quelle che risultano essere le esigenze dell'applicazione web che stiamo costruendo.
È possibile costruire sia degli HTML Server Controls, sia dei Web Server Controls, il punto però sta nel fatto che i primi sono stati creati solamente per mappare i tag presenti nelle specifiche dell'HTML e non hanno le possibilità che invece offrono i Web Server Controls.
Nell'esempio che affronteremo, esamineremo i passi necessari per implementare un semplice controllo Web personalizzato per creare dei link che aprano delle finestre pop-up. L'esempio è molto semplice, ma permette di imparare bene alcuni concetti di base riguardanti questo tipo di personalizzazioni.
La prima cosa da fare, è creare una nuova classe con uno dei lunguaggi compatibili con il .NET Framework e far si che questa erediti dalla classe WebControl
. Questa è la classe base di tutti i controlli Web lato server ed è la classe che fornisce le implementazioni della maggior parte dei metodi di base utili al corretto funzionamento del nostro controllo.
Ereditando da WebControl
infatti, non dobbiamo preoccuparci di gestire il postback o di implementare la lettura e il salvataggio dei valori dal ViewState
, poiché troviamo già tutto pronto; se invece, vogliamo personalizzare la gestione di un particolare comportamento oppure una delle fasi di vita del nostro controllo, non dobbiamo far altro che sovrascrive le proprietà o i metodi che vi vengono messi a disposizione.
Definizione di un controllo personalizzato
namespace Peppe.Web.UI.WebControls
{
public class PopUpLink : WebControl
{
//...
}
}
Le proprietà e i metodi che sovrascriveremo, saranno legati a due compiti ben diversi:
- la definizione del tag HTML di base - perché comunque un web control deve produrre del codice HTML e nel nostro caso, deve rappresentare un tipo di elemento ben preciso: il tag
<a>
; - il rendering degli attributi e dei contenuti.
Per quanto riguarda il primo obbligo, dobbiamo effettuare l'override della proprietà TagKey
(che ha solamente l'accessore get) in modo tale che ritorni, ogni qualvolta venga richiamata, il valore "A" dell'enumerazione HtmlTextWriterTag
, che rappresenta appunto l'elemento HTML <a>
.
protected override HtmlTextWriterTag TagKey
{
get { return HtmlTextWriterTag.A; }
}
In questa maniera, il nostro controllo web, una volta renderizzato dal motore di ASP.NET, produrrà un elemento <a>
.
Fatto questo, dobbiamo stabilire le proprietà utili a personalizzare al massimo il nostro controllo. Ognuna di queste avrà gli accessori set
e get
rispettivamente per scrivere e leggere dal ViewState
i relativi valori legati alle singole proprietà una volta che il controllo viene inserito all'interno di una pagina.
public string Text
{
get
{
object o = ViewState["Text"];
return (o == null) ? String.Empty : (string) o;
}
set { ViewState["Text"] = value; }
}
Come la proprietà Text appena vista, si è deciso di implementare le seguenti proprietà:
Text
- testo del linkUrl
- indirizzo da aprire in una finestra pop-upPopUpWidth
- larghezza della finestra pop-upPopUpHeight
- altezza della finestra pop-upResizable
- valore booleano che determina se il pop-up è ridimensianabile o meno
In questo modo il controllo è stato reso il più personalizzabile possibile.
Alcune di queste proprietà devono essere trattate in una maniera particolare. La proprietà Url
, si è deciso che dovrà essere la proprietà di default del nostro controllo, poiché non deve essere mai nulla, in quanto un link senza un'url allegata non ha ragione di esistere. La proprietà Text
invece, vogliamo che prenda e imposti il valore relativo al cotenuto testuale presente tra il tag di apertura e quello di chiusura del nostro controllo. Per definire questi vincoli però, dobbiamo decorare la definizione della nostra classe con particolari attributi:
[ParseChildren(true, "Text"), DefaultProperty("Url"), ToolboxData("<{0}:PopUpLink runat="server"> </{0}:PopUpLink>")]
public class PopUpLink : WebControl
Nota: l'attributo ToolboxData
serve all'integrazione del nostro controllo con Visual Studio .NET 2005, in quanto rappresenta il valore di markup che viene inserito all'interno della pagina, una volta che il controllo viene spostato tramite drag and drop dalla toolbox di Visual Studio alla pagina, in modalità visualizzazione.
Ora invece, dobbiamo pensare alla parte di rendering dei contenuti.
Per fare in modo che alla proprietà Text venga assegnato e letto il valore contenuto tra la fine del tag iniziale e l'inizio del tag finale del tag HTML che vogliamo renderizzare (nel nostro caso il tag "a"), dobbiamo sovrascrivere il metodo RenderContents e scrivere nello stream di output il cotenuto della nostra variabile Text.
protected override void RenderContents(HtmlTextWriter writer)
{
if (String.IsNullOrEmpty(Text))
Text = Url;
writer.Write(Page.Server.HtmlEncode(Text));
}
Fatto questo, l'output parziale del nostro controllo sarà:
<a>testo</a>
Per raggiungere l'obiettivo prefisso abbiamo bisogno di renderizzare gli attributi del nostro tag <a>
e la funzione JavaScript che genera il pop-up.
Per aggiungere degli attributi allo stream di output ci possiamo avvalere del metodo AddAttributesToRender
come segue:
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer); //per il rendering dell'attributo id
string href = String.Format("javascript:apri('{0}', {1}, {2}, {3});",
Url,
PopUpWidth.Value,
PopUpHeight.Value,
(Resizable) ? "1" : "0");
writer.AddAttribute(HtmlTextWriterAttribute.Href, href);
}
Mentre per quanto riguarda la generazione del codice javascript che crea la finestra pop-up, si è deciso di inserirne l'implementazione all'interno del metodo OnPreRender, in modo tale che questo venga inserito all'interno della pagina prima dell'effetivo rendering dell'elemento HTML che formerà il nostro link.
protected override void OnPreRender(EventArgs e)
{
string script = "<script type="text/javascript">n" +
"function apri(url,w,h,myRes)n" +
"{n" +
"mywin = window.open(url,"window",'toolbar=0,location=0," +
"directories=0,status=0,menubar=0,scrollbars=1,"+
"resizable='+myRes+',width='+w+',height='+h+'');n" +
"}n" +
"</script>";
if (!Page.ClientScript.IsClientScriptBlockRegistered(script))
{
Page.ClientScript.RegisterStartupScript(GetType(), "PopUp", script);
}
}
La funzione JavaScript che crea la finestra pop-up viene inserita all'interno della pagina attraverso il metodo RegisterStartupScript
della classe ClientScriptManager
, classe istanza della quale viene esposta tramite la proprietà ClientScript
della classe Page
e che permette di gestire lato server gli script all'interno della pagina.
Ora, non ci resta che testare il funzionamento del nostro controllo su un pagina web ASP.NET.
Installazione ed utilizzo
I controlli web, come la maggior parte degli oggetti legati ad ASP.NET (come i provider, gli expression builder, gli HTTP hanlder, gli HTTP modules, etc...), possono essere creati in due modi differenti:
- nella directory App_Code, salvando la classe del nostro controllo in questa cartella (con estensione ".cs", ".vb", o altro), esso sarà utilizzabile in tutte le pagine della nostra applicazione.
- All'interno di un assembly specifico, in modo tale da potersi creare le proprie librerie di controlli web e riutilizzarli in più applicazioni possibili.
Una volta inserita la classe all'interno dell'applicazione, dobbiamo registrare il controllo web, per permetterci di utilizzarlo nel markup di una pagina ASP.NET; anche in questo caso, le possibili tecniche sono due:
Possiamo una direttiva @Register
all'interno di ogni pagina in cui vogliamo utilizzare il nostro controllo, specificando i valori per le proprietà Assembly
, Namespace
e TagPrefix
.
Registrare un controllo su una singola pagina con @Register
<%@ Register Assembly="__code" Namespace="Peppe.Web.UI.WebControls" TagPrefix="peppe" %>
Altrimenti possiamo registrare il controllo Web all'interno del web.config
, aggiungendo una nuova voce sotto l'elemento system.web/pages/controls
, in modo da poterlo utilizzare in tutte le pagine della nostra applicazione.
Registrare un controllo per tutta l'applicazione
<pages>
<controls>
<add tagPrefix="peppe" namespace="Peppe.Web.UI.WebControls" assembly="__code"/>
</controls>
</pages>
Siamo ora in grado di inserire il controllo all'interno di una pagina web ASP.NET ed utilizzarne le relative funzionalità.
<peppe:PopUpLink ID="popup" runat="server"
Url="http://www.peppedotnet.it"
PopUpWidth="700"
PopUpHeight="500"
Resizable="true">peppedotnet.it</peppe:PopUpLink>
Integrazione con Visual Studio .NET 2005
Per far si che il nostro controllo web sia utilizzabile facilmente all'interno di Visual Studio, dobbiamo ricordarci di applicare alcuni accorgimento al codice, in modo tale da renderne possibile anche la ridistribuzione.
Nota: l'integrazione con Visual Studio risulta perfetta solo se si inseriscono i propri controlli web all'interno di assembly .NET specifici.
La prima cosa da fare è decorare il namespace con l'attributo TagPrefix
, in grado di specificare all'IDE che prefisso scrivere una volta che il controllo viene spostato tramite drag and drop all'interno della pagina:
Decorare il namespace
[assembly: TagPrefix("Peppe.Web.UI.WebControls", "peppe")]
namespace Peppe.Web.UI.WebControls
{
//...
}
Dobbiamo poi decorare anche la classe che rappresenta il nostro controllo Web con l'attributo ToolboxData
, come spiegato precedentemente ed aggiungere ad ogni proprietà gli attributi per specificarne la possibilità di modifica attraverso la toolbox (attributo Browsable
), un'eventuale valore di default e un'eventuale descrizione in grado di aiutare lo sviluppatore all'utilizzo del controllo distribuito.
Esempio di decorazione di una proprietà
[Browsable(true), Category("Appearance"), DefaultValue("http://www.peppedotnet.it"), Description("Link url value.")]
public string Url
{
get
{
object o = ViewState["Url"];
return (o == null) ? String.Empty : (string)o;
}
set { ViewState["Url"] = value; }
}
Una volta compilato l'assembly e aggiunto tra le referenze della nostra applicazione web, possiamo procedere all'inserimento del controllo nella toolbox di visual studio (se l'ide non ha provveduto a questa operazione in automatico) in questo modo:
- Tasto destro sulla Toolbox>Add Tab per aggiungere un nuovo gruppo di controlli
- Tasto destro nell'area del nuovo tab e poi Choose Items...
- Selezione dell'assembly appena compilato
- Selezione dei controlli web presenti all'interno dell'assembly
Eseguite queste operazioni, i controlli presenti all'interno della libreria verranno visualizzati nella toolbox di Visual Studio .NET 2005.
Conclusioni
In questo articolo abbiamo fatto una panoramica sui controlli lato server presenti all'interno dell'architettura che sta alla base di ASP.NET 2.0 e abbiamo visto quali sono i passi per implementarne di nuovi. Ora, lo sviluppo di controlli web personalizzati è uno dei campi più vasti del .NET Framework, in quanto è possibile soddisfare le più disperate necessità visuali e funzionali per le proprie applicazioni. Inoltre, per fare maggiore chiarezza nell'intera architettura, sono state create varie categorie di controlli (come abbiamo visto nei primi paragrafi dell'articolo) in modo da differenziarne i comportamenti e i relativi utilizzi.
Nei prossimi articoli vedremo com'è possibile aggiungere il supporto per il design-time di Visual Studio .NET al nostro controllo personalizzato e quali sono le altre sotto-famiglie dei Web Server Controls messe a disposizione da ASP.NET 2.