Costruzione di custom control per ASP.NET: parte 1

Di Daniele Bochicchio

La costruzione di custom control è una delle attività più complesse che lo sviluppatore ASP.NET possa affrontare, ma consente al tempo stesso di trarre il massimo vantaggio dall’infrastruttura messa a disposizione dall'ambiente, perchè rende riutilizzabile al massimo il codice e le funzionalità implementate.

Un custom control scritto bene può essere tra l’altro riutilizzato anche in ambiti differenti, garantendo un maggior ritorno in termini di tempo impiegato nell’implementazione di funzionalità simili.

All’interno di questo articolo, parte di una serie, verranno affrontati i temi più comuni nella creazione di controlli custom, con l’aiuto di numerosi esempi pratici, forniti sia in C# che VB.

In questa pagina

Cos’è un custom control Cos’è un custom control
Custom vs User Control Custom vs User Control
Il primo custom control Il primo custom control
Registrazione globale dei controlli Registrazione globale dei controlli
Conclusioni Conclusioni
Approfondimenti Approfondimenti

Cos’è un custom control

Un custom control è una classe particolare, pensata per emettere markup, in genere XHTML. Sfrutta l’infrastruttura del Page Framework di ASP.NET nello stesso identico modo in cui lo fanno i controlli che siamo abituati ad utilizzare, come Label o GridView.

Si chiamano custom control proprio perché sono controlli personalizzati nel comportamento, che non hanno niente da invidiare a quelli già inclusi in ASP.NET e possono tendenzialmente includere qualsiasi tipo di funzionalità, ponendo come unico limite le necessità e le capacità dello sviluppatore.

Proprio come i controlli inclusi all’interno di quelli offerti da ASP.NET, generalmente ereditano da un controllo che ne racchiudere parte delle funzionalità.

L’obiettivo, mediante uno dei principi fondamenti dell’Object Oriented Programming, è quello di estendere controlli esistenti sfruttando l’ereditarietà e componendo le funzionalità sulla base di qualcosa di già esistente.

Nel caso non si abbia la necessità di sfruttare ed espandere qualcosa che già esiste, ma si vuole partire da zero senza , si implementa una classe, derivando quella Control, contenuta nel namespace System.Web.UI.

In realtà quasi sempre si preferisce scegliere la classe WebControl del namespace System.Web.UI.WebControls, perché include maggiori funzionalità.

In questo modo il nostro controllo ha in dote una serie di caratteristiche minime, come il supporto per gli stili attraverso i CSS, piuttosto che la logica di rendering, che è quello che consente al controllo poi di emettere il markup desiderato. Per quanto riguarda quest’ultima fase, è la pagina a richiedere ai controlli che contiene di effettuare il rendering ed eventualmente il controllo stesso a richiederlo a quelli contenuti, così da avera a video il risultato desiderato, attraverso la generazione a catena del markup risultante.

 

Custom vs User Control

Rispetto ad uno user control, che magari si utilizzano con maggior dimestichezza, i custom control sono contraddistinti dall’essere composti al 100% da codici, essendo creati a partire da classi. Gli user control, dal canto loro, sono invece composti da una parte di markup inserita direttamente nella pagina, che spesso contiene dichiarazioni di controlli, e di una di puro codice, che si mischiano insieme un po’ come avviene nella pagina, di cui possono essere considerati a tutti gli effetti dei pezzi. Eccone un esempio:

C# - Time.ascx

<%@ Control Language="C#" AutoEventWireUp="false" %>
<p>Ora corrente: <asp:label id="Time" runat="server" /></p>
<script runat="server">
protected override void OnLoad(EventArgs e)
{
Time.Text = DateTime.Now.ToString();
}
</script>

VB – Time.ascx

<%@ Control Language="VB" %>
<p>Ora corrente: <asp:label id="Time" runat="server" /></p>
<script runat="server">
Sub Page_Load()
Time.Text = DateTime.Now.ToString()
End Sub
</script>

Successivamente alla registrazione nella pagina, l’inserimento nella stessa è l'operazione più semplice, con l’unica differenza del prefisso rispetto ai controlli inclusi in ASP.NET:

Time.aspx
<%@ Register TagPrefix="aspitalia" TagName="Time" Src="time.ascx" %>
<aspitalia:time runat="server" id="CurrentTime" />

*

Figura 1: lo user control in azione nella pagina.

Nel caso degli user control, non essendo classi, non è necessario ridistribuirne la forma compilata, contenuta all’interno di un assembly, ma il vero e proprio sorgente, contenuto nei file. Per questo motivo la distribuzione di user control all’intero di progetti differenti è difficile e spesso macchinosa. Viceversa, quella dei custom control è invece semplificata dalla necessità di distribuire direttamente l'assembly e dalla garanzia che questi siano indipendenti dalla necessità di una singola applicazione, grazie all’utilizzo di tecniche che favoriscono la possibilità di specificare lo stile da associare ai controlli in maniera slegata dal template utilizzato, ad esempio, a garanzia di un’ampia possibilità di riutilizzo e di una netta separazione tra funzionalità e stile grafico.

 

Il primo custom control

Il modo migliore per comprendere appieno le potenzialità di queste tecniche è quello di cominciare ad implementare un primo controllo, così da sperimentare in maniera diretta quanto in realtà gli argomenti di cui stiamo discutendo non siano poi così complessi.

Un custom control, come detto pocanzi, è semplicemente una classe e pertanto è necessario creare un progetto di tipo "Class Library", oppure, qualora si abbia la versione Express di Visual Web Developer, provvedere all’aggiunta della classe direttamente nella directory /App_Code/, introdotta con ASP.NET 2.0.

Questa directory può contenere qualsiasi tipo di classe, pertanto anche custom control, ed il suo contenuto è compilato in maniera automatica da ASP.NET alla prima richiesta fatta all’applicazione, non avendo particolari differenze, se non di facilità di distribuzione, rispetto alla creazione di una class library. Tuttavia, questa modalità è da evitare quando si può creare una class library, perché si ha un certo rallentamento nella fase iniziale di caricamento dell'applicazione, visto che le classi contenute devono essere compilate al volo in un assembly.

Per procedere con un primo esempio, provvediamo a creare un custom control che derivi da Control ed aggiunga una serie di proprietà, da utilizzarsi per cambiarne il modo di apparire.

Nel caso specifico, procediamo con la creazione di un semplicissimo custom control, in grado di mostrare la data e l’ora corrente, formattando in maniera diversa il risultato. Ad esempio, agendo su una proprietà di tipo enum, è possibile mostrare la data, l’ora oppure entrambi, mentre un’altra ancora consente di cambiare lo stile associato allo stesso. Per prima cosa, dunque, ecco il sorgente associato alla classe.

C#

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

namespace ASPItalia.com.Web.UI.Controls
{
public class Time : Control
{
private string _cssClass = null;
private string _timeCssClass = null;
private DisplayTimeType _displayTimeType = DisplayTimeType.Both;

public DisplayTimeType DisplayTimeType
{
get { return _displayTimeType; }
set { _displayTimeType = value; }
}

public string CssClass
{
get { return _cssClass; }
set { _cssClass = value; }
}

public string TimeCssClass
{
get { return _timeCssClass; }
set { _timeCssClass = value; }
}

protected override void Render(HtmlTextWriter writer)
{
// scrivo un tag <p>
writer.WriteBeginTag("p");
if (!string.IsNullOrEmpty(CssClass))
writer.WriteAttribute("class", TimeCssClass);

writer.Write(">");

// scrivo la data, solo se serve
if (DisplayTimeType == DisplayTimeType.Both ||
DisplayTimeType == DisplayTimeType.DateOnly)
writer.Write(DateTime.Now.ToShortDateString());

writer.Write(" ");

// scrivo l'orario, solo se serve
if (DisplayTimeType == DisplayTimeType.Both ||
DisplayTimeType == DisplayTimeType.TimeOnly)
{
// scrivo il CSS applicato all'orario, se specificato
if (!string.IsNullOrEmpty(TimeCssClass))
{
writer.WriteBeginTag("span");
writer.WriteAttribute("class", CssClass);
writer.Write(">");
}

writer.Write(DateTime.Now.ToShortTimeString());

// scrivo il CSS applicato all'orario, se specificato
if (!string.IsNullOrEmpty(TimeCssClass))
writer.WriteEndTag("span");
}

// chiudo il tag </p>
writer.WriteEndTag("p");

// scrivo a video
base.Render(writer);
}
}

public enum DisplayTimeType
{
Both = 0,
DateOnly = 1,
TimeOnly = 2
}
}

VB

Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts
Imports System.Web.UI.HtmlControls
 
Namespace ASPItalia.com.Web.UI.Controls
Public Class Time
 Inherits Control
Private _cssClass As String =  Nothing 
Private _timeCssClass As String =  Nothing 
Private _displayTimeType As DisplayTimeType =  DisplayTimeType.Both 
 
Public Property DisplayTimeType() As DisplayTimeType
Get 
 Return _displayTimeType
End Get
Set (ByVal Value As DisplayTimeType) 
 _displayTimeType = value
End Set
End Property
 
Public Property CssClass() As String
Get 
 Return _cssClass
End Get
Set (ByVal Value As String) 
 _cssClass = value
End Set
End Property
 
Public Property TimeCssClass() As String
Get 
 Return _timeCssClass
End Get
Set (ByVal Value As String) 
 _timeCssClass = value
End Set
End Property
 
Protected Overrides  Sub Render(ByVal writer As HtmlTextWriter)
' scrivo un tag p
writer.WriteBeginTag("p")
If Not String.IsNullOrEmpty(CssClass) Then
writer.WriteAttribute("class", TimeCssClass)
End If
 
writer.Write(">")
 
' scrivo la data, solo se serve
            If DisplayTimeType =
DisplayTimeType.Both Or DisplayTimeType =
UI.Controls.DisplayTimeType.DateOnly Then
                writer.Write(DateTime.Now.ToShortDateString())
            End If
 
writer.Write(" ")
 
' scrivo l'orario, solo se serve
            If DisplayTimeType =
DisplayTimeType.Both Or DisplayTimeType = UI.Controls.DisplayTimeType.TimeOnly Then               
 ' scrivo il CSS applicato all'orario, se specificato
                If Not String.IsNullOrEmpty(TimeCssClass) Then
                    writer.WriteBeginTag("span")
                    writer.WriteAttribute("class", CssClass)
                    writer.Write(">")
                End If

                writer.Write(DateTime.Now.ToShortTimeString())

                ' scrivo il CSS applicato all'orario, se specificato
                If Not String.IsNullOrEmpty(TimeCssClass) Then
                    writer.WriteEndTag("span")
                End If
            End If
 
' chiudo il tag p
writer.WriteEndTag("p")
 
' scrivo a video
MyBase.Render(writer)
End Sub
End Class

Public Enum DisplayTimeType 
Both = 0
DateOnly = 1
TimeOnly = 2
End Enum
End Namespace

La scrittura del markup avviene attraverso la classe HtmlTextWriter, che ha diversi metodi adatti allo scopo. Come si può notare, l'emissione dei tag non avviene utilizzando il generico metodo Write, ma con quelli specifici WriteBeginTag, WriteEndTag, WriteAttribute, che servono rispettivamente a scrivere un tag (ad esempio <p), a chiuderlo (ad esempio </p>) ed a scrivere un attributo (ad esempio attr="valore").

La logica di creazione, in questo caso, è tutta contenuta nel metodo Render, che è quello invocato per generare l'output. Le proprietà CssClass e TimeCssClass consentono di applicare uno stile CSS alla data ed in alternativa all'orario, mentre quella denominata DisplayTimeType consente di specificare un valore dell'enum che porta lo stesso nome, che indica cosa visualizzare.

Il controllo va poi registrato nella pagina e questo è possibile grazie alla diretttiva <%@Register%>, che deve avere questo aspetto:

<%@ Register TagPrefix="aspitalia" Namespace="ASPItalia.com.Web.UI.Controls" Assembly="app_code" %>

Le informazioni obbligatorie da inserire sono il prefisso del tag, il namespace e l'assembly, che nel caso in cui si metta il sorgente direttamente nella directory /App_Code/ prende proprio questo nome, slash esclusi.

Il TagName che viene specificato negli user control non è invece disponibile, perché viene preso in automatico il nome dato alla classe. Per questo motivo è essenziale evitare di aggiungere suffissi strani (come control o object), perché di fatto il controllo sarà usato con il nome che viene dato alla classe.

*

Figura 2: Il nostro primo custom control all'opera, con varie proprietà applicate ed un diverso comportamento risultante.

 

Registrazione globale dei controlli

Sia custom che user control possono essere registrati in maniera centralizzata, evitando di ripetere questa operazione ogni volta che sia necessario farlo. Questa tecnica consente di avere sempre pronti all'utilizzo i controlli più utilizzati nell'applicazione, senza fare nient'altro.

Nel nostro caso, sarebbe bastato aggiungere al web.config queste righe di configurazione:

<configuration>
  <system.web>
    <pages>
      <controls>
        <add tagPrefix="aspitalia" namespace=" ASPItalia.com.Web.UI.Controls " assembly="App_Code" />
      </controls>
    </pages>
  </system.web>
</configuration>

Nel caso di uno user control, la riga <add /> diventa:

<add tagPrefix="aspitalia" tagName="time" src="time.aspx" />

 

Conclusioni

Nelle prossime puntate analizzeremo con maggior dettaglio altri aspetti legati alla creazione di custom control, mostrando le varie situazione e soluzioni adottabili.

Il controllo proposto in questo primo articolo è uno spunto di riflessione, che vi consentirà di sperimentare cosa voglia dire costruire custom control basati su ASP.NET.

L'importante, quando si affronta questo argomento, è sempre ricordare che si è di fronte ad una classe vera e propria, con tutti i vantaggi e tutti gli svantaggi che questo comporta!

 

Approfondimenti