Using page fragment caching and AJAX thing

I am building a project powered by Ajax (telerik implementation, excellent stuff, highly recommended) these days. Soon I got an interesting challenge. How to enable UserControl (or page fragment) caching based on some parameters with Ajax (I won't discuss the importance of caching here nor its internals, just note that cache is your good friend). It is not strictly an Ajax issue but it stems from the way Ajax forces you to code - no postbacks, a callback has to be implemented as a server event. No postback means that one can't use VaryByParam caching attribute because you can't pass parameters in such way when doing Ajax. VaryByControl doesn't help either, as it is useful only in declarative way.

So, only attribute remaining is VaryByCustom. And this one I've used. Here is my control's ascx code:

 

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="WebUserControl.ascx.cs" Inherits="WebUserControl" %> <%@ OutputCache Duration="15" Shared="true" VaryByParam="None" VaryByCustom="tubo" %> <asp:Label ID="ASPxLabel1" runat="server" Text="<%# DateTime.Now.ToString() %>" /> - <asp:Label ID="tubek" runat="server" Text="ASPxLabel" />

 

Nothing dramatic here. A couple of labels - one that shows the date and the other that will show parameter passed. There is <%@ OutputCache %> directive that instructs asp.net runtime to cache the control for 15s based on VaryByCustom value and the cache output is shared among different pages.

Here is code behind:

 

public partial class WebUserControl : System.Web.UI.UserControl { protected void Page_Load(object sender, EventArgs e) { DataBind(); } public string Tublo { set { tubek.Text = value; } } }

 

Again, nothing spectacular - there is a property Tublo that sets the second label's text to show the parameter passed. My goal is to cache this usercontrol based on value passed to Tublo. Here steps in VaryByCustom attribute. It lets you define a custom string that later is used to determine the caching key. If the key matches an output already present in cache then no instance is generated and output is reused. The code that emits proper value defined by VaryByCustom has to be put in global.asax or better, it has to override HttpApplication.GetVaryByCustomString method as shown here (excerpt from global.asax file):

 

public override string GetVaryByCustomString(HttpContext context, string custom) { if (custom == "tubo") { string tublo = (string)context.Items["tublo"]; System.Diagnostics.Debug.WriteLine(DateTime.Now + ": tublo is " + tublo); return tublo; } else return base.GetVaryByCustomString(context, custom); }

 

In the custom argument you get text defined in VaryByCustom to make a decision which value to return (the return value is actually caching key for particular control). This part puzzled me the most. How can I let know which Tublo value to return (remember, my goal is to cache based on Tublo property which in turn is the number of button pressed). After some investigation I've found out that I could use HttpContext's request cache aka HttpContext.Items property - values put here are cached for the request lifetime. Once request is served the cached values are lost. No better place to communicate with GetVaryByCustomString as it gets HttpContext as an argument. I just get the value out of context.Items and return it.

Here is how is the host test page built:

Very simple. It has two buttons and beneath you see the ouput of WebUserControl defined above.

And here is matching aspx:

 

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <%@ Register Src="WebUserControl.ascx" TagName="WebUserControl" TagPrefix="uc1" %> <%@ Register Assembly="RadAjax.Net2" Namespace="Telerik.WebControls" TagPrefix="radA" %> <!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>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <radA:RadAjaxPanel ID="RadAjaxPanel1" runat="server" > <asp:Button ID="buttonOne" runat="server" OnClick="Button1_Click" Text=" 1 " /> <asp:Button ID="buttonTwo" runat="server" OnClick="Button2_Click" Text=" 2 " /> &nbsp;<asp:Panel id="Panel1" runat="server" style="font: normal 12px Arial, Verdana, Sans-serif; color: #a6a896;"></asp:Panel> </radA:RadAjaxPanel> </div> </form> </body> </html>

 

Note that I didn't put WebUserControl on the page at design time (however, if you want to access WebUserControl you have to register it with <%@ Register directive %>). Yes, that's right - you need to create WebUserControl at runtime to use asp.net's built it caching mechanism. All controls are embedded in RadAjaxPanel that handles Ajax mechanism for you - no need to do anything else, simple as that. Each button has its own Click event defined that does set WebUserControl's Tublo property. And here is code behind:

 

public partial class _Default : System.Web.UI.Page { private void CreateDynamicControl(string s) { Context.Items.Add("tublo", s); Control c = LoadControl("~/WebUserControl.ascx"); Panel1.Controls.Add(c); WebUserControl wc = c as WebUserControl; if (wc == null) { PartialCachingControl pcc = c as PartialCachingControl; if (pcc != null) { wc = pcc.CachedControl as WebUserControl; if (wc == null) Debug.WriteLine("Control is cached"); } } if (wc != null) wc.Tublo = s; } protected void Button1_Click(object sender, EventArgs e) { CreateDynamicControl("1"); } protected void Button2_Click(object sender, EventArgs e) { CreateDynamicControl("2"); } }

 

CreateDynamicControl method does create a new instance of WebUserControl and if it is not cached yet it sets its Tublo property according to the clicked button. A portion of its code that does creation is borrowed from MSDN Magazine's article Keep Sites Running Smoothly By Avoiding These 10 Common ASP.NET Pitfalls. The important sentence in this method is first one - it stores Tublo argument to HttpContext.Items - this value is later used in GetVaryByCustomString as a return value and thus acts the role of caching key. The value is removed when request life ends.

That's it. Run the demo and click to both buttons. You'll note that each control's version will have a different time and time will change after at least 15s which means the output was rebuilt. Which in turn means that your IIS server will be able to server more requests per second not to mention that Ajax speeds up applications considerably.

Here is the source code for this article.