Caching in ASP.Net

3 views
Skip to first unread message

DOTNETPRO

unread,
Jun 19, 2006, 5:16:29 AM6/19/06
to ALLABOUTDOTNET
What is Caching?

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.

Why Caching?

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.

The .NET Advantage

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.

* VaryByParam can take various "key" parameter names in query string.
Also there are other attributes like VaryByHeader, VaryByCustom etc.
Please refer to MSDN for more on this.

2. Fragment Caching

Even though this definition refers to caching portion/s of page, it is
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.

<%@ Control Language="C#"%>
<%@ 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>

Add the following to an *.aspx file. Please note the way "Register" tag
is 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.

<%@ Page Language="C#" Trace="true" %>
<%@ 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

With Page level Output caching one cannot cache objects between pages
within 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.

In almost all Web applications there could be numerous master tables
that 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?

We cannot hold these objects in Cache for entire application instance,
because if anybody changes data in these tables one has to also refresh
the cache. It is here that CacheDependancy can be used.

* Even though these tables are less frequently used for updates, they
are extensively used in our select statements through out the
applications.

Find below the snippet that uses CacheDependancy. Here what I have done
is 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.

Add database connection value in Web.Config and change the value as per
your setup.

<appSettings>
<add key="conn" value="Data
Source=vishnu;trusted_connection=yes;Initial Catalog=Users"/>
</appSettings>

First I get the dataset into which I fill the user list. But before
this I 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");

I create the cache with "Users" as key using Cache.Insert* and link
this 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.


Cache.Insert("Users",dsUsers,new
System.Web.Caching.CacheDependency(Server.MapPath("Master.xml")) ,
DateTime.Now.AddSeconds(45),TimeSpan.Zero);

* For other overloaded parameters refer MSDN.

Also note how we could use trace within to add my own statements.

HttpContext.Current.Trace.Write("from Database..");

<%@ Page Language="c#" Trace="true" %>
<%@ 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 created the page that initiates and uses the Cache. For testing
purpose 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.

<%@ Page Language="C#" Trace="true"%>
<%@ 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>

Now once you have created the above pages i.e. one that implements
caching 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.

Conclusion

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.

Reply all
Reply to author
Forward
0 new messages