Crittografia di ViewState in ASP.NET 2.0

Dino Esposito
Solid Quality Learning

Febbraio 2006

Relativo a:
Microsoft ASP.NET 2.0
Visual Basic 2005
Visual C# 2005
.NET Frameworks
Visual Web Developer 2005

Riassunto: continua la serie di Dino Esposito sullo sviluppo di controlli ASP.NET, che in questo quarto aggiornamento illustra le modalità di utilizzo e creazione di controlli composti (16 pagine stampate).

I codici sorgente Visual Basic e C# sono entrambi disponibili con questo articolo. Per scaricarli, fare clic qui.

Sommario

Introduzione
Vantaggi dell'utilizzo di controlli composti
Scenari comuni per i controlli composti
Motore di rendering dei controlli composti
Utilizzo della classe CompositeControl per risolvere problemi relativi alla fase di progettazione
Creazione di un controllo composto associato a dati
Conclusioni

Introduzione

I controlli composti sono soltanto normali controlli ASP.NET e non un tipo diverso di controlli server ASP.NET. Viene quindi da chiedersi il motivo per cui nei libri e nella documentazione vengono riservate sezioni speciali per tali controlli e quali siano le caratteristiche che li contraddistinguono.

Come si evince dal nome, un controllo composto è un controllo che aggrega vari componenti diversi in una singola posizione e in una singola API. Un controllo personalizzato costituito da un'etichetta e da una casella di testo rappresenta un esempio di controllo composto. L'aggettivo "composto" indica che, internamente, il controllo si ottiene dalla composizione runtime di altri controlli costitutivi. L'insieme di metodi e proprietà esposti da un controllo composto è spesso, ma non necessariamente, determinato dai metodi e dalle proprietà dei controlli che lo costituiscono e da alcuni nuovi membri. Un controllo composto può inoltre generare eventi personalizzati, nonché gestire ed estendere eventi generati da controlli figlio.

La caratteristica che contraddistingue un controllo composto in ASP.NET non è tanto la possibilità di identificarlo quanto il fatto che rappresenta una nuova generazione di controlli server, grazie soprattutto al supporto per il rendering ottenuto dal runtime ASP.NET.

I controlli composti sono uno strumento potente per la creazione di componenti completi e complessi risultante dall'interazione di oggetti attivi anziché dall'output di markup di alcuni oggetti del generatore di stringhe. Il rendering di un controllo composto è una struttura di controlli costitutivi, ognuno con il ciclo di vita e gli eventi corrispondenti, che complessivamente formano una nuova API, con un livello di astrazione variabile in base alle necessità.

In questo articolo verrà descritta l'architettura interna di controlli composti per illustrare i relativi vantaggi in varie situazioni e quindi verrà creato un controllo elenco composto da una gamma di funzionalità più ampia rispetto a quella dei controlli descritti nell'articolo precedente.

Vantaggi dell'utilizzo di controlli composti

Tempo fa mi sono dedicato personalmente all'analisi dei vantaggi derivanti dall'utilizzo di controlli composti in ASP.NET, ne ho studiato la teoria e la pratica nella documentazione MSDN e ho inoltre sviluppato alcuni controlli efficaci. Tuttavia ho compreso effettivamente l'utilità e i vantaggi dei controlli composti solo quando mi sono imbattuto per puro caso nell'esempio seguente, che illustra uno dei modi per ottenere il controllo più semplice e comune possibile grazie alla combinazione di due altri controlli, Label e TextBox. Tale controllo è denominato LabelTextBox.

public class LabelTextBox : WebControl, INamingContainer
{
   public string Text {
      get {
         object o = ViewState["Text"];
         if (o == null)
              return String.Empty;
         return (string) o;
      }
      set { ViewState["Text"] = value; }
   }
   public string Title {
      get {
         object o = ViewState["Title"];
         if (o == null)
              return String.Empty;
         return (string) o;
      }
      set { ViewState["Title"] = value; }
   }
   protected override void CreateChildControls()
   {
      Controls.Clear();
      CreateControlHierarchy();
      ClearChildViewState();
   }
   protected virtual void CreateControlHierarchy()
   {
       TextBox t = new TextBox();
       Label l = new Label();
       t.Text = Text;
       l.Text = Title;
       Controls.Add(l);
       Controls.Add(t);
   }
}

Il controllo include due proprietà pubbliche, Text e Title, nonché il motore di rendering. Le proprietà vengono mantenute nel viewstate e rappresentano rispettivamente il contenuto dei controlli TextBox e Label. Il controllo non include alcun override per il metodo Render e genera il relativo markup tramite il metodo CreateChildControls sottoposto a override. Il funzionamento della fase di rendering è descritto più avanti. Il codice relativo a CreateChildControls cancella innanzitutto l'insieme di controlli figlio e quindi crea la struttura di controlli che formano l'output del controllo corrente. CreateControlHierarchy è un metodo specifico del controllo e non deve essere contrassegnato necessariamente come protetto e virtuale. Si noti tuttavia che i controlli composti nativi, ad esempio DataGrid, espongono solo la logica utilizzata per creare la struttura del controllo tramite un metodo virtuale analogo.

Il metodo CreateControlHierarchy crea un'istanza di tutti i controlli costitutivi necessari e compone l'output finale. Al termine, i controlli vengono aggiunti all'insieme Controls del controllo corrente. Se si prevede che l'output del controllo sarà una tabella HTML, è necessario creare un controllo Table e aggiungere righe e celle con il relativo contenuto appropriato. Tutte le righe, le celle e i controlli contenuti sono elementi figlio della tabella più esterna. In questo caso, viene aggiunto solo il controllo Table all'insieme Controls. Nel codice precedente, Label e TextBox sono elementi figlio diretti del controllo LabelTextBox e vengono aggiunti direttamente all'insieme. Il rendering e il funzionamento del controllo sono ugualmente corretti.

In termini di prestazioni, la creazione di istanze temporanee dei controlli non è efficiente come il rendering di testo normale. Consideriamo un modo alternativo per scrivere lo stesso controllo senza controlli figlio. In questo caso, il controllo è denominato TextBoxLabel.

public class LabelTextBox : WebControl, INamingContainer
{
   :
   protected override void Render(HtmlTextWriter writer)
   {
   string markup = String.Format(
         "<span>{0}</span><input type=text value='{1}'>",
       Title, Text);
      writer.Write(markup);
   }
}

Il controllo include le stesse due proprietà, Text e Title, ed esegue l'override del metodo Render. Come è possibile osservare, l'implementazione è notevolmente più semplice e consente di ottenere un codice leggermente più veloce. In sostituzione ai controlli figlio, viene composto un testo in un generatore di stringhe e si ottiene l'output di markup finale per il browser. Anche in questo caso, il rendering del controllo è corretto. Ma come è possibile verificare se anche il funzionamento è corretto? Nella figura 1 sono illustrati i due controlli in esecuzione in una pagina di esempio.

Figura 1. Controlli simili che utilizzano motori di rendering diversi

Attivare la traccia nella pagina e ripetere l'esecuzione. Quando la pagina viene visualizzata nel browser, scorrere verso il basso ed esaminare la struttura dei controlli. L'aspetto della struttura sarà il seguente:

Figura 2. Strutture generate dai due controlli

Un controllo composto è formato da istanze attive dei controlli costitutivi. Il runtime ASP.NET supporta tali controlli figlio, con i quali può comunicare direttamente per quanto riguarda la gestione dei dati inviati. Di conseguenza, i controlli figlio possono gestire direttamente il viewstate ed estendere automaticamente gli eventi.

La situazione è diversa per un controllo basato sulla composizione di markup. Come illustrato nella figura, il controllo è un'unità atomica di codice con un insieme Controls vuoto. Se il markup inserisce elementi interattivi nella pagina (caselle di testo, pulsanti ed elenchi a discesa), ASP.NET non può gestire dati ed eventi di postback senza interessare il controllo stesso.

Proviamo a inserire un testo in entrambe le caselle di testo e a fare clic sul pulsante di aggiornamento (Refresh) della figura 1 in modo che venga eseguito un postback. Il primo controllo, ovvero il controllo composto, mantiene correttamente il testo assegnato durante il postback. Il secondo controllo, che utilizza il metodo Render, perde il nuovo testo durante i postback. Tale inconveniente è dovuto a due motivi correlati.

Il primo motivo è che nel markup precedente non è stato assegnato un nome al tag di <input>. In questo modo, non verrà eseguito il postback del contenuto. Si noti che è necessario assegnare un nome all'elemento utilizzando l'attributo name. Modifichiamo il metodo Render come indicato di seguito.

protected override void Render(HtmlTextWriter writer)
{
   string markup = String.Format(
      "<span>{0}</span><input type=text value='{1}' name='{2}'>",
      Title, Text, ClientID);
   writer.Write(markup);
}

Ora l'elemento <input> inserito nella pagina relativa al client dispone dello stesso ID del controllo server. Quando viene eseguito il postback della pagina, il runtime ASP.NET può trovare un controllo server corrispondente all'ID del campo inserito ma non è in grado di utilizzarlo. Per consentire ad ASP.NET di applicare tutte le modifiche client al controllo server, è necessario che il controllo implementi l'interfaccia IPostBackDataHandler.

Un controllo composto che include un elemento TextBox non comporta problemi di postback, in quanto il controllo incorporato funzionerà automaticamente con ASP.NET. Un controllo che esegue il rendering di un elemento TextBox deve interagire con ASP.NET per assicurare che i valori inviati vengano gestiti correttamente e che gli eventi vengano generati come previsto. Nel codice seguente viene illustrata la modalità di estensione del controllo TextBoxLabel per consentire il supporto completo dei postback.

bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
    string currentText = Text;
    string postedText = postCollection[postDataKey];
    if (!currentText.Equals(postedText, StringComparison.Ordinal))
    {
        Text = postedText;
        return true;
    }
    return false;
}
void IPostBackDataHandler.RaisePostDataChangedEvent()
{
    return;
}

Scenari comuni per i controlli composti

I controlli composti sono lo strumento appropriato per creare un'architettura di componenti complessi in cui più controlli figlio sono aggregati e interagiscono tra di loro e con il mondo esterno. I controlli di cui è stato eseguito il rendering sono adatti esclusivamente per l'aggregazione di controlli di sola lettura in cui l'output non include elementi interattivi quali elenchi a discesa o caselle di testo.

Se si è interessati alla gestione di eventi e ai dati inviati, è consigliabile optare per i controlli composti. Se si utilizzano controlli figlio, la creazione di una struttura di controlli complessa sarà più semplice e il risultato finale sarà più accurato ed elegante. Inoltre è necessario utilizzare interfacce di postback solo per aggiungere ulteriori funzionalità.

I controlli di cui è stato eseguito il rendering richiedono l'implementazione di interfacce aggiuntive, nonché accurate operazioni per unire porzioni statiche di markup con i valori delle proprietà.

I controlli composti sono inoltre estremamente utili per il rendering di un elenco di elementi omogenei, come in un controllo DataGrid. Il fatto di avere a disposizione ogni elemento costitutivo come oggetto attivo consente di generare eventi di creazione e di accedere alle relative proprietà a livello di programmazione. In ASP.NET 2.0, la maggior parte del codice boilerplate necessario per un'implementazione completa di controlli composti realistici e associati a dati (i controlli precedenti sono soltanto esempi fittizi) è nascosto all'interno di una nuova classe base: CompositeDataBoundControl.

Motore di rendering dei controlli composti

Prima di illustrare in dettaglio le tecniche di codifica di ASP.NET 2.0, è necessario esaminare il funzionamento interno dei controlli composti. Come già menzionato, il rendering di un controllo composto è basato sul metodo CreateChildControls ereditato dalla classe base Control. Si potrebbe pensare che l'override del metodo Render sia essenziale per il rendering del contenuto di un controllo server. Come illustrato in precedenza, tale operazione non è sempre necessaria se viene eseguito l'override del metodo CreateChildControls. Ma cosa accade quando il metodo CreateChildControls viene richiamato nello stack di chiamate del controllo?

La prima volta che viene visualizzata la pagina, il metodo CreateChildControls viene richiamato durante la fase pre-rendering, come illustrato nella figura.

Figura 3. Metodo CreateChildControls richiamato durante la fase pre-rendering

In particolare, il codice di elaborazione delle richieste nella classe Page chiama il metodo EnsureChildControls immediatamente prima di generare l'evento PreRender per la pagina e ogni controllo figlio. In altre parole, non viene eseguito il rendering di alcun controllo se la creazione della struttura non è stata completata.

Nel frammento di codice seguente viene illustrato lo pseudo-codice per EnsureChildControls, un altro metodo definito nella classe Control.

protected virtual void EnsureChildControls()
{
   if (!ChildControlsCreated)
   {
       try {
          CreateChildControls();
       }
       finally {
          ChildControlsCreated = true;
       }
   }
}

Il metodo può essere richiamato ripetutamente durante il ciclo di vita di pagine e controlli. Per impedire la duplicazione dei controlli, la proprietà ChildControlsCreated è impostata su true. Se la proprietà restituisce true, il metodo viene chiuso immediatamente.

Quando viene eseguito il postback della pagina, il metodo ChildControlsCreated viene richiamato in una fase precedente del ciclo. Come illustrato nella figura 4, il metodo viene chiamato durante la fase di elaborazione dei dati inviati.

Figura 4. Metodo richiamato durante la fase di elaborazione dei dati inviati in caso di postback

Quando la pagina ASP.NET inizia l'elaborazione dei dati inviati dal client, tenta di trovare un controllo server con ID corrispondente al nome del campo inserito. A tale scopo, il codice della pagina richiama il metodo FindControl nella classe Control. Prima di procedere, questo metodo deve verificare a sua volta che la struttura del controllo sia stata completata. Pertanto richiama il metodo EnsureChildControls e, se necessario, crea la gerarchia del controllo.

Esaminiamo ora il codice da eseguire all'interno del metodo CreateChildControls. Sebbene non siano disponibili linee guida ufficiali da seguire, la prassi comune prevede che il metodo CreateChildControls debba eseguire almeno le operazioni seguenti: cancellare l'insieme Controls, creare la struttura del controllo e cancellare il viewstate dei controlli figlio. L'impostazione della proprietà ChildControlsCreated dal metodo CreateChildControls non è obbligatoria. La struttura delle pagine ASP.NET, infatti, richiama sempre il metodo CreateChildControls tramite EnsureChildControls, che imposta automaticamente il flag booleano.

Utilizzo della classe CompositeControl per risolvere problemi relativi alla fase di progettazione

ASP.NET 2.0 viene fornito con una classe base denominata CompositeControl. Per tale motivo, i nuovi controlli composti non associati a dati devono essere derivati da questa classe anziché da WebControl. L'utilizzo della classe CompositeControl non comporta sostanziali modifiche alla modalità di sviluppo del controllo. È comunque necessario eseguire l'override del metodo CreateChildControls ed eseguire la codifica come illustrato in precedenza. Per comprendere il ruolo della classe CompositeControl, iniziamo dall'analisi del relativo prototipo:

public class CompositeControl : WebControl, 
                                INamingContainer, 
                                ICompositeControlDesignerAccessor

Grazie alla classe, non è necessario decorare il controllo con INamingContainer (anche se in effetti tale vantaggio non è poi così significativo, dato che l'interfaccia è solo un indicatore e non include metodi). Lo scopo principale della classe è tuttavia quello di implementare una nuova interfaccia denominata ICompositeControlDesignerAccessor.

public interface ICompositeControlDesignerAccessor
{
   void RecreateChildControls();
}

L'interfaccia viene utilizzata dalla finestra di progettazione standard dei controlli composti per ricreare la struttura dei controlli in fase di progettazione. Di seguito viene illustrata l'implementazione predefinita del metodo nella classe CompositeControl.

void ICompositeControlDesignerAccessor.RecreateChildControls()
{
   base.ChildControlsCreated = false;
   EnsureChildControls();
}

In breve, se un controllo composto viene derivato dalla classe CompositeControl, non si riscontreranno problemi relativi alla fase di progettazione e non sarà necessario ricorrere a espedienti vari per garantire il corretto funzionamento del controllo sia in fase di esecuzione che in fase di progettazione.

Per comprendere pienamente l'importanza di questa interfaccia, è necessario esaminare la pagina di esempio che ospita un controllo composto LabelTextBox e quindi attivare la modalità progettazione. Il controllo funziona correttamente in fase di esecuzione, ma è invisibile in fase di progettazione.

Figura 5. Problemi relativi alla fase di progettazione dei controlli composti, ad eccezione di quelli derivati dalla classe CompositeControl

Se si sostituisce semplicemente la classe WebControl con CompositeControl, il controllo continuerà a funzionare correttamente in fase di esecuzione ma funzionerà perfettamente anche in fase di progettazione.

Figura 6. Controlli composti che funzionano correttamente in fase di progettazione

Creazione di un controllo composto associato a dati

La maggior parte dei controlli server complessi è associata a dati, eventualmente basata su modelli e formata da una vasta gamma di controlli figlio. Tali controlli mantengono un elenco di elementi costitutivi, in genere righe o celle di una tabella. L'elenco viene mantenuto durante i postback nel viewstate e viene creato dai dati associati o generato nuovamente dal viewstate. Il controllo salva inoltre nel viewstate il relativo numero di elementi costitutivi in modo che sia possibile ricreare correttamente la struttura della tabella in caso di postback causato da altri controlli presenti nella pagina. Per chiarire il concetto, esaminiamo un controllo DataGrid.

Il controllo DataGrid è costituito da un elenco di righe, ognuna delle quali rappresenta un record nell'origine dati associata. Ogni riga della griglia è rappresentata da una classe oggetto DataGridRow derivata da TableRow. Quando le singole righe della griglia vengono create e aggiunte alla tabella della griglia finale, nella pagina vengono generati eventi appropriati quali ItemCreated e ItemDataBound. Se il controllo DataGrid viene creato tramite associazione dati, il numero di righe è determinato dal numero di elementi associati e dalle dimensioni della pagina. Cosa accade se viene eseguito il postback della pagina con il controllo DataGrid?

In tal caso, se il postback è causato dallo stesso controllo DataGrid, ad esempio l'utente ha fatto clic per ordinare o visualizzare la pagina, la nuova pagina eseguirà nuovamente il rendering del controllo DataGrid tramite l'associazione dati. Tale comportamento è ovvio, in quanto il controllo DataGrid deve visualizzare dati aggiornati. La situazione è diversa se il postback della pagina di destinazione viene eseguito perché è stato selezionato un altro controllo della pagina, ad esempio un pulsante. In tal caso, il controllo DataGrid non è associato a dati ed è necessario ricrearlo dal viewstate. Se il viewstate è disattivato, lo scenario cambia e sarà possibile visualizzare la griglia solo tramite l'associazione dati.

L'origine dati non viene mantenuta nel viewstate. Come ogni controllo composto, DataGrid contiene controlli figlio, ognuno dei quali viene mantenuto nel relativo stato originario all'interno del viewstate e viene ripristinato da tale posizione. Il controllo DataGrid deve soltanto tenere traccia del numero di iterazioni necessarie affinché tutte le righe e i controlli contenuti vengano ripristinati dal viewstate. Tale numero coincide con il numero di elementi associati visualizzati e deve essere memorizzato nel viewstate tra le informazioni relative allo stato del controllo. In ASP.NET 1.x, è necessario apprendere e implementare manualmente questo schema. In ASP.NET 2.0, è sufficiente derivare il controllo composto dalla nuova classe CompositeDataBoundControl.

Proviamo a utilizzare un controllo griglia che visualizza titoli di notizie espandibili associati a dati. A tale scopo, verrà riutilizzato il controllo Headline descritto in articoli precedenti.

public class HeadlineListEx : CompositeDataBoundControl
{
  :
}

Il controllo HeadlineListEx conteggia una proprietà dell'insieme Items in cui sono raccolti tutti gli elementi associati a dati. L'insieme è pubblico ed è inoltre possibile popolarlo a livello di programmazione, come accade per la maggior parte dei controlli elenco. Il supporto per l'associazione dati classica viene implementato tramite la coppia di proprietà DataTextField e DataTitleField. Tali proprietà indicano i campi dell'origine dati che verranno utilizzati per popolare titolo e testo delle notizie. L'insieme Items viene mantenuto nel viewstate.

Per trasformare il controllo HeadlineListEx in un vero controllo composto, è necessario innanzitutto derivarlo da CompositeDataBoundControl e quindi eseguire l'override di CreateChildControls. È interessante notare che CreateChildControls è un metodo di overload.

override int CreateChildControls()
override int CreateChildControls(IEnumerable data, bool dataBinding)

Il primo overload esegue l'override del metodo definito nella classe Control. Il secondo overload è un metodo astratto di cui ogni controllo composto deve eseguire l'override. In pratica, lo sviluppo di un controllo composto viene ridotto a due operazioni principali:

  • Override del metodo CreateChildControls
  • Implementazione di una proprietà dell'insieme Rows per tenere traccia di tutti gli elementi costitutivi del controllo

La proprietà Rows è diversa da Items in quanto non viene mantenuta nel viewstate, ha la stessa durata della richiesta e fa riferimento a oggetti helper, non a elementi associati a dati.

public virtual HeadlineRowCollection Rows
{
    get
    {
        if (_rows == null)
            _rows = new HeadlineRowCollection();
         return _rows;
     }
}

L'insieme Rows viene popolato quando viene creato il controllo. Esaminiamo ora l'override del metodo CreateChildControls. Il metodo accetta due argomenti: gli elementi associati e un flag booleano che indica se il controllo verrà creato tramite associazione dati o viewstate.

override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
   if (dataBinding)
   {
      string textField = DataTextField;
      string titleField = DataTitleField;
      if (dataSource != null)
      {
         foreach (object o in dataSource)
         {
            HeadlineItem elem = new HeadlineItem();
            elem.Text = DataBinder.GetPropertyValue(o, textField, null);
            elem.Title = DataBinder.GetPropertyValue(o, titleField, null);
            Items.Add(elem);
         }
      }
   } 

   // Start building the hierarchy of controls
   Table t = new Table();
   Controls.Add(t);
   Rows.Clear();
   int itemCount = 0;

   foreach(HeadlineItem item in Items)
   {
       HeadlineRowType type = HeadlineRowType.Simple;
       HeadlineRow row = CreateHeadlineRow(t, type, 
                                           item, itemCount, dataBinding);
       _rows.Add(row);
       itemCount++;
    }

    return itemCount;
}

Nel caso dell'associazione dati, viene popolato innanzitutto l'insieme Items. Si esegue un ciclo nell'insieme associato, si estraggono i dati e si compilano le nuove istanze della classe HeadlineItem. Successivamente, si esegue un ciclo nell'insieme Items, che potrebbe contenere ulteriori elementi aggiunti a livello di programmazione, e si creano righe nel controllo.

HeadlineRow CreateHeadlineRow(Table t, HeadlineRowType rowType, 
                      HeadlineItem dataItem, int index, bool dataBinding)
{
   // Create a new row for the outermost table
   HeadlineRow row = new HeadlineRow(rowType);

   // Create the cell for a child control
   TableCell cell = new TableCell();
   row.Cells.Add(cell);
   Headline item = new Headline();
   cell.Controls.Add(item);

   // Fire HERE a HeadlineRowCreated event 

   // Add the row to the HTML table being created
   t.Rows.Add(row);

   // Handle the data object binding
   if (dataBinding)
   {
       row.DataItem = dataItem;
       Headline ctl = (Headline) cell.Controls[0];
       ctl.Text = dataItem.Text;
       ctl.Title = dataItem.Title;
                
       // Fire HERE a HeadlineRowDataBound event 
    }
    return row;
}

Il metodo CreateHeadlineRow crea e restituisce un'istanza della classe HeadlineRow derivata da TableRow. In tal caso, la riga contiene una cella compilata con un controllo Headline. In altre situazioni, è possibile modificare questa parte del codice per aggiungere il numero di celle necessarie e compilarle nel modo appropriato.

È importante dividere le operazioni da eseguire in due fasi distinte: creazione e associazione dati. Si crea innanzitutto il layout della riga, si genera l'evento creato dalla riga, se disponibile, e infine lo si aggiunge alla tabella padre. Successivamente, se il controllo verrà associato a dati, si impostano le proprietà dei controlli figlio sensibili ai dati associati. Al termine, si genera un evento associato a dati della riga, se disponibile.

Si noti che questo è lo schema che descrive nel modo migliore l'architettura interna di controlli composti nativi ASP.NET.

Per generare eventi, è possibile utilizzare il codice seguente.

HeadlineRowEventArgs e = new HeadlineRowEventArgs();
e.DataItem = dataItem;
e.RowIndex = index;
e.RowType = rowType;
e.Item = row;
OnHeadlineRowDataBound(e);

Si noti che la proprietà DataItem viene impostata solo se si genera l'evento associato a dati. La struttura di dati dell'evento è impostata arbitrariamente sul valore indicato di seguito. Se necessario, è possibile modificarlo.

public class HeadlineRowEventArgs : EventArgs
{
   public HeadlineItem DataItem;
   public HeadlineRowType RowType;
   public int RowIndex;
   public HeadlineRow Item;
}

Per generare fisicamente un evento, di norma viene utilizzato un metodo protetto, definito nel modo illustrato di seguito.

  protected virtual void OnHeadlineRowDataBound(HeadlineRowEventArgs e)
  {
  if (HeadlineRowDataBound != null)
  HeadlineRowDataBound(this, e);
  }

Per dichiarare l'evento, in ASP.NET 2.0 è possibile utilizzare il nuovo delegato del gestore eventi generico.

public event EventHandler<HeadlineRowEventArgs> HeadlineRowDataBound;

In una pagina di esempio, viene utilizzata la procedura standard. Si definisce il gestore nel markup del controllo e si scrive un metodo nel file di codice. Di seguito viene indicato un esempio.

<cc1:HeadlineListEx runat="server" ID="HeadlineListEx1" 
    DataTextField="notes" DataTitleField="lastname" 
    DataSourceID="MySource" OnHeadlineRowDataBound="HeadlineRowCreated" />

Il codice per il gestore eventi HeadlineRowCreated è il seguente.

protected void HeadlineRowCreated(object sender, HeadlineRowEventArgs e)
{
   if (e.DataItem.Title.Contains("Doe"))
      e.Item.BackColor = Color.Red;
}

Figura 7. Controllo HeadlineListEx in azione

Se si collega l'evento associato a dati, viene utilizzato uno sfondo rosso per tutti gli elementi che contengono Doe.

Conclusioni

I controlli composti sono controlli creati tramite l'aggregazione di altri controlli all'interno di un'API comune. Un controllo composto mantiene le istanze attive dei relativi controlli figlio e non si limita a richiederne il rendering. È possibile notarlo facilmente esaminando la sezione relativa alla struttura dei controlli nell'output di traccia di una pagina. L'utilizzo di controlli composti comporta alcuni vantaggi, ad esempio la gestione semplificata di eventi e postback. In ASP.NET 1.x, la creazione di un controllo complesso associato a dati risulta difficile e richiede una conoscenza approfondita di alcuni dettagli di implementazione. Tale operazione viene tuttavia parzialmente semplificata in ASP.NET grazie all'introduzione della classe base CompositeDataBoundControl. In ultima analisi, se è necessario un controllo composto non associato a dati, in ASP.NET 2.0 viene utilizzata la classe base CompositeControl. Per un controllo composto associato a dati, si ricorre invece alla classe CompositeDataBoundControl. In entrambi i casi, è necessario eseguire un override valido del metodo CreateChildControls, che rappresenta la parte fondamentale di qualsiasi controllo composto, dove viene creata la gerarchia di controlli figlio.

 

Informazioni sull'autore

Dino Esposito è un mentore presso Solid Quality Learning e l'autore di "Programming Microsoft ASP.NET 2.0" (Microsoft Press, 2005). Dino, che vive in Italia, partecipa spesso come relatore a eventi del settore in tutto il mondo. Per contattarlo, è disponibile l'indirizzo cutting@microsoft.com o il blog http://weblogs.asp.net/despos.

Mostra: