Back to
swartzentruber.net
 

Code Examples in VB.NET (Framework 1.1)

Code Samples Home


cachemanager

createlinkspagewithxml

hideorshowaddnew

lookupusercontrolwithevents

lookupusercontrolwithevents2

looplistboxforselected

tourcalendarinternational

tourcalendarwithdetail

tourcalendarwithdetail2

tourcalendarwithdetail3

usercontrol_sessionview

usertablestoarraylist_via_sqldmo

Cache Manager
Summary & Comments

When I first started working with ASP.NET, one of the new features I was most excited about was the built-in caching.  Having worked on a few e-commerce sites where much time was spent in optimizing for scalability, I had used several "roll-your-own" techniques for caching.  Typically this just meant storing some dynamically generated HTML in a database table with a key of some sort.  Then I would schedule purges on some schedule and/or have methods of deleting cached items on demand.

The built-in caching available with ASP.NET puts a fairly robust caching engine at your disposal, but after working with it for awhile I confess there are still some things lacking.  The biggest one is that although you can cache items based on dependencies, that dependency cannot be tied to an action on a database in any kind of transparent way.  (See the help for more on the CacheDependency class, typically it would be an xml file, a change to a directory, a change to another cached item, etc.) 

There are hacks out there, such as using triggers to generate/delete a file of some sort or maybe a Web service that monitors your database for changes and changes some dependency file for you.  I'm sure there are ways to do it.  Just nothing very clean and "built-in."  I've heard through the grapevine that database dependencies are high on the list of added features in the next version.

I would like a dependency on a table row tied to a primary key or maybe on a stored procedure resultset, if you want to get really fancy.  For instance, in any kind of online store scenerio, a piece of product usually has at least one table row (and often other related rows) used to store product information.  Since this doesn't change often, it is an ideal candidate for caching.  However, when something changes, for instance a price, available stock, color or whatever, you want that product page updated as close to real-time as possible.

Here is another point to consider with page-level caching.  I was going to just make it easy on myself and cache the whole product page.  However, that page also has some search functionality in code-behind.  And you guessed it, if I cache the whole page, the search doesn't run because code is not executed when the page is displayed from the cache.  So back to the drawing board on that one.

Luckily, it is relatively simple to just manage the caching yourself using Cache.Add or Cache.Insert.  I prefer the latter because Cache.Add throws an exception if there is already an object in the cache with the name you are trying to use.  Plus you have more options with Cache.Insert.  The other good thing about adding items to the cache yourself is you can name them a meaningful name, like something including a database primary key (ProductID) for instance.

In traditional ASP, I already had templates written to display data a certain way.  My presentation classes pulled data from the database and used the templates to format and return a string of HTML.  I won't argue the relative merits of using repeaters/datalists/datagrids now rather than creating HTML from scratch.  I do things both ways and probably will change some of the code in the future.  The thing is, you can cache HTML if you want or a Dataset or a Hashtable or whatever works for your app.

Whenever you want to cache, just do something like this (watch wrapping):

Dim cacheName As String = "Product:" & ProductID.ToString
Dim content As String

If Cache(cacheName) Is Nothing Then
    ' assume presentation class returns a string of html
    Dim oContent As New Presentation()
    content = oContent.GetHtml_Product(ProductID)
    oContent = Nothing
    Cache.Insert(cacheName, content, Nothing, Cache.NoAbsoluteExpiration, TimeSpan.FromHours(6))
    ' throw our content into a label
    '(could also be a literal or whatever)
    LblContent.Text = content
Else
    LblContent.Text = Convert.ToString(Cache(cacheName))
End If

This code sample is really just a simple cache viewer allowing you to view and delete these items from the cache as needed.  Not terribly elegant, but doesn't require a lot of dependency code.  I'm not sure which is worse, to output dependency files for every piece of product or manage it by hand.  Still too early in the learning curve to know for sure.

Of  course if you are okay with the page being out of date for a few hours,  your time span should be the maximum length of time you are comfortable with your page being out of date.  Any caching should get some additional scalability, all else being equal. 

Keep in mind this page has to run in the same app space as your actual site, so you might want to protect it with some authentication or something.  There are obviously much worse security threats than someone deleting cached items from your site, but by the same token you really don't want anyone on this page that shouldn't be.

The aspx page simply displays the current cache items in a datagrid.  When the page first renders, I don't show any of the cached content because it can get rather long.  Instead, there is a simple toggle linkbutton that lets you see the actual cache contents if it is in a viewable form.  This works less well for datasets or items like that, but you could extend it.  For instance, use special notation when you name your cache items and then cast them to the appropriate type when attempting to view them.

This is very elementary, but hopefully it will save somebody a little time or give you some ideas.  I'm still playing around with some dependency ideas as well, but this might do for now.

Added 5/6/2002

ASPX Code
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="Cache_Control.aspx.vb" Inherits="swartzentrubernet.Cache_Control"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>< BR> <HEAD><BR><title>Cache_Control</title>
</HEAD>
<body>
<form runat="server" id="Form1">
<p><b>The contents of the ASP.NET application cache are:</b></p>
<asp:Label id="RemoveMessage" runat="server" />
<asp:DataGrid itemstyle-verticalalign="Top" id="myDataGrid" runat="server" autogeneratecolumns="false" onitemcommand="Grid_ItemCommand">
<Columns>
<asp:TemplateColumn headertext="Remove">
<ItemTemplate>
<asp:LinkButton id="RemoveButton" text="Remove" commandname="RemoveFromCache" runat="server" />
</ItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn headertext="Name (Key)">
<ItemTemplate>
<asp:Label id="CacheItemName" runat="server" text='<%# Container.DataItem.Key %>' />
</ItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn headertext="Show">
<ItemTemplate>
<asp:LinkButton id="ShowButton" text="Show/Hide Item" commandname="Toggle" runat="server" />
</ItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn headertext="Cache Data">
<ItemTemplate>
<asp:Panel id="panItem" runat="server" visible="False">
<asp:Label id="CacheItemData" runat="server" text='<%# Container.DataItem.Value %>' />
</asp:Panel>
</ItemTemplate>
</asp:TemplateColumn>
</Columns>
</asp:DataGrid>
</form>
</body>
</HTML>
VB.NET Codebehind Code
Option Explicit On
Option Strict Off

Imports System
Imports System.Collections
Imports System.Data
Imports System.Text
Imports System.Web.UI.WebControls

Public Class Cache_Control
    Inherits System.Web.UI.Page
    Protected WithEvents RemoveMessage As System.Web.UI.WebControls.Label
    Protected WithEvents lblProp As System.Web.UI.WebControls.Label
    Protected WithEvents myDataGrid As System.Web.UI.WebControls.DataGrid

#Region " Web Form Designer Generated Code "

    'This call is required by the Web Form Designer.
     Private Sub InitializeComponent()

    End Sub

    Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
        'CODEGEN: This method call is required by the Web Form Designer
        'Do not modify it using the code editor.
        InitializeComponent()
    End Sub

#End Region

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'Put user code to initialize the page here

        If Not Page.IsPostBack Then
            BindGrid()
        End If
        
    End Sub

    Private Sub BindGrid()
        myDataGrid.DataSource = GetCachedItems()
        myDataGrid.DataBind()
    End Sub

    Sub Grid_ItemCommand(ByVal sender As Object, ByVal e As DataGridCommandEventArgs)

        Dim cmdType As String = CType(e.CommandSource, LinkButton).CommandName
        Try
            Dim myCacheItemNameLabel As Label = CType(e.Item.FindControl("CacheItemName"), Label)
            Dim myCacheItemName As String = myCacheItemNameLabel.Text
            Dim myPanItem As Panel = CType(e.Item.FindControl("panItem"), Panel)

            Select Case cmdType
                Case "RemoveFromCache"
                    Cache.Remove(myCacheItemName)
                    BindGrid()
                Case "Toggle"
                    myPanItem.Visible = Not myPanItem.Visible
            End Select
        Catch myException As Exception
            RemoveMessage.Text = "The item was not found in the cache or there was an error while removing the cached item."
        End Try

    End Sub

    Function GetCachedItems() As Hashtable
        ' Declare variables.
        Dim htCache As New Hashtable()
        Dim oItem As DictionaryEntry
        Dim strName As String

        For Each oItem In Cache
            strName = oItem.Key
            If Left(strName, 7) <> "System." And Left(strName, 5) <> "ISAPI" Then
                htCache.Add(strName, oItem.Value)
            End If
        Next

        Return htCache

    End Function



End Class

© 1999-2008 swartzentruber.net