In applicazioni Web di una certa complessità, spesso le classiche tecniche di caricamento dinamico di UserControl tramite PostBack o UpdatePanel rischiano di rendere lo sviluppo delle pagine molto complesso nonché, cosa più pericolosa, non performante. Di certo uno scenario maggiormente flessibile dovrebbe anzitutto ridurre al minimo il traffico da/verso il server (vedi "UpdatePanel e i suoi abusi") ma soprattutto permettere di richiedere il caricamento di contenuti on-demand sfruttando la potenza di AJAX.
In questo esempio voglio mostrare quanto sia semplice implementare un' infrastruttura di base che permetta il caricamento dinamico di UserControl tramite jQuery AJAX (client-side) e un HttpHandler (server-side). In breve:
- Il client richiede all'HttpHandler uno UserControl con una richiesta JSON
- L'HttpHandler processa la richiesta inizializzando solo lo UserControl desiderato ed inviando al client solo l' (X)Html di rendering dello UserControl
- Il client posiziona l' (X)Html ricevuto in una zona specifica della pagina
Ovviamente esistono altri modi per creare dinamicamente UserControl con AJAX (es. tramite WebService), ma per brevità tralasciamo volutamente questo aspetto.
1. Il DataContract della richiesta
Anzitutto abbiamo bisogno di una classe che faccia da DataContract per la richiesta di uno UserControl. In particolare, la proprietà ControlParameters permette di specificare degli eventuali parametri da passare allo UserControl per pilotarne il corretto comportamento.
[DataContract]
public class ControlRequest
{
[DataMember]
public string ControlName { get; set; }
[DataMember]
public Dictionary<string, string> ControlParameters { get; set; }
}
2. HttpHandler
Server-side abbiamo un HttpHandler che deserializza le richieste JSON conformi al DataContract sopra specificato per caricare ed inizializzare correttamente (tramite reflection) lo UserControl richiesto. Il codice si commenta quasi da solo :D
public class UserControlHandler : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
// 1. Deserializzazione della richiesta JSON
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(ControlRequest));
ControlRequest controlRequest = serializer.ReadObject(context.Request.InputStream) as ControlRequest;
// 2. Caricamento dello UserControl
Page page = new Page();
UserControl userControl = (UserControl)page.LoadControl("~/Controls/" + controlRequest.ControlName + ".ascx");
userControl.EnableViewState = false;
// 3. Inizializzazione dei parametri dello UserControl via Reflection
SetControlParameters(controlRequest, userControl);
// 4. Esecuzione dello UserControl all'interno di un form
HtmlForm form = new HtmlForm();
form.Controls.Add(userControl);
page.Controls.Add(form);
StringWriter textWriter = new StringWriter();
HttpContext.Current.Server.Execute(page, textWriter, false);
// 5. Invio dell'html di rendering dello UserControl (senza il form contenitore)
context.Response.ContentType = "text/html";
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.Write(Regex.Replace(textWriter.ToString(), @"<[/]?(form)[^>]*?>", string.Empty, RegexOptions.IgnoreCase));
}
private void SetControlParameters(ControlRequest request, UserControl userControl)
{
foreach (string parameterName in request.ControlParameters.Keys)
{
PropertyInfo property = userControl.GetType().GetProperty(parameterName);
if (property != null) property.SetValue(userControl, request.ControlParameters[parameterName], null);
}
}
...
}
Let's try!!!
Supponiamo di avere nella nostra soluzione uno UserControl CustomersGridView.ascx il cui codebehind include la complicatissima logica per popolare e filtare la classica griglia di Customers.
public partial class CustomersGridView : System.Web.UI.UserControl
{
public string FilterExpression { get; set; }
protected void NorthwindSqlDataSource_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
{
e.Command.Parameters["@ContactName"].Value = FilterExpression;
}
}
Come possiamo notare la proprietà FilterExpression esposta serve proprio per determinare il filtraggio dei customer in fase di popolamento della GridView.
A questo punto non rimane che creare la nostra pagina di test:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default"
EnableViewState="false" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Dynamic UserControl Loading via jQuery Ajax and HttpHandler</title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="scriptManager" runat="server">
<Scripts>
<asp:ScriptReference Path="~/scripts/jquery-1.2.6.pack.js" ScriptMode="Release" />
<asp:ScriptReference Path="~/scripts/jquery.json-1.3.min.js" ScriptMode="Release" />
</Scripts>
</asp:ScriptManager>
<div>
ContactName:
<input id="txtCustomer" type="text" />
<input type="button" value="Search" onclick="SearchCustomer()" />
<br /><br />
</div>
<div id="testDiv"></div>
</form>
<script type="text/javascript">
function getData(request)
{
$.ajax
({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "UserControlHandler.ashx",
data: $.toJSON(request), // Uso del plugin jquery.json
success: function(msg) { $('#testDiv').html(msg); }, // 'testDiv' viene popolato con l'Html di rendering dello UserControl
error: function(XMLHttpRequest, textStatus, errorThrown) { alert("Error Occured!"); }
});
}
function SearchCustomer()
{
var filterExpression = $('#txtCustomer').val();
var request = {ControlName:'CustomersGridView',ControlParameters:[{Key:'FilterExpression',Value:filterExpression}]};
getData(request);
}
</script>
</body>
</html>
Al click del button "Search" viene invocata la function SearchCustomer() che inizializza la stringa JSON da inviare all' HttpHandler, specificando il nome dello UserControl desiderato (CustomersGridView) ed i parametri richiesti (FilterExpression). Viene quindi invocata la funzione getData(...) che si occupa di gestire la comunicazione AJAX con l' HttpHandler. Infine, l' Html di rendering dello UserControl CustomersGridView viene posizionato all'interno del div "testDiv" della pagina.