Click here to Skip to main content
15,881,173 members
Articles / Web Development / IIS
Article

Correlated Caching for ASP.NET User Controls

Rate me:
Please Sign up or sign in to vote.
3.83/5 (7 votes)
3 Oct 2004CPOL5 min read 64.4K   891   25  
Enable caching dependencies among user controls.

Introduction

ASP.NET 1.x provided excellent support for caching user controls using directive @outputcach and PartialCaching attribute. Also, it provides some mechanism to remove a cached user control from cache if data has been changed. Specifically, cached user controls are removed from cache if dependent file has changed or dependent cached item has changed. (Check MSDN and ASP.NET Caching for details).

In reality, the content of a user control depends on data mostly from database, rarely from files, and never from cache. It is therefore fair to say that cache dependency provided by ASP.NET 1.x is not useful out of box for user control cache removal. Developers have to come up with ways to extend and enhance this cache dependency just like what were done in Dino Esposito's article using timer pulling database, and Jeff Prosese's article using database extended stored procedure interacting with file system.

This article takes a little bit different approach: instead of monitoring changes in database table using timer or file system change notification, we will monitor the internal state of the user control that uniquely determines the data to be loaded. For example, suppose we login to a broker account and do some transaction, then the content of the transaction history user control will be determined by Session ID and Account number, for the following reasons:

  • the cached transaction history user control must vary by Session ID, not allowing private data be disclosed to different login users.
  • the cached transaction history user control will be removed from cache if transaction processing user controls with matching account number is loaded for use.

In essence, we have to correlate Transaction History User Control to Transaction Processing User Control. And consequently, when a user is trying to do a transaction for an account, we will remove the cached History user control immediately. Now, let me explain how to use "VaryByCustom", CacheDependency class, and custom attribute to implement this cache removal strategy.

Multi version Caching of User Control

There is only one way to access a cached user control correctly assuming we drag and drop History onto IDE:

C#
History h = (History) FindControl("History1");
if( h !=null) h.AccountNumber=this.tbAccountNumberRetrival.Text;

You may argue that we can use the following code to load cached user control and change its property AccountNumber:

C#
PartialCachingControl pcc= (PartialCachingControl) LoadControl("History.ascx");
this.TheLocation.Controls.Add(pcc);
if (pcc.CachedControl !=null)
  ((History) pcc.CachedControl).AccountNumber=this.tbAccountNumberRetrival.Text;

But as shown in my sample project included with this article, LoadControl approach will not work for cached user control (please reference commented-out code snippet in Page_Load function in WebForm1.aspx.cs). Therefore, we will stay with FindControl function and declarative loading (drag and drop) of cached user control, and programmatically set its state for content changes.

Unfortunately, the above approach is not sufficient enough for multi-version caching since after the user control is cached, FindControl will return null and therefore we cannot reference its property AccountNumber. As it turns out, ASP.NET disallows programmatically changing caching content, except using VaryByCustom or VaryByControl. In other words, when the custom string or control value changes, FindControl function will return not-null reference to the user control and therefore allow us to set its property to retrieve multi-version content. Here are the related code segments utilizing VaryByCustom from ASP.NET:

ASP.NET
<%@ OutputCache Duration="60" VaryByParam="None" 
  VaryByCustom="CachingMultiVersion" %>

In Global.asax:

C#
public override string GetVaryByCustomString(HttpContext context, string custom)
{
  if ( custom.ToLower() =="cachingmultiversion")
  {
    HttpCookie cookie = context.Request.Cookies["CachingMultiVersion"];
    if(cookie != null) return cookie.Value;
  }
  return base.GetVaryByCustomString (context, custom);
}

public static string CachingMultiVersion
{
  set
  {
    HttpContext.Current.Response.Cookies["CachingMultiVersion"].Value=value;
  }
}

In WebForm1.aspx:

C#
Global.CachingMultiVersion=this.tbSessionID.Text + 
         this.tbAccountNumber.Text;

In essence, we need to correctly vary CachingMultiVersion custom string so ASP.NET will give us a chance to use FindControl function. This seems to be the only way to implement multi-version User Control caching correctly.

Removal of Cached Content of a user control:

Now we know how to do multi-version caching of user control per "Account Number". Let us look at how to remove cached user control History.ascx when another user control Processing.ascx is loaded with matching "Account Number". The answer lies in CacheDependency class. In fact, ASP.NET generates StaticPartialCachingControl or PartialCachingControl, both of which are derived from BasePartialCachingControl. Therefore, we can use BasePartialCachingControl's Dependency property to establish a dependency with a cached item:

C#
pcc= Parent as PartialCachingControl;
spcc= Parent as StaticPartialCachingControl;

if (pcc !=null) pcc.Dependency=new CacheDependency(null, 
                                new string[]{CachingKey});

if (spcc !=null) spcc.Dependency=new CacheDependency(null, 
                                new string[]{CachingKey});

The code is actually in Base.ascx.cs, from which all user controls will be derived. Therefore, all cached user controls can be dependent on some cached item. Consequently, Process.ascx just needs the following code to remove the related cached item in order to remove the cached user control History.ascx:

C#
HttpRuntime.Cache.Remove(CachingKey);

Most important, we need to build CachingKey by relating Monitored Parameter "AccountNumber" on History.ascx and Processing.ascx so that only matched caching will be removed. Here is the code for applying attribute CorrelatedCaching in order to build CachingKey correctly:

Code in History.ascx.cs:

C#
[PartialCaching(60)]
[CorrelatedCaching(CorrelatedCachingActionOption.Caching,"CorrCachingName1")]
public class History : JQD.BaseUserControl
{
  ......
  string _AccountNumber;

  [CorrelatedCaching(CorrelatedCachingActionOption.Monitoring, 
                                              "AccountNumber")]
  protected System.Web.UI.WebControls.TextBox tbAccountNumber;
  public string AccountNumber;
  {
    get { return _AccountNumber;}

    set { 
      this.tbAccountNumber.Text=value;
      _AccountNumber=value;
    }
  }

Code in Processing.ascx:

C#
[CorrelatedCaching(CorrelatedCachingActionOption.Monitoring,"AccountNumber")]
protected string _Prop1;

public string Prop1
{
  get { return _Prop1;}
  set{ _Prop1=value;}
}

It is very important to understand that applying attribute is like passing initialization data to a class. That class must implement a special function to take data from attributes. I implemented a function RegisterForCorrelatedCaching to just do that and here are some of the code segments:

C#
object[] obj = 
  this.GetType().GetCustomAttributes(typeof(CorrelatedCachingAttribute), true);

CorrelatedCachingAttribute corrCachingAttr = (CorrelatedCachingAttribute) obj[0];
MonitoringString="";
BuildMonitoringString();
CachingKey=corrCachingAttr.Name + MonitoringString;

private void BuildMonitoringString()
{
  Type t = this.GetType();
  FieldInfo[] allFields= t.GetFields( ...);
  foreach (FieldInfo fi in allFields)
  {
    CorrelatedCachingAttribute[] attr = 
      (CorrelatedCachingAttribute[]) 
      fi.GetCustomAttributes(typeof(CorrelatedCachingAttribute),true);

    if (attr[0].Action ==CorrelatedCachingActionOption.Monitoring)
      MonitoringString += attr[0].Name +"="+fi.GetValue(this).ToString()+";"; 
  }
}

What we are doing here is to loop through all User Control properties and pick those with CorreletedCaching attribute applied and with CorrelatedCachingActionOption.Monitoring. Then we will get those properties' values using reflection, and build MonitorString. CachingKey will be the concatenation of MonitorString and Correlation Name, with the Correlation Name being present in the attribute applied to the class History and the class Processing. This makes correlation of user controls possible by virtually having the same CachingKey.

How to run the sample project

  1. Download the zip file and extract it into C:\inetpub\wwwroot.
  2. Click on the solution file to open it up in VS.NET 2003, and build Solution. Note that we may need to use IIS MMC to configure directory C:\inetpub\wwwroot\TestCorrelatedCaching as application to enable running in debug mode.
  3. Run in Debug mode and you should see the following screen:

    Sample screenshot

  4. Set SessionID and AccountNumber, and click on "CachingMultiVersion", and then "Submit from page". You should see History User Control timestamp changes once, and then stay the same.
  5. Now change AccountNumber and set AccountNumber for content retrieval, and click on "CachingMultiVersion" and then "Submit from page". You should see timestamp changes once and AccountNumber show up in History User Control and then stay the same.
  6. Finally, input matching AccountNumber into Account Number for caching removal, and then click "Submit from page". You will see the timestamp constantly changing, indicating History User Control is not cached anymore. But if you input a mismatched AccountNumber, the timestamp will not change.

Conclusion

Correlated Caching is actually a very simple idea if you are comfortable using attributes and reflection. I intended to use it in my current project and hope you will also find it useful.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
I am a Microsoft Certified Application Developer (MCAD), currently focusing on using .Net Framework to develop Business Solutions. I am mostly language neutral. I have used C, C++, ATL, MFC, VB.Net, C#, VB 6, PL/SQL, Transact SQL, ASP, Fortran, etc.

Comments and Discussions

 
-- There are no messages in this forum --