Performance Improvement in ASP.NET Using Caching

The challenge in building high-performance, scalable Web applications is the ability to store items, whether data objects, pages, or parts of a page, in memory the initial time they are requested. You can store these items on the Web server or other software in the request stream, such as the proxy server or browser. This allows you to avoid recreating information that satisfied a previous request, particularly information that demands significant processor time or other resources.

ASP.NET provides caching at several levels for you to leverage and improve the responsiveness of your application by storing the page output or application data across HTTP requests and reuse it. This allows the web server to take advantage of processing the request without recreating the information and thus saving time and resources.

Caching Opportunities in ASP.NET web pages

ASP.Net supports both page (or portion of a page) caching and also caching data from a backend data source and storing these individual objects in memory.

To achieve this following features are provided in ASP.NET

  • Page output caching
  • Page fragment caching
  • Data caching

Page Output Caching

Dynamically generated .aspx pages can be cached for efficiency instead of re-generating each .aspx page for identical requests, the pages are cached.

Page Output caching can be achieved in the following 3 ways.

1) This can be achieved by specifying the @OutputCache directive at the top of the ASP.Net page. It controls the caching duration (in seconds).

<%@ OutputCache Duration="3600" VaryByParam="none" %>
<html>
<
script language="C#" runat
="server">
void Page_Load(Object sender, EventArgs e)
{
msg.Text = DateTime.Now.ToString();
}
</script
>
<
body
>
<
h3>Output Cache example</font></h3
>
<
p>Last generated on: <asp:label id="msg" runat
="server"/>
</
body
>
</
html> 

2) Programmatically you can achieve this using the HttpCachePolicy sealed class which can be accessed from the HttpResponse.Cache property of the Page.Response property.

HttpCachePolicy class

public sealed class HttpCachePolicy
{
public HttpCacheVaryByHeaders VaryByHeaders {get
;}
public HttpCacheVaryByParams VaryByParams {get
;}
public void AppendCacheExtension(string
extension);
public void
SetCacheability(HttpCacheability cacheability);
public void
SetExpires(DateTime date);
public void
SetLastModified(DateTime date);
public void
SetMaxAge(TimeSpan delta);
public void
SetNoServerCaching();
public void SetSlidingExpiration(bool
slide);
//...
}

Modifying a page's caching policy programmatically.

<html>
<
script language="C#" runat
="server">
void Page_Load(Object sender, EventArgs e)
{
Response.Cache.SetExpires(DateTime.Now.AddSeconds(360));
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetSlidingExpiration(true);
msg.Text = DateTime.Now.ToString();
}
</script
>
<
body
>
<
h3>Output Cache example</font></h3
>
<
p>Last generated on: <asp:label id="msg" runat
="server"/>
</
body
>
</
html> 

3) Caching of GET requests with query strings or POST requests with bodies controlled by VaryByParam property. It determines how many versions of the page are cached. Specifying it to "none" means that only GET requests with NO query strings or POST requests with NO body will hit the cache. Specifying VaryByParam to "*" means that as many different querystring or POST body requests are received
will be cached.

The following table gives you a brief idea of the VaryByParam values.

VaryByParam Description
none One version of page cached (only raw GET)
* n versions of page cached based on query string and/or POST body
V1 n versions of page cached based on value of V1 variable in query string or POST body
V1;V2 n versions of page cached based on value of V1 and V2 variables in query string or POST body

<%@ OutputCache Duration="60" VaryByParam="none" %>
<%@ OutputCache Duration="60" VaryByParam="*" %>
<%@ OutputCache Duration="60" VaryByParam="name;age" %>

The first time the page is requested, the response is generated and added to the cache. If the page is requested within 60 seconds with the same values for name and age, then the cached version is used.

For Information:

Other cache varying options

The OutputCache directive supports several other cache varying options

  • VaryByHeader - maintain separate cache entry for header string changes (UserAgent, UserLanguage, etc.)
  • VaryByControl - for user controls, maintain separate cache entry for properties of a user control
  • VaryByCustom - can specify separate cache entries for browser types and version or provide a custom GetVaryByCustomString method in HttpApplication derived class.

Page fragment caching

Parts of the ASP.Net page which are to be cached are encapsulated in Web Forms User Controls.

MyUserControl.ascx

<%@ OutputCache Duration="60" VaryByParam="none" %>
<%@ Control Language=C# %>

<script runat=server>
protected void Page_Load(Object src, EventArgs e)
{
m_Date.Text = "Control generated at " +DateTime.Now.ToString();
}
</script>
<
asp:Label id=m_Date runat=server /> 

Client.aspx

<%@ Page Language=C# %>
<%@ Register TagPrefix="DM" TagName="UserFrag"src="MyUserControl.ascx" %>

<html>
<
script runat=server>protected void Page_Load(Object src, EventArgs e)
{
m_PageDate.Text = "Page generated at " +DateTime.Now.ToString();
}
</script>
<
body>
<
DM:UserFrag runat=server ID="Userfrag1" NAME="Userfrag1"/><br>
<
asp:Label id=m_PageDate runat=server />
</
body>
</
html>

Data caching

In simple terms data caching is storing data in memory for quick access. Typically information that is costly to obtain (in terms of performance) is stored in the cache. One of the more common items stored in a cache in a Web application environment is commonly displayed database values; by caching such information, rather than relying on repeated database calls, the demand on the Web server and database server's system resources are decreased and the Web application's scalability increased.

ASP.NET provides a full-featured cache engine that can be used by pages to store data across HTTP requests

A small example of storing the value obtained from the database is given below.

<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<html>
<
script language="C#" runat="server">
protected void Page_Load(Object src, EventArgs e)
{
DataView dv = (DataView)Cache.Get("EmployeesDataView");
if (dv == null)
{
// wasn't there
SqlConnection conn =new SqlConnection("server=localhost;uid=sa;pwd=;database=Test");
SqlDataAdapter da =new SqlDataAdapter("select * from Employees", conn);
DataSet ds = new DataSet();
da.Fill(ds, "Employees");
dv = ds.Tables["Employees"].DefaultView;
Cache.Insert("EmployeesDataView", dv);conn.Close();
}
else
Response.Write("<h2>Loaded employees from data cache!</h2>");
lb1.DataSource = dv;
lb1.DataTextField = "Name";
lb1.DataValueField = "Age";
DataBind();
}
</script>
<
body>
<
asp:ListBox id="lb1" runat=server />
</
body>
</
html> 

Cache entry attributes

When adding cache entries, several attributes can be specified

  • Dependencies (on files, directories, or other cache entries)
  • Absolute expiration time
  • Sliding expiration time
  • Relative priority
  • Rate of priority decay
  • Callback function for removal notification

public void Insert( string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback );

The different parameters which the Insert method accepts is given below

Parameters

key The cache key used to reference the object.
value The object to be inserted in the cache.
dependencies The file or cache key dependencies for the item. When any dependency changes, the object becomes invalid and is removed from the cache. If there are no dependencies, this parameter contains a null reference.
absoluteExpiration The time at which the inserted object expires and is removed from the cache.
slidingExpiration The interval between the time the inserted object was last accessed and when that object expires. If this value is the equivalent of 20 minutes, the object will expire and be removed from the cache 20 minutes after it was last accessed.
priority The cost of the object relative to other items stored in the cache, as expressed by the CacheItemPriority enumeration. This value is used by the cache when it evicts objects; objects with a lower cost are removed from the cache before objects with a higher cost.
onRemoveCallback A delegate that, if provided, will be called when an object is removed from the cache. You can use this to notify applications when their objects are deleted from the cache.

Removing objects from the cache

  • Objects can be explicitly taken out of the cache by calling Remove
  • Cache can remove item implicitly for a variety of reasons
    • Data expiration
    • Memory consumption
  • Low priority data removed first
  • Values marked with Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, or CacheItemPriorityDecay.Never are never removed
  • Can register for removal notification, including reason

Using removal notification callback

<script language=C# runat=server>
public void Application_OnStart()
{
System.IO.StreamReader sr =new System.IO.StreamReader("pi.txt");
string pi = sr.ReadToEnd();
Context.Cache.Add("pi", pi, null,Cache.NoAbsoluteExpiration,new TimeSpan(0, 5, 0),CacheItemPriorityDecay.Never,new CacheItemReomovedCallback(this.OnRemove));
}
public void OnRemove(string key, object val, CacheItemRemovedReason r)
{
// respond to cache removal here
}
</script> 

Disadvantages of Caching

Although the caching support can be really helpful in a lot of scenarios, it has some major disadvantages:
ยท With cached pages that display results retrieved from a database the cache can be inconsistent, i.e. not reflecting the latest changes applied to a database.

Conclusion

Caching dramatically improves the performance of a web site, but it has disadvantages as well. The user should take care of the parameter values for expiration policy.