WEBSITE

Microsoft ASP.NET 3.5 : Caching Application Data (part 4) - Designing a Custom Dependency, A Cache Dependency for XML Data

1/12/2013 3:22:51 AM

4. Designing a Custom Dependency

Let’s say it up front: writing a custom cache dependency object is no picnic. You should have a very good reason to do so, and you should carefully design the new functionality before proceeding. As mentioned, from ASP.NET 2.0 onward the CacheDependency class is inheritable—you can derive your own class from it to implement an external source of events to invalidate cached items.

The base CacheDependency class handles all the wiring of the new dependency object to the ASP.NET cache and all the issues surrounding synchronization and disposal. It also saves you from implementing a start-time feature from scratch—you inherit that capability from the base class constructors. (The start-time feature allows you to start tracking dependencies at a particular time.)

Let’s start reviewing the original limitations of CacheDependency that have led to removing the sealed attribute on the class, making it fully inheritable.

What Cache Dependencies Cannot Do in ASP.NET 1.x

In ASP.NET 1.x, a cached item can be subject to four types of dependencies: time, files, other items, and other dependencies. The ASP.NET 1.x Cache object addresses many developers’ needs and has made building in-memory collections of frequently accessed data much easier and more effective. However, this mechanism is not perfect, nor is it extensible.

Let’s briefly consider a real-world scenario. What type of data do you think a distributed data-driven application would place in the ASP.NET Cache? In many cases, it would simply be the results of a database query. But unless you code it yourself—which can really be tricky—the object doesn’t support database dependency. A database dependency would invalidate a cached result set when a certain database table changes. In ASP.NET 1.x, the CacheDependency class is sealed and closed to any form of customization that gives developers a chance to invalidate cached items based on user-defined conditions.

As far as the Cache object is concerned, the biggest difference between ASP.NET 1.x and newer versions is that newer versions support custom dependencies. This was achieved by making the CacheDependency class inheritable and providing a made-to-measure SqlCacheDependency cache that provides built-in database dependency limited to SQL Server 7.0 and later.

Extensions to the CacheDependency Base Class

To fully support derived classes and to facilitate their integration into the ASP.NET caching infrastructure, a bunch of new public and protected members have been added to the CacheDependency class. They are summarized in Table 6.

Table 6. New Members of the CacheDependency Class
MemberDescription
DependencyDisposeProtected method. It releases the resources used by the class.
GetUniqueIDPublic method. It retrieves a unique string identifier for the object.
NotifyDependencyChangedProtected method. It notifies the base class that the dependency represented by this object has changed.
SetUtcLastModifiedProtected method. It marks the time when a dependency last changed.
UtcLastModifiedPublic read-only property. It gets the time when the dependency was last changed. This property also exists in version 1.x, but it is not publicly accessible.

As mentioned, a custom dependency class relies on its parent for any interaction with the Cache object. The NotifyDependencyChanged method is called by classes that inherit CacheDependency to tell the base class that the dependent item has changed. In response, the base class updates the values of the HasChanged and UtcLastModified properties. Any cleanup code needed when the custom cache dependency object is dismissed should go into the DependencyDispose method.

Getting Change Notifications

As you might have noticed, nothing in the public interface of the base CacheDependency class allows you to insert code to check whether a given condition—the heart of the dependency—is met. Why is this? The CacheDependency class was designed to support only a limited set of well-known dependencies—against changes to files or other items.

To detect file changes, the CacheDependency object internally sets up a file monitor object and receives a call from it whenever the monitored file or directory changes. The CacheDependency class creates a FileSystemWatcher object and passes it an event handler. A similar approach is used to establish a programmatic link between the CacheDependency object and the Cache object and its items. The Cache object invokes a CacheDependency internal method when one of the monitored items changes. What does this all mean to the developer?

A custom dependency object must be able to receive notifications from the external data source it is monitoring. In most cases, this is really complicated if you can’t bind to existing notification mechanisms (such as file system monitor or SQL Server 2005 notifications). When the notification of a change in the source is detected, the dependency uses the parent’s infrastructure to notify the cache of the event. We’ll consider a practical example in a moment.

The AggregateCacheDependency Class

Starting with ASP.NET 2.0, not only can you create a single dependency on an entry, you can also aggregate dependencies. For example, you can make a cache entry dependent on both a disk file and a SQL Server table. The following code snippet shows how to create a cache entry, named MyData, that is dependent on two different files:

// Creates an array of CacheDependency objects
CacheDependency dep1 = new CacheDependency(fileName1);
CacheDependency dep2 = new CacheDependency(fileName2);
CacheDependency deps[] = {dep1, dep2};

// Creates an aggregate object
AggregateCacheDependency aggDep = new AggregateCacheDependency();
aggDep.Add(deps);
Cache.Insert("MyData", data, aggDep)

Any custom cache dependency object, including SqlCacheDependency, inherits CacheDependency, so the array of dependencies can contain virtually any type of dependency.

The AggregateCacheDependency class is built as a custom cache dependency object and inherits the base CacheDependency class.

5. A Cache Dependency for XML Data

Suppose your application gets some key data from a custom XML file and you don’t want to access the file on disk for every request. So you decide to cache the contents of the XML file, but still you’d love to detect changes to the file that occur while the application is up and running. Is this possible? You bet. You arrange a file dependency and you’re done.

In this case, though, any update to the file that modifies the timestamp is perceived as a critical change. As a result, the related entry in the cache is invalidated and you’re left with no choice other than re-reading the XML data from the disk. The rub here is that you are forced to re-read everything even if the change is limited to a comment or to a node that is not relevant to your application.

Because you want the cached data to be invalidated only when certain nodes change, you create a made-to-measure cache dependency class to monitor the return value of a given XPath expression on an XML file.

Note

If the target data source provides you with a built-in and totally asynchronous notification mechanism (such as the command notification mechanism of SQL Server 2005), you just use it. Otherwise, to detect changes in the monitored data source, you can only poll the resource at a reasonable rate.


Designing the XmlDataCacheDependency Class

To better understand the concept of custom dependencies, think of the following example. You need to cache the inner text of a particular node in an XML file. You can define a custom dependency class that caches the current value upon instantiation and reads the file periodically to detect changes. When a change is detected, the cached item bound to the dependency is invalidated.

Note

Admittedly, polling might not be the right approach for this particular problem. Later on, in fact, I’ll briefly discuss a more effective implementation. Be aware, though, that polling is a valid and common technique for custom cache dependencies.


A good way to poll a local or remote resource is through a timer callback. Let’s break the procedure into a few steps:

1.
The custom XmlDataCacheDependency class gets ready for the overall functionality. It initializes some internal properties and caches the polling rate, file name, and XPath expression to find the subtree to monitor.

2.
After initialization, the dependency object sets up a timer callback to access the file periodically and check contents.

3.
In the callback, the return value of the XPath expression is compared to the previously stored value. If the two values differ, the linked cache item is promptly invalidated.

There’s no need for the developer to specify details on how the cache dependency is broken or set up. The CacheDependency class in ASP.NET 2.0 takes care of it entirely.

Note

If you’re curious to know how the Cache detects when a dependency is broken, read on. When an item bound to a custom dependency object is added to the Cache, an additional entry is created and linked to the initial item. NotifyDependencyChanged simply dirties this additional element which, in turn, invalidates the original cache item. Figure 3 illustrates the connections.

Figure 3. Custom dependencies use helper cache entries to invalidate any items under their control.



Implementing the Dependency

The following source code shows the core implementation of the custom XmlDataCacheDependency class:

public class XmlDataCacheDependency : CacheDependency
{
    // Internal members
    static Timer _timer;
    int _pollSecs = 10;
    string _fileName;
    string _xpathExpression;
    string _currentValue;

   public XmlDataCacheDependency(string file, string xpath, int pollTime)
   {
      // Set internal members
      _fileName = file;
      _xpathExpression = xpath;
      _pollSecs = pollTime;

      // Get the current value
      _currentValue = CheckFile();

      // Set the timer
      if (_timer == null) {
         int ms = _pollSecs * 1000;
         TimerCallback cb = new TimerCallback(XmlDataCallback);
         _timer = new Timer(cb, this, ms, ms);
      }
   }
   public string CurrentValue
   {
      get { return _currentValue; }
   }

   public void XmlDataCallback(object sender)
   {
      // Get a reference to THIS dependency object
      XmlDataCacheDependency dep = (XmlDataCacheDependency) sender;

      // Check for changes and notify the base class if any are found
      string value = CheckFile();
      if (!String.Equals(_currentValue, value))
          dep.NotifyDependencyChanged(dep, EventArgs.Empty);
   }

   public string CheckFile()
   {
      // Evaluates the XPath expression in the file
      XmlDocument doc = new XmlDocument();
      doc.Load(_fileName);
      XmlNode node = doc.SelectSingleNode(_xpathExpression);

      return node.InnerText;
   }

   protected override void DependencyDispose()
   {
      // Kill the timer and then proceed as usual
      _timer.Dispose();
      _timer = null;
      base.DependencyDispose();
   }
}


					  

When the cache dependency is created, the file is parsed and the value of the XPath expression is stored in an internal member. At the same time, a timer is started to repeat the operation at regular intervals. The return value is compared against the value stored in the constructor code. If the two are different, the NotifyDependencyChanged method is invoked on the base CacheDependency class to invalidate the linked content in the ASP.NET Cache.

Testing the Custom Dependency

How can you use this dependency class in a Web application? It’s as easy as it seems—you just use it in any scenario where a CacheDependency object is acceptable. For example, you create an instance of the class in the Page_Load event and pass it to the Cache.Insert method:

protected const string CacheKeyName = "MyData";
protected void Page_Load(object sender, EventArgs e)
{
   if (!IsPostBack)
   {
      // Create a new entry with a custom dependency
      XmlDataCacheDependency dep = new XmlDataCacheDependency(
          Server.MapPath("employees.xml"),
          "MyDataSet/NorthwindEmployees/Employee[employeeid=3]/lastname",
          1);
      Cache.Insert(CacheKeyName, dep.CurrentValue, dep);
   }

   // Refresh the UI
   Msg.Text = Display();
}

You write the rest of the page as usual, paying close attention to accessing the specified Cache key. The reason for this is that because of the dependency, the key could be null. Here’s an example:

protected string Display()
{
    object o = Cache[CacheKeyName];
    if (o == null)
        return "[No data available--dependency broken]";
    else
        return (string) o;
}

The XmlDataCacheDependency object allows you to control changes that occur on a file and decide which are relevant and might require you to invalidate the cache. The sample dependency uses XPath expressions to identify a subset of nodes to monitor for changes. For simplicity, only the first node of the output of the XPath expression is considered. The sample XPath expression monitors in the sample employees.xml file the lastname node of the subtree where employeeid=3:

<MyDataSet>
    <NorthwindEmployees>
        ...
        <Employee>
            <employeeid>3</employeeid>
            <lastname>Leverling</lastname>
            <firstname>Janet</firstname>
            <title>Sales Representative</title>
        </Employee>
        ...
    </NorthwindEmployees>
</MyDataSet>

The XML file, the cache dependency object, and the preceding sample page produce the output shown in Figure 4.

Figure 4. The custom dependency object in action in a sample page.

The screen shot at the top is what users see when they first invoke the page. The page at the bottom is what they get when the cached value is invalidated because of a change in the monitored node of the XML file. Note that changes to other nodes, except lastname where employeeid=3, are blissfully ignored and don’t affect the cached value.

Note

I decided to implement polling in this sample custom dependency because polling is a pretty common, often mandatory, approach for custom dependencies. However, in this particular case polling is not the best option. You could set a FileSystemWatcher object and watch for changes to the XML file. When a change is detected, you execute the XPath expression to see whether the change is relevant for the dependency. Using an asynchronous notifier, if available, results in much better performance.

Other  
 
Video
Top 10
SG50 Ferrari F12berlinetta : Prancing Horse for Lion City's 50th
The latest Audi TT : New angles for TT
Era of million-dollar luxury cars
Game Review : Hearthstone - Blackrock Mountain
Game Review : Battlefield Hardline
Google Chromecast
Keyboards for Apple iPad Air 2 (part 3) - Logitech Ultrathin Keyboard Cover for iPad Air 2
Keyboards for Apple iPad Air 2 (part 2) - Zagg Slim Book for iPad Air 2
Keyboards for Apple iPad Air 2 (part 1) - Belkin Qode Ultimate Pro Keyboard Case for iPad Air 2
Michael Kors Designs Stylish Tech Products for Women
REVIEW
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
Popular Tags
Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Exchange Server Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe Photoshop CorelDRAW X5 CorelDraw 10 windows Phone 7 windows Phone 8 Iphone