ASP.NET Ajax using a custom ViewManager to render paged data without updatepanels
Sometimes you do not want the overhead of using an
updatepanel, as each time you make a request it is posting
more back than you might need, sure it can be powerful and
easy to use but not always the most effective way Say you
would like to be able to not use viewstate or postbacks but
would like to get a result set of data that can be paged and
you would like this totally client driven and to use the
least ammount of overhead.
Following on from
Scott Guthries
ViewManager
(thanks Scott), and some ideas I have had lately to do alot
more things client side and maybe stop using updatepanels. I
have made a more generic ViewManager which I blogged about
earlier, basically is just allows you to set properties of
your control alot easier and can set any properties the
control may have before you go ahead and render it. Don't
flame for writing something already done, I find my version
of the ViewManager easier to use, so thought I would share.
Data Control:
You will need to
create a usercontrol that will be used to render the pages
of data, on mine I gave it a few properties so that you can
set the current page and page size, I am just rendering my
data into a listview, my data comes from a database and I am
using LINQ to SQL to access it:
<%@ Control
Language="C#" AutoEventWireup="true"
CodeBehind="Data.ascx.cs" Inherits="NoUpdatePanels.Data"
EnableViewState="false" %>
<asp:ListView
ID="lvData" runat="server"
ItemPlaceholderID="plcItem">
<LayoutTemplate>
<br /><br
/>
<asp:PlaceHolder ID="plcItem"
runat="server"></asp:PlaceHolder>
</LayoutTemplate>
<ItemTemplate>
Name: <%# Eval("Name") %>
<hr
/>
</ItemTemplate>
</asp:ListView>
using
System;
using System.Collections;
using
System.Configuration;
using System.Data;
using
System.Linq;
using System.Web;
using
System.Web.Security;
using System.Web.UI;
using
System.Web.UI.HtmlControls;
using
System.Web.UI.WebControls;
using
System.Web.UI.WebControls.WebParts;
using
System.Xml.Linq;
namespace NoUpdatePanels {
public partial class Data : System.Web.UI.UserControl {
#region Properties
public int CurrentPage
{ get; set; }
public int PageSize { get; set;
}
#endregion
#region
Constructors/Load
// Constructor, just
init properties
public Data() {
this.CurrentPage = 0;
this.PageSize = 5;
}
protected void Page_Load(object
sender, EventArgs e) {
TestDataContext db =
new TestDataContext();
var query =
(from p in db.Persons
select
p).Skip(CurrentPage * PageSize).Take(PageSize);
lvData.DataSource = query;
lvData.DataBind();
}
#endregion
}
}
So
basically all I am doing is taking into account the current
page and page size to get a page of data and bind this to
the listview, viewstate has been turned off for this control
also.
The Page:
The hosting page is what
does all the work to get the data control rendered. All this
is done client side and done using web service methods, I
did not use a webservice in my case due to pure laziness but
used a static method on my page, then I set
EnablePageMethods=true on the ScriptManager so this would
all work, basically all the page is doing is using this
webmethod to get a page of data. The webmethod accepts a
paremeter which is the current page, is creates an instance
of the ViewManager based on the Data control sets the
CurrentPage property and then renders the control, it
returns this data.
The page uses javascript to
call this method, it places the result in a div. I keep
track of the current page in a variable so that we know
which page to load as we page through the data. A few things
to note here, it will not stop at the end of paging, it
doesnt really do any real error checking etc., this is just
a quick prototype of an idea of mine. The code for the page
html and codebehind are below:
<%@ Page
Language="C#" AutoEventWireup="true"
CodeBehind="Default.aspx.cs"
Inherits="NoUpdatePanels._Default" %>
<%@
Register src="Data.ascx" tagname="Data" tagprefix="uc1"
%>
<!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>Client Side Data
Loading</title>
</head>
<body>
<form
id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server"
EnablePageMethods="true"></asp:ScriptManager>
<a
href="http://weblogs.asp.net/controlpanel/blogs/posteditor.aspx?SelectedNavItem=NewPost#"
onclick="PreviousPage();">Previous <</a> -
<a
href="http://weblogs.asp.net/controlpanel/blogs/posteditor.aspx?SelectedNavItem=NewPost#"
onclick="NextPage();">Next ></a>
<div id="contentRegion">
Loading...
</div>
</form>
<script
type="text/javascript">
var currentPage =
0;
function
LoadPage(page) {
PageMethods.GetDataPage(page, function(result) {
// We loaded our data populate our div.
document.getElementById("contentRegion").innerHTML =
result;
},
function(error)
{
alert(error.get_message());
});
}
function
PreviousPage() {
currentPage--;
LoadPage(currentPage);
}
function NextPage() {
currentPage++;
LoadPage(currentPage);
}
// Add our load event
handler
Sys.Application.add_load(LoadPage);
</script>
</body>
</html>
using
System;
using System.Collections;
using
System.Configuration;
using System.Data;
using
System.Linq;
using System.Web;
using
System.Web.Security;
using System.Web.UI;
using
System.Web.UI.HtmlControls;
using
System.Web.UI.WebControls;
using
System.Web.UI.WebControls.WebParts;
using
System.Xml.Linq;
using System.Web.Services;
namespace
NoUpdatePanels {
public partial class _Default :
System.Web.UI.Page {
[WebMethod()]
public static string GetDataPage(int page) {
// Create an instance of our viewmanager.
ViewManager<Data> man = new
ViewManager<Data>("~/Data.ascx");
// Set the current page property.
man.Control.CurrentPage = page;
//
Return the rendered control.
return
man.Render();
}
}
}
The ViewManager:
This is the generic
viewmanager that does the actual rendering of the
control.
using System;
using System.Data;
using
System.Configuration;
using System.Linq;
using
System.Web;
using System.Web.Security;
using
System.Web.UI;
using System.Web.UI.HtmlControls;
using
System.Web.UI.WebControls;
using
System.Web.UI.WebControls.WebParts;
using
System.Xml.Linq;
using System.Text;
using
System.IO;
namespace NoUpdatePanels {
/// <summary>
/// A generic user control
rendering helper, basically you initialise the view manager
and
/// call render to render that control, but
the benifit of this version is you can access the control
/// the view manager is rendering and can set custom
properties etc.
/// </summary>
///
<typeparam name="T">The type of the control you are
rendering</typeparam>
public class
ViewManager<T> where T : Control {
#region Properties
private T _control =
default(T);
/// <summary>
/// Gives you access to the control you are rendering
allows
/// you to set custom properties etc.
/// </summary>
public T Control {
get {
return _control;
}
}
// Used as a placeholder
page to render the control on.
private Page
_holder = null;
#endregion
#region Constructor
///
<summary>
/// Default constructor for
this view manager, pass in the path for the control
/// that this view manager is render.
///
</summary>
/// <param
name="inPath"></param>
public
ViewManager(string path) {
//Init the
holder page
_holder = new Page();
// Create an instance of our control
_control = (T)_holder.LoadControl(path);
// Add it to our holder page.
_holder.Controls.Add(_control);
}
#endregion
#region Rendering
/// <summary>
/// Renders the current
control.
/// </summary>
///
<returns></returns>
public string
Render() {
StringWriter sw = new
StringWriter();
// Execute the page
capturing the output in the stringwriter.
HttpContext.Current.Server.Execute(_holder, sw, false);
// Return the output.
return
sw.ToString();
}
#endregion
}
}
Thanks for reading hope you found that useful, all comments
appreciated.
Stefan