Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Chiamate veloci al server

Evitare il postback usando il Client Callback Manager di ASP.NET 2.0
Evitare il postback usando il Client Callback Manager di ASP.NET 2.0
Link copiato negli appunti

Quando pensiamo ad una applicazione web non possiamo non tener conto dei problemi legati allo scambio di informazioni tra client e server. Possiamo cercare di rendere il più snello possibile questo flusso di dati evitando ad esempio un numero eccessivo di postback.

È anche a partire da considerazioni come queste che si sono sviluppate librerie AJAX come ATLAS per ASP.NET

Con ASP.NET 1.x, quindi, per superare il problema derivante dai continui postback, si impiega l'oggetto Microsoft XMLHTTP ActiveX che serve ad inviare richieste ai metodi lato server, da funzioni Javascript lato client. In ASP.NET 2.0 questo processo è stato semplificato con l'introduzione del Client Callback Manager.

Il Client Callback Manager di ASP.NET 2.0 fornisce la possibilità di invocare una funzione o un metodo lato server di una pagina web senza effettuare l'aggiornamento del browser. Affinché funzioni il Client Callback Manager, il browser naturalmente deve supportare l'XMLHTTP.

Il Client Callback Manager può quindi essere usato per aggiornare singoli controlli o per la validazione o anche per inviare i dati di un form senza inviare tutta la pagina. Il callback avviene attraverso script Javascript che inviano e ricevono dati sotto forma di flussi XML.

Quindi un evento che si verifica sul client, gestito da uno script presente sul client, può inviare una richiesta di processamento al server in modo asincrono, senza causare un postback.

Il server possiede alcuni metodi che ricevono la richiesta del client, la processano ed inviano la risposta. Infine, lato client, viene ricevuta ed elaborata la risposta del server.

La comunicazione fra le due parti avviene attraverso una stringa che contiene sia il comando da eseguire, sia i dati da inviare o ricevere.

Implementare il Client Callback Manager

Per usufruire del Client Callback Manager dobbiamo implementare l'interfaccia ICallBackEventHandler nella nostra pagina. Il che significa aggiungere l'interfaccia alla dichiarazione della classe.

Implementare l'interfaccia ICallbackEventHandler

public partial class _Default : System.Web.UI.Page, ICallbackEventHandler

A questo che dobbiamo realizzare concretamente i metodi dell'interfaccia:

  • RaiseCallbackEvent
  • GetCallbackResult

RaiseCallbackEvent() è invocato quando il client invia una richiesta al server. Questo metodo si occupa di leggere ed interpretare la stringa inviata dal client per poi eseguirne il comando con i dati inviati.

GetCallbackResult() è invocato dopo RaiseCallbackEvent() e restituisce la stringa da inviare al client come risposta della richiesta effettua.

Abbiamo poi bisogno di una stringa, che possiamo chiamare 'callbackStr', da utilizzare lato client e che conterrà il codice Javascript per lo scambio dei dati con il server. Questa stringa deve essere dichiarata pubblica e valorizzata con il metodo GetCallbackEventReference della classe ClientScriptManager.

La firma completa del metodo è la seguente:

GetCallbackEventReference(Control control, string argument, string clientCallback, string context, string errorCallback, Boolean async).

I parametri di GetCallbackEventReference sono i seguenti:

  1. Control control è il controllo che implementa il RaiseCallbackEvent. Se il metodo è implementato nella pagina, il riferimento sarà this;
  2. string argument è un valore che viene inviato al RaiseCallbackEvent attraverso il parametro eventArgument;
  3. string clientCallback è il nome del gestore di eventi lato client che riceve il risultato inviato dal server;
  4. string context è un valore che identifica chi ha iniziato il callback;
  5. string errorCallback è il nome del gestore di errore sul client che riceve la risposta di errore dal server se questa si verifica;
  6. Boolean async è un valore booleano che specifica se i callback vanno eseguiti in modo asincrono.

Il metodo GetCallbackEventReference quindi serve a costruire il metodo Javascript lato client WebForm_DoCallback che viene utilizzato dai gestori di eventi sul client per effettura le chiamate al server.

Il Client Callback Manager raccontato a parole sembra più complicato di quello che in effetti è. Con l'esempio ci chiariremo meglio le idee e avremo modo di apprezzare questa tecnica.

L'esempio

Realizziamo un esempio per comprendere meglio il funzionamento di questo meccanismo. Creiamo un piccolo form per la ricerca di una località attraverso filtri successivi. Facciamo selezionare all'utente prima una nazione, poi una regione e finalmente una città.

Le selezioni avvengono con delle liste a discesa (DropDownList). Normalmente ad ogni selezione su un certo livello del filtro segue un postback col quale viene richiesta la lista delle opzioni per il livello successivo. Vediamo come evitare di dover ricaricare la pagina ogni volta ed ottenere lo stesso risultato.

Realizzazione con postback

Costruiamo una semplice tabella di database come quella mostrata in figura:

Figura 1. Database di esempio
Database di esempio

Il database di esempio rappresenta, in modo molto semplificato, le informazioni sugli appartamenti che una società immobiliare ha a disposizione per la locazione e che vuole fornire ai clienti, che si collegano al sito.

Vogliamo realizzare una pagina in cui l'utente possa scegliere la nazione fra quelle disponibili. In base a questa scelta avrà una lista di regioni disponibili ed in base alla regione scelta, avrà una lista di città. Scegliendo la città verrà visualizzata una tabellina con gli appartamenti a disposizione.

Realizzare una simile pagina con i controlli che ASP.NET 2.0 ci mette a disposizione è molto semplice. Potremmo utilizzare tre: una DropDownList, un GridView e quattro DataSource:

Figura 2. Realizzazione con postback
Realizzazione con postback

Con una simile realizzazione però, ad ogni scelta avviene un postback, cioè tutta la pagina viene inviata al server che la processa e la riinvia al client.

Per verificare che avviene il postback possiamo mettere una write all'interno del Page_Load oppure semplicemente notare che la barra di avanzamento del browser si mette in azione:

Figura 3. Barra di avanzamento durante il postback
Barra di avanzamento durante il postback

Oltre a questo, la sequenza di andata e ritorno dal server è lenta perché si porta dietro molte informazioni che, nel nostro caso, non sarebbero necessarie.

Nella prossima puntata vedremo come realizzare l'esempio con il Client Callback Manager.

Realizzazione con Client Callback Manager

Nella parte precedente dell'articolo abbiamo realizzato il nostro esempio usando la tecnica classica del postback ora realizziamo la stessa pagina facendo uso del Client Callback Manager.

Posizioniamo sulla pagina le tre DropDownList che riempiremo dinamicamente. La tabellina invece verrà costruita sul client con i dati inviati dal server.

Listato 1. Le DropDownList sul codice sorgente della Default.aspx

...
<table>
<tr>
 <td style=""> Scegli la nazione:</td>
 <td style=""> Scegli la regione:</td>
</tr>
<tr>
 <td style="">

  <asp:DropDownList ID="ddl1Nazione" runat="server">
  <asp:ListItem Selected="True">-- Nazione --</asp:ListItem>
  </asp:DropDownList>

 </td>
 <td style="">

  <asp:DropDownList ID="ddl2Regione" runat="server">
  <asp:ListItem Selected="True">-- Regione --</asp:ListItem>
  </asp:DropDownList>

 </td>
</tr>
<tr>
 <td style=""> Scegli la città:</td>
 <td style=""></td>
</tr>
<tr>
 <td style="vertical-align: top; ">

  <asp:DropDownList ID="ddl3Città" runat="server">
   <asp:ListItem Selected="True">-- Città --</asp:ListItem>
  </asp:DropDownList>

 </td>
 <td style="">
  <div id="aggiungiQui"></div>
 </td>
</tr>
</table>
...

Nel listato 1 possiamo notare la presenza di un tag <div> con id="aggiungiQui" che ci servirà in seguito per posizionare la tabellina che costruiremo sul client.

Per riempire la prima DropDownList, la ddl1Nazione, in modo dinamico, basta fare una lettura sul database Immobiliare.MDF e scrivere le nazioni contenute nell'unica tabella presente, la tabella Appartamenti. Quindi inseriremo nella Page_Load una chiamata al metodo Imposta_ddl1Nazione():

Listato 2. Metodo Imposta_ddl1Nazione()

protected void Imposta_ddl1Nazione()
{
 SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["AppConnStr"].ConnectionString);
 SqlDataAdapter adapter = new SqlDataAdapter("SELECT DISTINCT [Nazione] FROM [Appartamenti]", conn);
 DataSet ds = new DataSet();
 adapter.Fill(ds, "Appartamenti");

 foreach (DataRow row in ds.Tables["Appartamenti"].Rows)
  ddl1Nazione.Items.Add(row["Nazione"].ToString());
}

Il metodo Imposta_ddl1Nazione() esegue una semplice SELECT DISTINCT della colonna Nazione nella tabella Appartamenti e, per ogni nazione trovata, aggiunge un elemento a ddl1Nazione.

Abbiamo la lista con i nomi delle nazioni. Quando l'utente ne sceglie una, la seconda DropDownList deve essere popolata con le regioni, presenti sul database, che appartengono a quella nazione.

Vogliamo però fare questo evitando il postback, perché non serve ricaricare tutta la pagina per quelle poche informazioni che ci servono.

Abbiamo detto che la pagina che utilizza il Client Callback deve implementare l'interfaccia ICallbackEventHandler, quindi nella parte codice interno della pagina, scriviamo le tre righe riportate nel listato 3, dove all'interno della classe parziale _Default abbiamo definito la stringa pubblica callbackStr, di cui abbiamo già parlato, ed una stringa privata _result che ci servirà per passare i risultati al client.

Listato 3. Implementazione dell'interfaccia ICallbackEventHandler

public partial class _Default : System.Web.UI.Page, ICallbackEventHandler
{
 public string callbackStr;
 private string _result;
 ...

Nel Page_Load, abbiamo già posizionato la chiamata al metodo per leggere le nazioni. Mettiamo poi un controllo sulle capacità del browser con cui abbiamo a che fare. Se il browser non supporta il Client Callback lanciamo un'eccezione.

Siccome poi le nostre DropDownList sono gestite dal client, aggiungiamo ad ognuna di esse un attributo che specifica la funzione lato client che gestisce l'evento onChange.

Listato 4. Implementazione del Page_Load

protected void Page_Load(object sender, EventArgs e)
{
 Imposta_ddl1Nazione();
 if (!Request.Browser.SupportsCallback)
  throw new ApplicationException("Il browser non supporta il Client Callback.");
 
 ddl1Nazione.Attributes.Add("onChange", "LeggiRegioneDalServer()");
 ddl2Regione.Attributes.Add("onChange", "LeggiCittàDalServer()");
 ddl3Città.Attributes.Add("onChange", "LeggiAppartamentiDalServer()");
 
 callbackStr = Page.ClientScript.GetCallbackEventReference(this, "Command", "CallBackHandler", "context", "onError", false);
}

Le funzioni lato client che abbiamo specificato nel listato 4 andranno poi implementate in Javascript, compresa la CallBackHandler ed onError presenti come parametri nel GetCallbackEventReference.

Passiamo quindi sulla pagina, la Default.aspx, ed implementiamo la prima funzione LeggiRegioneDalServer().

Listato 5. Implementazione di LeggiRegioneDalServer()

<script language="javascript" type="text/javascript">
function LeggiRegioneDalServer()
{
 var Command = "1:" + document.forms[0].elements['ddl1Nazione'].value;
 var context = new Object();
 context.CommandName = "LeggiRegioneDaNazione";
 <%=callbackStr %>
}

LeggiRegioneDalServer()costruisce la variabile Command che abbiamo utilizzato nel listato 4, come parametro del GetCallbackEventReference, mettendo un "1:" all'inizio della stringa in modo che il metodo sul server possa capire che si tratta di questo comando, e poi il valore di ddl1Nazione selezionato dall'utente.

Definisce poi l'oggetto context a cui associa il CommandName "LeggiRegioneDaNazione". Anche context è un parametro del GetCallbackEventReference.

L'ultima riga è un'istruzione lato server che inserisce in quel punto il valore della stringa callbackStr, che ricordiamo contiene il valore impostato con il GetCallbackEventReference, cioè la funzione WebForm_DoCallback('__Page', Command, CallBackHandler, context, onError, false).

Passiamo ora alla parte server, cioè sul codice interno della pagina, ed implementiamo il metodo RaiseCallbackEvent.

Listato 6. Implementazione di RaiseCallbackEvent

public void RaiseCallbackEvent(string eventArgument)
{

 // se il comando arriva dal primo controllo
 // dobbiamo riempire il secondo

 if (eventArgument.StartsWith("1:"))
 {

  // imposto il primo item

  _result = "-- Regione --;";

  // prendo la stringa dalla posizione 3

  eventArgument = eventArgument.Substring(2);
  if (eventArgument != "-- Nazione --")
  {

   // leggo il database

   SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["AppConnStr"].ConnectionString);
   SqlDataAdapter adapter = new SqlDataAdapter("SELECT DISTINCT [Regione] FROM [Appartamenti] WHERE ([Nazione] = @Nazione)", conn);
   DataSet ds = new DataSet();
   adapter.SelectCommand.Parameters.Add("@Nazione", SqlDbType.NChar, 10).Value = eventArgument;
   adapter.Fill(ds, "Appartamenti");

   // costruisco la stringa da restituire

   foreach (DataRow row in ds.Tables["Appartamenti"].Rows)
    _result += row["Regione"].ToString() + ";";
  }
 }
...

RaiseCallbackEvent controlla i primi due caratteri del parametro eventArgument per capire di che cosa si tratta. Poi prende la stringa che comincia dalla posizione tre, che contiene il valore della nazione, con cui dobbiamo interrogare il database, ed effettuata la query. Ricevuti i valori, costruisce la stringa da restituire al client, che contiene i nomi delle regioni separati dal punto e virgola.

Per restituire la stringa _result al client ci pensa il metodo GetCallbackResult che dobbiamo implementare per rispettare l'interfaccia ICallbackEventHandler.

Listato 7. Implementazione di GetCallbackResult

public string GetCallbackResult()
{
 if (_result == "error")
  throw new Exception("Selezione non valida!");
 else
  return _result;
}

GetCallbackResult lancia un'eccezione se si trova di fronte ad un errore, altrimenti restituisce la stringa.

Torniamo ora sulla parte client, dove dobbiamo implementare, in Javascript, una funzione che decodifichi la stringa che arriva dal server ed esegua le azioni richieste. La funzione si deve chiamare CallBackHandler come abbiamo impostato sul metodo GetCallbackEventReference.

Listato 8. Implementazione di CallBackHandler

function CallBackHandler(result, context)
{
 if (context.CommandName == "LeggiRegioneDaNazione")
 {
  document.forms[0].elements['ddl2Regione'].options.length = 0;
  while (result.length > 0)
  {
   var indexofSep = result.indexOf(";");
   var itemList = result.substring(0, indexofSep);
   result = result.substring(indexofSep + 1);
   document.forms[0].elements['ddl2Regione'].options.add(new Option(itemList, itemList));
  }
  document.forms[0].elements['ddl3Città'].options.length = 0;
  document.forms[0].elements['ddl3Città'].options.add(new Option("-- Città --", "-- Città --"));
  rimuoviRabellaRisultati();
 }
...

CallBackHandler controlla il CommandName da cui è partita la richiesta client-server, quindi sa che sta ricevendo una risposta per la richiesta "LeggiRegioneDaNazione".

Decodifica poi la stringa che, come abbiamo visto, contiene valori separati dal punto e virgola, e, per ogni valore ricevuto, aggiunge dinamicamente un item con quel valore a ddl2Regione.

Il listato 8 contiene altri dettagli, che sono di facile comprensione avendo una visione generale dell'applicazione.

Dobbiamo inoltre implementare la funzione di errore sul client che gestirà le eccezioni lanciate dal server.

Listato 9. Implementazione della funzione di errore sul client

function onError(message, context)
{
 alert("Exception :n" + message);
}

È ovvio che il nome onError deve coincidere con il parametro di errore del GetCallbackEventReference.

Allo stesso modo di LeggiRegioneDalServer() possiamo implementare LeggiCittàDalServer().

L'ultima lettura che dobbiamo fare sul server è quella degli appartamenti avendo selezionato la città. Poi con i dati restituiti dobbiamo costruire dinamicamente sul client una tabellina.

Sul RaiseCallbackEvent dobbiamo aggiungere questa possibilità: se il comando della richiesta è il numero 3, preleviamo il nome della città ed interroghiamo il database. Costruiamo poi la stringa da restituire con i campi letti a coppie di due, ma sempre separati dal punto e virgola.

Listato 10. Implementazione sul RaiseCallbackEvent

...
else if (eventArgument.StartsWith("3:"))
{

 // prendo la stringa dalla posizione 3

 eventArgument = eventArgument.Substring(2);
 if (eventArgument != "-- Città --")
 {

  // leggo il database

  SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["AppConnStr"].ConnectionString);
  SqlDataAdapter adapter = new SqlDataAdapter("SELECT [Locali], CAST([Prezzo in Euro] AS decimal(8, 2)) AS Prezzo FROM [Appartamenti] WHERE [Città] = @Città", conn);
  DataSet ds = new DataSet();
  adapter.SelectCommand.Parameters.Add("@Città", SqlDbType.NChar, 10).Value = eventArgument;
  adapter.Fill(ds, "Appartamenti");

  // costruisco la stringa da restituire

  foreach (DataRow row in ds.Tables["Appartamenti"].Rows)
   _result += row["Locali"].ToString() + ";" + "€ " + row["Prezzo"].ToString() + ";";
 }
}
...

Quindi sul client dovremmo implementare all'interno del CallBackHandler il seguente pezzo di codice in cui verifichiamo il comando ed eliminiamo la tabella vecchia con rimuoviRabellaRisultati(). Se ci sono risultati inizializziamo una nuova tabella con la funzione aggiungiTabellaRisultati(). Il resto del codice serve a costruire la tabella con la stringa che ci ritorna dal server, tenendo presente che i campi sulla stringa sono a coppie di due e tutti separati dal punto e virgola.

Listato 11. Implementazione sul CallBackHandler

...
if (context.CommandName == "LeggiAppartamenti")
{
 rimuoviRabellaRisultati();
 if (result.length > 0)
 {
  aggiungiTabellaRisultati();
  var tabella = document.getElementById("tabellaRisultati");
  var i = 0; var j = 0;
 }
 while (result.length > 0)
 {
  var indexofSep = result.indexOf(";");
  var itemList = result.substring(0, indexofSep);
  result = result.substring(indexofSep + 1);
  if (j == 0)
   tabella.insertRow(i + 1);

  tabella.rows[i + 1].insertCell(j);
  tabella.rows[i + 1].cells[j].innerHTML = itemList;

  if (j < 1)
   j = j + 1;
  else
  {
   i = i + 1;
   j = 0;
  }
 }
}
...

Per completezza riportiamo i listati delle funzioni Javascript per inizializzare la tabella e per cancellarla.

Listato 12. Implementazione di aggiungiTabellaRisultati() sul client

function aggiungiTabellaRisultati()
{
 var theTable = document.createElement("table");
 theTable.setAttribute("id", "tabellaRisultati");
 theTable.setAttribute("border", "2");
 theTable.style.cssText = "font-weight: bold; text-align: center; background-color: whitesmoke;";
 theTable.insertRow(0);
 theTable.rows[0].insertCell(0); theTable.rows[0].insertCell(1);
 theTable.rows[0].cells[0].innerHTML = "Locali";
 theTable.rows[0].cells[1].innerHTML = "Prezzo";
 theTable.rows[0].style.cssText = "background-color: darkblue; color: white;";
 theTable.rows[0].cells[0].style.cssText = "padding-left: 20px; padding-right: 20px;";
 theTable.rows[0].cells[1].style.cssText = "padding-left: 20px; padding-right: 20px;";
 var insertSpot = document.getElementById("aggiungiQui");
 insertSpot.appendChild(theTable);
}

Nel listato 12 notiamo che la tabella viene aggiunta all'interno del tag con id aggiungiQui che avevamo visto all'inizio. Sono inoltre presenti delle istruzioni per impostare gli stili css per la tabella.

Listato 13. Implementazione di rimuoviRabellaRisultati() sul client

function rimuoviRabellaRisultati()
{
 var insertSpot = document.getElementById("aggiungiQui");
 if (insertSpot.hasChildNodes())
  insertSpot.removeChild(insertSpot.lastChild);
}

A questo punto possiamo testare la nostra applicazione.

La selezione della nazione provoca la formazione della lista delle regioni.

Figura 4. Selezione della nazione
Selezione della nazione

Selezionando la regione riempiamo la lista delle città.

Figura 5. Selezione della città
Selezione della città

Selezionando la città otteniamo la tabellina degli appartamenti.

Figura 6. Tabellina degli appartamenti
Tabellina degli appartamenti

Il tutto avviene senza postback con un incremento notevole delle prestazioni.

Il sorgente dell'esempio sviluppato in questo articolo può essere scaricato da qui ed è stato testato su Internet Explorer 6.0 e Firefox 1.5.

Ti consigliamo anche