Caching is a technique of persisting the data in
memory for immediate access to requesting program calls. Many in the developer
community consider caching as one of the features available to improve
performance of Web applications.
Consider a page that has list of Employee name, contact numbers and mail-Ids of
existing employees of a company on an intranet accessible by all employees.
This is very useful information that is available throughout the company and
could also be one of the most accessed pages. The functionality of adding,
updating or deleting is usually less intensive compared to more
transaction-based systems like Purchase ordering, Voucher creation etc. Now in
a normal scenario the process of querying database for each request is not
cost-effective in terms of server resources, hence is lot better to cache or
persist the data to avoid this costly loss of resources.
ASP.NET provides the flexibility in terms of caching at different levels.
1. Page Level Output Caching
This is at the page level and one of the easiest means for caching pages.
This requires one to specify Duration of cache and Attribute of caching.
Syntax: <%@ OutputCache Duration=”60″ VaryByParam=”none” %>
The above syntax specifies that the page be cached for duration of 60
seconds and the value “none” for VaryByParam* attribute makes sure that there
is a single cached page available for this duration specified.
there are other attributes like VaryByHeader, VaryByCustom etc. Please refer to
MSDN for more on this.
2. Fragment Caching
actually caching a user control that can be used in a base web form page. In
theory, if you have used include files in the traditional ASP model then this
caching model is like caching these include files separately. In ASP.NET more
often this is done through User Controls. Initially even though one feels a bit
misleading, this is a significant technique that can be used especially when
implementing “n” instances of the controls in various *.aspx pages. We can use
the same syntax that we declared for the page level caching as shown above, but
the power of fragment caching comes from the attribute “VaryByControl”. Using
this attribute one can cache a user control based on the properties exposed.
Syntax: <%@ OutputCache Duration=”60″ VaryByControl=”DepartmentId”
%>
The above syntax when declared within an *.ascx file ensures that the
control is cached for 60 seconds and the number of representations of cached
control is dependant on the property “DepartmentId” declared in the control.
Add the following into an *.ascx file. Please note the use of tag “Control”
and the cache declaration.
<%@ outputcache duration=”60″ varybycontrol=”DepartMentId” %>
<script runat=”server”>
private int _Departmentid=0;
public int DepartMentId
{
get{return _Departmentid;}
set{_Departmentid =value;}
}
//Load event of control
void Page_Load(Object sender, EventArgs e)
{
lblText.Text = “Time is ” + DateTime.Now.ToString() + ” for Department id = ”
+ _Departmentid + “\n”;
}
</script>
<asp:Label id=”lblText” runat=”server”></asp:Label>
used; the declaration of control using syntax <[TagPrefix]:[TagName]>;
Usage of property ” DepartMentId”. Open the page in two browsers and closely
watch the Base form timing and the User control timing. Also note that the
following page results in two copies or representation of user control in the
cache.
<%@ Register TagPrefix=”CacheSample” TagName=”Text” Src=”CachingControl.ascx”
%>
<script runat=server>
void Page_Load(Object sender, EventArgs e)
{
this.lbltime.Text =”Base form time is ” + DateTime.Now.ToString() + “\n”;
}
</script>
<html>
<head>
</head>
<body>
<form runat=”server” ID=”Form2″>
<table>
<tbody>
<tr>
<td>
<asp:Label id=”lbltime” runat=”server”></asp:Label>
</td>
</tr>
<tr>
<td>
<CACHESAMPLE:TEXT id=”instance1″ runat=”Server”
DepartMentId=”0″>
</CACHESAMPLE:TEXT>
</td>
</tr>
<tr>
<td>
<CACHESAMPLE:TEXT id=”instance2″ runat=”Server”
DepartMentId=”1″>
</CACHESAMPLE:TEXT>
</td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
3. Application Level Caching
an application. Fragment caching is great in that sense but has limitations by
using user controls as means to do. We can use the Cache object
programmatically to take advantage of caching objects and share the same
between pages. Further the availability of different overloaded methods gives a
greater flexibility for our Cache policy like Timespan, Absolute expiration
etc. But one of the biggest takes is the CacheDependancy. This means that one
can create a cache and associate with it a dependency that is either another
cache key or a file.
act as lookups to application specific tables. For e.g. if you take up adding a
Employee, usually one has master tables like “tblQualification” to get list of
qualifications, “tblLocations” to get list of locations etc. These tables* are
usually set during the initial application configuration phase and could be
modified once a month or even less than that. Hence it makes sense for us to
use them in our Cache rather than making calls to database on each request. But
then what Cache Policy do we adopt?
because if anybody changes data in these tables one has to also refresh the
cache. It is here that CacheDependancy can be used.
extensively used in our select statements through out the applications.
to provide a list view of existing employees. You need to create a Database in
Sql Server, setup some data before you can continue. The schema scripts are
enclosed in the article.
setup.
<add key=”conn” value=”Data Source=vishnu;trusted_connection=yes;Initial
Catalog=Users”/>
</appSettings>
check for the cache initially if it exists I directly cast it to a dataset, if
not create a cache again.
daUsers.Fill(dsUsers,”tblUsers”);
with a file “Master.xml”. This “Master.xml” is a XML file that contains Master
data of “tblQualifications” and “tbllocations”. I have used “Server.MapPath” to
get the physical path of the file on the server. The CacheDependancy instance
will make sure that any change in this dependency file means that you need to
recreate your cache key definition. This is a great feature to use since I can
recreate my cache only when required instead of caching the data at the page
level.
System.Web.Caching.CacheDependency(Server.MapPath(”Master.xml”)) ,
DateTime.Now.AddSeconds(45),TimeSpan.Zero);
Also note how we could use trace within to add my own statements.
HttpContext.Current.Trace.Write(”from Database..”);
<%@ import Namespace=”System” %>
<%@ import Namespace=”System.Data” %>
<%@ import Namespace=”System.Data.SqlClient” %>
<%@ import Namespace=”System.Configuration” %>
<%@ import Namespace=”System.Web” %>
<%@ import Namespace=”System.Collections” %>
<%@ import Namespace=”System.IO” %>
<script runat=”server”>
void Page_Load(Object sender, EventArgs e)
{
DataSet dsUsers;
try
{
if(Cache["Users"]==null)
{
SqlConnection cn;
dsUsers = new DataSet(”new”);
cn = new SqlConnection(ConfigurationSettings.AppSettings.Get(”conn”));
SqlDataAdapter daUsers;
daUsers = new SqlDataAdapter(”Select * from tblUsers”,cn);
cn.Open();
daUsers.Fill(dsUsers,”tblUsers”);
//Update the cache object
Cache.Insert(”Users”,dsUsers, new System.Web.Caching.CacheDependency(
Server.MapPath(”Master.xml”)), DateTime.Now.AddSeconds(45),TimeSpan.Zero);
HttpContext.Current.Trace.Write(DateTime.Now.AddSeconds(45).ToString() + ”
is expiry time..”);
cn.Close();
cn.Dispose();
HttpContext.Current.Trace.Write(”from Database..”);
lblChange.Text =”From the database….”;
}
else
{
HttpContext.Current.Trace.Write(”From cache..”);
lblChange.Text =”From the cache….”;
dsUsers= (DataSet) Cache["Users"];
}
dlUsers.DataSource =dsUsers;
dlUsers.DataMember = dsUsers.Tables[0].TableName ;
//lblChange.Text += Server.MapPath(”Master.xml”);
this.DataBind();
}
catch(Exception ex)
{
lblChange.Text = ex.Message;
}
}
</script>
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN” >
<html>
<head>
<title>Cache Dependency Tester</title>
<meta content=”Microsoft Visual Studio 7.0″ name=”GENERATOR” />
<meta content=”C#” name=”CODE_LANGUAGE” />
<meta content=”JavaScript” name=”vs_defaultClientScript” />
<meta content=”http://schemas.microsoft.com/intellisense/ie5″
name=”vs_targetSchema” />
</head>
<body ms_positioning=”GridLayout”>
<form id=”Form1″ method=”post” runat=”server”>
<asp:DataList id=”dlUsers” style=”Z-INDEX: 101; LEFT: 44px; POSITION:
absolute; TOP: 104px” runat=”server” Height=”148px” Width=”343px”
BorderWidth=”1px” GridLines=”Horizontal” CellPadding=”4″
BackColor=”White” ForeColor=”Black” BorderStyle=”None”
BorderColor=”#CCCCCC”>
<SelectedItemStyle font-bold=”True” forecolor=”White”
backcolor=”#CC3333″></SelectedItemStyle>
<FooterStyle forecolor=”Black”
backcolor=”#CCCC99″></FooterStyle>
<HeaderStyle font-bold=”True” forecolor=”White”
backcolor=”#333333″></HeaderStyle>
<ItemTemplate>
<table>
<tr>
<td>
<%#DataBinder.Eval(Container.DataItem,”UserId”)%></td>
<td>
<%#DataBinder.Eval(Container.DataItem,”FirstName”)%></td>
<td>
<%#DataBinder.Eval(Container.DataItem,”LastName”)%></td>
</tr>
</table>
</ItemTemplate>
</asp:DataList>
<asp:Label id=”lblChange” style=”Z-INDEX: 102; LEFT: 46px; POSITION:
absolute; TOP: 63px” runat=”server” Height=”28px”
Width=”295px”></asp:Label>
<asp:Button id=”btnMaster” style=”Z-INDEX: 103; LEFT: 50px; POSITION:
absolute; TOP: 293px” onclick=”btnMaster_Click” runat=”server” Text=”Refresh
Master”></asp:Button>
</form>
</body>
</html>
we need another page that will overwrite this “Master.xml” on click of a button
for which the code snippet is as follows. This ideally should be our master
maintenance page that adds/updates Master records in database and overwrites
the XML. But to make it easy I have just written an overwriting sample.
<%@ import Namespace=”System” %>
<%@ import Namespace=”System.Data” %>
<%@ import Namespace=”System.Data.SqlClient” %>
<script runat=”server”>
void btnMaster_Click(Object sender, EventArgs e)
{
//Call save function
this.Save();
}
void Save()
{
try
{
SqlConnection cn;
DataSet dsUsers = new DataSet(”Users”);
//I have used this to get the Connectionstring from the
//Configuration file.
cn = new SqlConnection(ConfigurationSettings.AppSettings.Get(”conn”));
SqlDataAdapter daQualification;
SqlDataAdapter daLocations;
daQualification = new SqlDataAdapter(”Select * from tblqualifications”,cn);
daLocations = new SqlDataAdapter(”Select * from tblLocations”,cn);
cn.Open();
daQualification.Fill(dsUsers,”tblqualifications”);
daLocations.Fill(dsUsers,”tblLocations”);
HttpContext.Current.Trace.Write(”Masters data up..”);
//Overwrite the XML file. Also please read MSDN on the overloaded parameters
for WriteXml
dsUsers.WriteXml(HttpContext.Current.Server.MapPath
“Master.xml”),XmlWriteMode.WriteSchema);
cn.Close();
cn.Dispose();
}
catch(Exception ex)
{
throw new Exception(ex.Message);
}
}
</script>
<html>
<head>
</head>
<body>
<form runat=”server” ID=”Form1″>
<span>
<table>
<tbody>
<tr>
<td>
<label id=”lblRefresh” runat=”server”>
Rewrite the XML File by clicking the buttonbelow.</label>
</td>
</tr>
<tr align=”middle”>
<td>
<asp:Button id=”btnMaster” onclick=”btnMaster_Click” runat=”server”
Text=”Write XML”></asp:Button>
</td>
</tr>
</tbody>
</table>
</span>
</form>
</body>
</html>
and other that overwrites the dependency file, create two instance of browser
and open the cache implementation page and note for trace, label text; open the
other instance of browser with the page which overwrites the XML. Note the
former, the first time it fetches data from the database and the subsequent
request will be from cache till your expiration time of 45 seconds is reached
or anyone overwrites or changes the “Master.xml” file. Also give a look on
Timespan parameter since you have a concept of Sliding expiration that can also
be implemented. Keep refreshing the first page and you will see that trace
indicates the cached page retrieval. Click the overwrite XML button on the
latter page that would overwrite the XML and again refresh the former page to
note that the data is retrieved from database. Though in this example I have
not shown any direct relation between the cached data and the dependency file
(like get values from dependency file and merge with cached object etc) in
terms of integrated usage, this could very easily be designed and implemented.
Dependency caching is a powerful technique that .NET supports and should be
utilized wherever applicable.
Caching is a technique that definitely improves the performance of web
applications if one is careful to have a balance in terms of which data needs
to be cached and parameter values for expiration policy.