Friday, October 7, 2011
Partial Rendering Control using JQuery
Introduction
In the last few years, it is very common to work on n-layer web application where the front end is represented by an ASP.NET website that talks with the layer below where, in an SOA context, one layer is usually represented by either WebServices or WCF to expose all the functionality using a standard completely decoupled from the consumer. In the last few years, the necessity to query the services using an asynchronous way is getting a key of a well done web application to avoid refreshing the entire page against HTTP requests especially when we need to refresh only a portion of the web page (Partial Rendering). JQuery is getting the standard to do this job allowing the front end to post and get data using JSON format. Unfortunately, in my experience, to achieve this behavior, I saw so many bad things first of all web services that returns HTML against a jquery call to have partial rendering. From my point of view, webservice/WCF must always return either POCO objects or JSON, in the case we are querying it using JQUERY but never HTML. Some other solutions are based on some kind of client side "template" that are going to be filled up by a client script using Jquery or JavaScript. With this solution against any change on the front end we have to change the template and probably the login to bind it with the JSON response. In this article, I'm going to show a good way to do partial rendering using a custom control that can be used in the web page without dealing with JQuery at all!How It Works
First of all, we have to create a web site project. After that, we have to add theJQueryControllor
on the toolbox bar clicking on choose items and selecting JqueryController.dll.Basically, this control inherits from the Panel web control and it's going to work as a container for the portion of the page we want to refresh (more or less the same behaviour of the AJAX update panel). Each
JaqueyControll
must contains at most one web user control (.ascx) that represents the portion of the page that is going to be refreshed. If you need to refresh more, then on section you just need to add more than one JqueryControll
, each one with their web user control. The key concept of this solution is instead of call a web service using Jquery to bind the JSON response to the interface, it does a post to a web page passing any parameters we need on the server side to satisfy the request. On the page (that must inherit from PageBase
that is explained later), you can find already filled up a collection with all the parameters you post. Thus you have everything you need on the server side to satisfy the request. Then you just need to call the Refresh
method on the JqueryController
to refresh the interface on the client side without dealing with Jquery or JavaScript! If you need, you can also provide a JavaScript callback function that is going to be called after the refresh bringing back also any parameters you may need to initialize again the web form or for any other reasons. Below, you can see the Sequence diagram of that process. As you can see on the diagram, the process is very similar to a classic ASP.NET postback but instead of rendering back the whole page, it calls a method on
JqueryControl
that returns just the portion of the HTML that has to be refreshed.In this example, we got a web page that contains
GridView
that is bounded to a data source. The user wants to add one record to this data source (an employee in this case) and then we have to refresh the GridView
to update the result. The web form should look like the one below:All the processes start by call a special JavaScript function that is going to start the process.
That function is shown below:
function JqueryPost() {
var argv = JqueryPost.arguments;
var argc = argv.length;
var strParms=new String()
page = argv[0];
for (var i = 1; i < argc; i++) {
strParms += argv[i] + '&'
}
strParms = strParms.substr(0, strParms.length - 1);
$.post(page, { __parameters: strParms },
function(data){
jQuery.globalEval(data);
});
}
This is the JavaScript code we have to add to the control we want to make as a trigger. In this case, it is a button click of an asp:Button
. By requirement, if we use a server control, like in this example, we have to add "return false
" after the call to avoid a post back. <asp:button id="Button1" runat="server" text="Add an employee"By requirement, that function can accept any parameters that we could need on server side to satisfy the request (for example, it could be a selected item of a drop down list). The first parameter must be the page where we want to post the data to.
onclientclick="JqueryPost('default.aspx','action=add');return false">
As you can see, this function uses the Jquery
Post
method that posts the web form to the page with all the parameters we have included into the call. Then the function executes the jquery code that comes back from the request that contains the JQuery code to inject the HTML we want to refresh.On the server side, the
PageBase
is going to do all the work: - Get the parameter from the request
- Create the HTML to send back to the client
- Add the jquery code to inject that HTML
- Call the JavaScript callback function if there is one
- Ends the response to send back just the HTML that has to be refreshed
PageBase
code is shown. There are two main points I would like to focus on: the function that creates the HTML and the one that returns the Jquery to inject that HTML. Below you can see the function that creates the HTML to be rendered:
public static string RenderUserControl(Control crtl)
{
Control control = null;
const string STR_BeginRenderControlBlock = "";
const string STR_EndRenderControlBlock = "";
StringWriter tw = new StringWriter();
Page page = new Page();
page.EnableViewState = false;
HtmlForm form = new HtmlForm();
form.ID = "__temporanyForm";
page.Controls.Add(form);
form.Controls.Add(new LiteralControl(STR_BeginRenderControlBlock));
form.Controls.Add(crtl);
form.Controls.Add(new LiteralControl(STR_EndRenderControlBlock));
HttpContext.Current.Server.Execute(page, tw, true);
string Html = tw.ToString();
//TO DO:clean the response!!!!!
int start = Html.IndexOf("");
int end = Html.Length - start;
Html = Html.Substring(start,end);
return Html;
}
This function gets in input the user control that we want to render back (in this case, the user control that contains the gridview
) and generates at run-time the HTML. First, it dynamically creates a new Web Page instance, then adds the control passed in input to the page.Then the control will be added to that page. After that, using server.execute
, we execute the page end, return the HTML of it but having just the control, we want to refresh on that page, it returns just that HTML.At this point, we could think that everything is done but we just miss the Jquery code that injects that HTML into the page and where it(the container) has to be injected.
The function to do that is shown below:
public void AddToRender(string PaneldId, string htmlToAdd)
{
this.ResponseToRender.Append("$('#" + PaneldId + "').html
('" + htmlToAdd + "');");
}
After this call, the page returns just the Jquery code that injects the HTML on the page. On the client side, that code is executed by the function we call at the beginning of the process (JqueryPost
) and the page will be refreshed. All this stuff is encapsulated into the
JqueryController
and the PageBase
. All we need is to call the JqueryController.Refresh()
method to have partial rendering! That method also accepts a callback function that gets a list of parameters and a name of a JavaScript function we could need after the render to do something more on the client side (for example, select an item on a drop down list). Now we are going through the page's code to understand how we can use that control. As we said at the beginning, everything starts by handling a client event (in this case, a button click) that has to call the JqueryPost()
function. This call does a post to the server so that we can move the focus on the page.using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using JqueryController;
public partial class _Default : PageBase
{
Employee emp = new Employee();
protected void Page_Load(object sender, EventArgs e)
{
if (this.IsPartialRendering)
{
if (this.PostParameter.ContainsKey("action")) {
string action = this.PostParameter["action"];
switch (action) {
case "add":
emp.AddOneEmployee();
break;
case "remove":
emp.RemoveTheLastEmployee();
break;
}
BindDataGrid(emp.GetAllEmployee());
}
if (this.PostParameter.ContainsKey("selectedId"))
{
int selectedId = int.Parse(this.PostParameter["selectedId"]);
SelectEmployees(selectedId);
}
}
}
private void BindDataGrid(IEnumerable<employee> datasource) {
GridUserControl1.GridDetails.DataSource = datasource;
GridUserControl1.GridDetails.DataBind();
JqueryControllerGrid.RefreshPanel("parameterBack");
}
private void SelectEmployees(int selectedId) {
switch(selectedId){
case 0:
BindDataGrid(emp.GetAllEmployee());
break;
case 1:
BindDataGrid(emp.GetAllEmployee().Where(x =>x.ID > 30));
break;
case 2:
BindDataGrid(emp.GetAllEmployee().Where(x => x.ID < 30));
break;
}
}
}
As you can see, on the page load, we have a new method called IsPartialRendering
that allows the developer to understand if the post was raised from a JqueryControl
. Thus after checking if is a partial rendering post, we can understand what kind of operation we want to perform getting the parameters we sent along the HTTP post. To do that, we can access PostParameter
collection. In this example, we post a parameter named "Action
" that contains the action we want to perform (in this case, add or remove and employ). After that, we are able to call the right function to perform the action required. In this example, we just call a method on the page that inserts or deletes the employee but on a real context we could call a WCF or whatever there is below the front end layer on our architecture. The good point is that if our front-end is based on MCV or MVP, we can carry on to use those patterns because we are just posting data to the page as a classic postback but we don't by-pass any layer as we could call for example a Service layer without passing through all the layers between the front end and the service layer itself. Anyway, let's focus on the example. After knowing what action we have to perform, we are able to call the function that adds or removes an employee. At this point, all we need to do is bind the datagrid again to the data-source and call JqueryContorl.Refresh()
to update the interface. In this example, we want to call a callback function on the client side passing a string
"parameterBack
". We can see that after that call, the datagrid
has been refreshed and the callback function has been called showing the parameter we sent back. Below, you can see the callback function that is contained on the page: function showParams() {
alert(showParams.arguments[0]);
}