Costruzione di custom control per ASP.NET: parte 2

Di Daniele Bochicchio

Costruire custom control è una delle attività più impegnative che uno sviluppatore ASP.NET possa intraprendere, poichè questi componenti sono uno dei punti di successo di ASP.NET in quanto tecnologia per lo sviluppo web, grazie alla possibilità che offrono di utilizzare un approccio totalmente dichiarativo nello sviluppo, basato cioè su tag anzichè codice vero e proprio.

L’impegno consiste nel progettare al meglio questi controlli così che lo sviluppatore possa trarre il massimo vantaggio possibile dall’architettura di ASP.NET, evitando malfunzionamenti dovuti all’utilizzo improprio di alcune di queste funzionalità.

In questa seconda parte di questa serie ci soffermeremo in maniera specifica sul mantenimento dello stato, caratteristica molto importante nel modello offerto da ASP.NET.

In questa pagina

Il ViewState prima di ASP.NET 2.0 Il ViewState prima di ASP.NET 2.0
Utilizzare il ViewState Utilizzare il ViewState
Il ControlState di ASP.NET 2.0 Il ControlState di ASP.NET 2.0
Utilizzare il ControlState Utilizzare il ControlState
Conclusioni Conclusioni
Approfondimenti Approfondimenti

Il ViewState prima di ASP.NET 2.0

ASP.NET, fino alla versione 1.1, per mantenere i valori delle proprietà dei controlli presenti sulla pagina tra i vari PostBack si basa su una funzionalità nota come ViewState. Questo meccanismo è perfettamente collaudato e consente di “dimenticarsi” di alcuni dettagli durante lo sviluppo, come l’impostare il valore di una TextBox in caso di PostBack, perchè sono intrinseci all’architettura stessa della pagina e fanno parte di quelle funzionalità note come Page Framework.

Per gli sviluppatori abituati a dover mantenere manualmente i valori dei vari componenti della pagina, questa novità ha a suo tempo rappresentato un’autentica rivoluzione, consentendo di abbattere notevolemente il tempo di sviluppo e, di riflesso, anche di semplificarlo. Se infatti per una TextBox questa operazione è davvero banale, può diventare più complessa nel caso di DropDownList, piuttosto che in controlli più complessi, come può essere una griglia. Il tutto tocca vette di maggiore complessità quando i dati che utilizzano sono prelevati da fonti esterne, come un database.

Dal punto di vista tecnico, il ViewState è rappresentato da una classe specifica che si occupa di contenere le informazioni, che poi vengono inviate al client all’interno di un campo nascosto presente nella WebForm, chiamato __VIEWSTATE. Questo campo rappresenta la coppia chiave/valore della classe StateBag, trasformata in formato Base64 per essere inviata ogni volta tra richiesta e risposta senza perdita di informazioni.

La chiave di funzionamento di un meccanismo del genere è rappresentata, infatti, dalla possibilità che tra le varie richieste che si susseguono, per intercettare e scatenare gli eventi, queste informazioni vengano inviate avanti ed indietro tra client e server. In questo modo possono sempre essere recuperate al successivo invio e lo stato dei controlli può essere mantenuto senza intervento da parte dello sviluppatore.

Il risultato di tutto questo è che ASP.NET consente, attraverso i propri controlli, di supportare un meccanismo di sviluppo tipico, fino alla sua introduzione, soltanto delle applicazioni Windows, dove è del tutto naturale basarsi sul concetto di evento e scrivere codice in maniera object oriented.

Il prezzo da pagare per questo automatismo, così per come è stato pensato, è quello di avere spesso una quantità di informazioni salvata all’interno di questo campo nascosto che spesso è superiore al peso dell’HTML del resto dell’intera pagina, con un ovvio degrado delle performance.

Se il consiglio migliore, quando possibile, è quello di disattivare il ViewState se si utilizzano controlli di terze parti (a patto che il controllo stesso non ne abbia assoluta necessità), come sviluppatori di custom control la necessità è invece quella di minimizzarne più possibile l’uso, proprio per evitare che anzichè trarne giovamento, alla fine si abbia un effetto totalmente contrario, con il già citato degrado di performance dietro l’angolo.

 

Utilizzare il ViewState

Quando si sviluppano controlli è possibile fare uso del ViewState semplicemente salvando e caricando le informazioni utilizzando l’omonima classe. Supponendo di voler salvare il valore di una proprietà denominata Text, così che sia sempre disponibile in caso di PostBack, il codice da scrivere sarebbe all’incirca quello seguente:

C#

using System;
using System.Web.UI;

namespace ASPItalia.com.MSDN.Controls2
{
public class TextControl: Control
{
public string Text
{
get
{
return (string)ViewState("Text");
}
set
{
ViewState["Text"] = value;
}
}

protected override void Render(HtmlTextWriter writer)
{
writer.Write(Text);
base.Render(writer);
}
}
}

VB

Imports System
Imports System.Web.UI
 
Namespace ASPItalia.com.MSDN.Controls2
Public Class TextControl
 Inherits Control
Public Property Text() As String
Get 
Return CType(ViewState("Text"), String)
End Get
Set (ByVal Value As String) 
ViewState("Text") = value
End Set
End Property
 
Protected Overrides  Sub Render(ByVal writer As HtmlTextWriter)
writer.Write(Text)
MyBase.Render(writer)
End Sub
End Class
 
End Namespace

Come si può notare, l’utilizzo del ViewState è molto intuitivo e di facile applicazione: basta salvare i valori delle proprietà in un campo, quando serve, senza abusarne.

 

Il ControlState di ASP.NET 2.0

La versione 2.0 di ASP.NET include un nuovo meccanismo di persistenza dello stato specifico per i controlli, chiamato ControlState.

La caratteristica di molti controlli di ASP.NET 1.1 di non essere in grado di funzionare con il ViewState disabilitato, nemmeno mantenendo un set minimo di funzionalità, rappresenta motivo di fastidio per molti sviluppatori. Il caso più eclatante è sicuramente quella rappresentato dal controllo DataGrid, che salva all’interno del ViewState anche le informazioni relative alla sua formattazione, nonostante queste spesso siano definite direttamente nella dichiarazione del markup e dunque vengano riassegnate a prescindere ad ogni PostBack. Questo controllo, poi, salva all’interno del ViewState anche i valori delle singole celle, che con buona probabilità saranno comunque riletti dalla fonte dati al successivo PostBack.

Con il controllo appena citato, ad esempio, non è possibile far funzionare nessuno degli eventi senza che il ViewState sia abilitato, con lo svantaggio che in molte situazioni è necessario optare per altri controlli, piuttosto che rinunciare alle performance, generando centinaia di KB di informazioni che spesso sono perfettamente inutili.

Quello che il Control State rappresenta è un repository specifico per i controlli, che riesca a funzionare anche in assenza del ViewState, consentendo di memorizzare solo le informazioni strettamente necessarie al funzionamento dei controlli durante i vari PostBack, rispondendo a questa esigenza specifica di chi ha necessità di rendere in grado di funzionare i propri controlli anche in assenza di ViewState.

A partire dalla versione 2.0 di ASP.NET, il ViewState rappresenta il contenitore delle funzionalità di contorno, come le impostazioni dell’interfaccia grafica, mentre il nuovo ControlState rappresenta il contenitore dello stato comportamentale, consentendo di salvare le informazioni strettamente necessarie.

Stando a quanto detto, il nuovo controllo GridView di ASP.NET 2.0, che sostituisce come caratteristiche il controllo DataGrid, funziona esattamente in questo modo, così che gli eventi scatenati in risposta alla necessità di cambiare pagina o modificare un dato posssano funzionare anche con ViewState disabilitato, poichè sfruttano il ControlState in luogo del ViewState.

Se il nostro obiettivo è quello di creare controlli che funzionino solo con ASP.NET 2.0, possiamo tranquillamente adottare questa tecnica, anche per evitare un inutile appesantimento della banda e del carico del server.

Tecnicamente parlando, anche il ControlState va a finire nello stesso campo nascosto del ViewState ed è il motivo per cui, anche disattivando il ViewState a livello di pagina, ci sono alcune informazioni comunque presenti all’interno di quest’ultimo. Il contenuto del ControlState viene infatti memorizzato sempre come ultimo nodo nell’albero del ViewState, sotto forma di un oggetto di tipo HybridDictionary, a patto che almeno uno dei controlli della pagina ne faccia uso.

Poichè non è disattivabile dallo sviluppatore, come invece avviene con il ViewState, nel ControlState dovrebbero solo essere memorizzare quelle informazioni vitali al controllo stesso e pertanto, come sviluppatori di custom control, è necessario prestare molta attenzione in fase di design dei controlli.

Ancora una volta, il caso migliore da prendere in considerazione è il controllo GridView, novità rispetto alla versione 1.1. Gli sviluppatori del team di ASP.NET hanno fatto la saggia scelta di salvare all’interno del ControlState solo gli indici da utilizzare per poter gestire gli eventi lato server EditIndex, SelectedIndex, PageIndex, SortDirection, SortExpression, anche in assenza di ViewState. A proposito di questa scelta, è anche utile notare la natura non complessa (stringhe) di queste informazioni, così da evitare un rapido aumento delle dimensioni del ControlState e quindi, di riflesso, del ViewState.

 

Utilizzare il ControlState

Per accedere al ControlState è necessario fare l’override di alcune funzioni virtual, precisamente LoadControlState e SaveControlState, che si occupano rispettivamente di caricare e salvare il contenuto del ControlState nel ViewState, con un modello molto simile a quello previsto dal caricamento e salvataggio del ViewState stesso.

L’altra aggiunta riguarda la presenza di una chiamata al metodo RegisterRequiresControlState della classe Page all’interno del metodo OnInit, che ha l’effetto di notificare alla pagina la necessità del controllo di accedere alla funzionalità in oggetto.

A differenza di quanto accade con il ViewState, il cui accesso è fornito attraverso un’apposita classe, il ControlState è più nascosto, proprio per evitare che venga abusato.

Nell’esempio che segue viene mostrato un pezzo di codice da utilizzare per costruire un controllo che al PostBack mantenga il valore dell’indice selezionato andandolo a leggere e scrivere nel ControlState:

C#

namespace ASPItalia.com.MSDN.Controls2
{
public class StateControl : Control
{
int _selectedIndex = 0;

protected override void OnInit(EventArgs e)
{
// registra il controllo per il ControlState
Page.RegisterRequiresControlState(this);

base.OnInit(e);
}

/// <summary>
/// Carica il contenuto dal ControlState
/// </summary>
/// <param name="savedState"></param>
protected override void LoadControlState(Object savedState)
{
// lettura del ControlState di base
Object[] rawState = (Object[])savedState;
base.LoadControlState(rawState[0]);

// lettura dell'indice selezionato
_selectedIndex = (int)rawState[1];
}


/// <summary>
/// Salva il contenuto del ControlState.
/// </summary>
/// <returns></returns>
protected override object SaveControlState()
{
// scrittura del ControlState di base
Object[] rawState = new Object[2];
rawState[0] = base.SaveControlState();

// aggiunta dell'indice selezionato
rawState[1] = _selectedIndex;
return rawState;
}

}

}

VB

Namespace ASPItalia.com.MSDN.Controls2
Public Class StateControl
 Inherits Control

Dim _selectedIndex As Integer =  0 

Protected Overrides  Sub OnInit(ByVal e As EventArgs)
' registra il controllo per il ControlState
Page.RegisterRequiresControlState(Me)
 
MyBase.OnInit(e)
End Sub

‘ Carica il contenuto dal ControlState
Protected Overrides  Sub LoadControlState(ByVal savedState As Object)
' lettura del ControlState di base
Dim rawState() As Object = CType(savedState, Object())
MyBase.LoadControlState(rawState(0))
 
' lettura dell'indice selezionato
_selectedIndex = CType(rawState(1), Integer)
End Sub

‘ Salva il contenuto del ControlState. 
Protected Overrides Function SaveControlState() As Object
' scrittura del ControlState di base
Dim rawState() As Object =  New Object(2) {} 
rawState(0) = MyBase.SaveControlState()
 
' aggiunta dell'indice selezionato
rawState(1) = _selectedIndex
Return rawState
End Function
 
End Class
 
End Namespace

Nell’eventualità di avere più valori da salvare, è sufficiente aggiungerne di nuovi all’interno dell’array di Object appena creato, per poi leggerli come si è fatto nel caso appena mostrato.

 

Conclusioni

Per la natura stessa di ASP.NET ed il meccanismo del PostBack, il mantenimento dello stato da parte dei controlli rappresenta una caratteristica fondamentale, poichè consente a chi li utilizza di sfruttare al massimo uno dei vantaggi offerti dal Page Framework di ASP.NET.

La versione 2.0 introduce diverse novità per chi sviluppa custom control che vale la pena di assimilare, specie se questa pratica è molto diffusa all’interno delle proprie applicazioni.

 

Approfondimenti