How to: Notify an Application When an Item Is Removed from the Cache

In most cache scenarios, when an item is removed from the cache, you do not need to put it back into the cache until it is needed again. The typical development pattern is to always check the cache for the item before using it. If the item is in the cache, you use it. If it is not in the cache, you retrieve the item again and add it back to the cache.

However, in some cases it is useful for your application to be notified when an item is removed from the cache. For example, you might have a cached report that takes considerable processing time to create. When it is removed from the cache, you want it to be regenerated and put into the cache immediately so that the next time it is requested, the user does not have to wait for it to be processed.

To enable notification of items being removed from the cache, ASP.NET provides the CacheItemRemovedCallback delegate. The delegate defines the signature to use when you write an event handler that is called in response to an item being removed from the cache. ASP.NET also provides the CacheItemRemovedReason enumeration, which is used to specify the reason for cache item removal.

Typically, you implement the callback by creating a handler in a business object that manages the particular cache data that you are trying to retrieve. For example, you might have a ReportManager object that has two methods, GetReport and CacheReport. The GetReport report method checks the cache to see if the report is already cached and if it is not, the method regenerates the report and caches it. The CacheReport method has the same function signature of the CacheItemRemovedCallback delegate; when the report is removed from cache, ASP.NET calls the CacheReport method, which then re-adds it to the cache.

To notify an application when an item is removed from the cache

  1. Create a class that is responsible for retrieving the item from the cache and handling the callback method to add the item back into the cache.

  2. In the class, create a method that adds an item to the cache.

  3. In the class, create a method that gets an item from the cache.

  4. Create a method that handles the cache item removal callback. The method must have the same function signature as the CacheItemRemovedCallback delegate. In the method, perform the logic you want to run when the item is removed from the cache, such as regenerating the item and adding it back into the cache.

To test the cache item callback

  1. Create an ASP.NET Web page that calls the method in your class that adds the item to the cache.

    The following code example shows how to call the GetReport method of the ReportManager class (defined in the example that follows the procedure). It then displays the report in a Label control named Label1 during the page's Page_Load method.

    protected void Page_Load(object sender, EventArgs e)
    {
        this.Label1.Text = ReportManager.GetReport();
    }
    
    Protected Sub Page_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs) Handles Me.Load
        Me.Label1.Text = ReportManager.GetReport()
    End Sub
    
  2. Request the ASP.NET page in a browser and view the report.

    The report is created the first time the page is requested, and subsequent requests will access the report from the cache until the report is removed.

Example

The following code example shows a complete class named ReportManager that handles notification when an item is deleted from the cache. The class manages a report in the form of a string that represents a long-running process.

Although the example uses a class declared as static (Shared in Visual Basic), it is not required that you use a static class. However, the method that will handle the callback must exist when the cache item is deleted. For example, you should not implement the callback handler in an ASP.NET page, because the page might have been disposed of by the time the item is deleted from the cache and therefore the method to handle the callback will not be available. Using a static class for the method that handles the callback ensures that the method will still exist when the item is removed from cache. However, the drawback to a static class is that all static methods need to be thread safe.

Caution noteCaution

Do not set the CacheItemRemovedCallback to a method in a page. In addition to a page method not being available for a callback after the page is disposed of, pointing the callback to a page method can prevent the memory used by the page from being reclaimed by garbage collection. This happens because the callback contains a reference to the page and the garbage collector will not remove an item from memory if the item has any references. During periods of application load, this could cause memory to be used up very quickly.

The example class includes these features:

  • A private member for tracking whether the report has been removed from the cache.

  • A method named CacheReport that adds an item to the cache under the name MyReport and sets the item to expire one minute after being added to the cache. The method also passes the ReportRemovedCallback method to the onRemoveCallback parameter, which registers the ReportRemoveCallback method so it is called when the item is deleted from cache.

  • A method named GetReport that gets an item from the cache. The method determines whether the item named MyReport exists in the cache. If the item does not exist, the method calls CacheReport, which adds the item to the cache.

  • A method named ReportRemovedCallback that handles the cache item removal callback. ReportRemovedCallback has the same function signature as the CacheItemRemovedCallback delegate. The method sets the variable _reportRemovedFromCache to true and then adds the item back to the cache via the CacheReport method.

using System;
using System.Web;
using System.Web.Caching;
public static class ReportManager
{
    private static bool _reportRemovedFromCache = false;
    static ReportManager()
    { }

    public static String GetReport()
    {
        lock (typeof(ReportManager))
        {
            if (HttpContext.Current.Cache["MyReport"] != null)
                return (string)HttpRuntime.Cache["MyReport"];
            else
            {
                CacheReport();
                return (string)HttpRuntime.Cache["MyReport"];
            }
        }
    }

    public static void CacheReport()
    {
        lock (typeof(ReportManager))
        {
            HttpContext.Current.Cache.Add("MyReport",
                CreateReport(), null, DateTime.MaxValue,
                new TimeSpan(0, 1, 0), 
                System.Web.Caching.CacheItemPriority.Default,
                ReportRemovedCallback);
        }
    }

    private static string CreateReport()
    {
        System.Text.StringBuilder myReport = 
            new System.Text.StringBuilder();
        myReport.Append("Sales Report<br />");
        myReport.Append("2005 Q2 Figures<br />");
        myReport.Append("Sales NE Region - $2 million<br />");
        myReport.Append("Sales NW Region - $4.5 million<br />");
        myReport.Append("Report Generated: " + DateTime.Now.ToString() 
            + "<br />");
        myReport.Append("Report Removed From Cache: " + 
            _reportRemovedFromCache.ToString());
        return myReport.ToString();
    }

    public static void ReportRemovedCallback(String key, object value, 
        CacheItemRemovedReason removedReason)
    {
        _reportRemovedFromCache = true;
        CacheReport();
    }
}
Imports System
Imports System.Web
Imports System.Web.Caching
Public Class ReportManager
    Private Shared _reportRemovedFromCache As Boolean = False
    Shared Sub New()
    End Sub

    Private Sub New()
    End Sub

    Public Shared Function GetReport() As String
        SyncLock (GetType(ReportManager))
            If HttpContext.Current.Cache("MyReport") IsNot Nothing Then
                Return CStr(HttpRuntime.Cache("MyReport"))
            Else
                CacheReport()
                Return CStr(HttpRuntime.Cache("MyReport"))
            End If
        End SyncLock
    End Function

    Public Shared Sub CacheReport()
        SyncLock (GetType(ReportManager))
            HttpContext.Current.Cache.Add("MyReport", CreateReport(), _
            Nothing, DateTime.MaxValue, New TimeSpan(0, 1, 0), _
            System.Web.Caching.CacheItemPriority.Default, _
            AddressOf ReportRemovedCallback)
        End SyncLock
    End Sub

    Private Shared Function CreateReport() As String
        Dim myReport As New System.Text.StringBuilder()
        myReport.Append("Sales Report<br />")
        myReport.Append("2005 Q2 Figures<br />")
        myReport.Append("Sales NE Region - $2 million<br />")
        myReport.Append("Sales NW Region - $4.5 million<br />")
        myReport.Append("Report Generated: " & _
            DateTime.Now.ToString() & "<br />")
        myReport.Append("Report Removed From Cache: " _
            & _reportRemovedFromCache.ToString())
        Return myReport.ToString()
    End Function

    Public Shared Sub ReportRemovedCallback(ByVal key As String, _
            ByVal value As Object, ByVal removedReason _
            As CacheItemRemovedReason)
        _reportRemovedFromCache = True
        CacheReport()
    End Sub
End Class

See Also

Tasks

How to: Add Items to the Cache
How to: Retrieve Values of Cached Items
How to: Delete Items from the Cache in ASP.NET

Concepts

ASP.NET Caching Overview
What's New in ASP.NET Caching
Cache Configuration in ASP.NET
Caching Application Data