views:

311

answers:

5

I want a ASP.NET cache item to be recycled when a specific file is touched, but the following code is not working:

                       HttpContext.Current.Cache.Insert(
                            "Key",
                            SomeObject,
                            new CacheDependency(Server.MapPath("SomeFile.txt")),
                            DateTime.MaxValue,
                            TimeSpan.Zero,
                            CacheItemPriority.High,
                            null);

"SomeFile.txt" does not seem to be checked when I'm hitting the cache, and modifying it does not cause this item to be invalidated.

What am I doing wrong?

A: 

I think you'll need to specify a path:

var d = new CacheDependency(Server.MapPath("SomeFile.txt"));

Prepend with ~\App_Data as needed.

Mark Brackett
I am already passing a full path, sorry the contrived example was not clear.
FlySwat
A: 

Your code looks fine to me. However, beyond this snippet, anything could be going on.

  • Are you re-inserting on every postback by any chance?

  • Try making your cache dependency a class field, and checking it on every postback. Modify the file in between and see if it ever registers as "Changed". e.g.:

    public partial class _Default : System.Web.UI.Page
    {
       CacheDependency dep;
    
    
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            dep = new CacheDependency(Server.MapPath("SomeFile.txt"));
            HttpContext.Current.Cache.Insert(
                        "Key",
                        new Object(),
                        dep,
                        DateTime.MaxValue,
                            TimeSpan.Zero, CacheItemPriority.High, null);
        }
    
    
    
    if (dep.HasChanged)
        Response.Write("changed!");
    else
        Response.Write("no change :(");   }}
    
womp
Definitely not reinserting on postback. This is actually abstracted way far away in a library.
FlySwat
+1  A: 

Is ASP.NET running under an account with the proper permissions for the file specified in the CacheDependency? If not, then this might be one reason why the CacheDependency is not working properly.

Chris Symons
Yes it is, because it parses that file to create the original cached object.
FlySwat
A: 

The only way I am able to reproduce this behavior is if the path provided to the constructor of CacheDependency does not exist. The CacheDependency will not throw an exception if the path doesn't exist, so it can be a little misleading.

Rex M
However, I open this file (and use the same exact path variable) one line up and read it into the cache object.
FlySwat
+3  A: 

Problem Solved:

This was a unique and interesting problem, so I'm going to document the cause and solution here as an Answer, for future searchers.

Something I left out in my question was that this cache insertion was happening in a service class implementing the singleton pattern.

In a nutshell:

public class Service 
{
        private static readonly Service _Instance = new Service();
        static Service () { }
        private Service () { }

        public static Service Instance
        {
            get { return _Instance; }
        }

        // The expensive data that this service exposes      
        private someObject _data = null;

        public someObject Data
        {
            get
            {
                 if (_data == null)
                     loadData();
                 return _data;
            }
        }


        private void loadData()
        {
            _data = GetFromCache();
            if (_data == null)
            {
                 // Get the data from our datasource
                 _data = ExpensiveDataSourceGet();

                 // Insert into Cache
                 HttpContext.Current.Cache.Insert(etc);
            }
        }
}

It may be obvious to some, but the culprit here is lazy loading within the singleton pattern. I was so caught up thinking that the cache wasn't being invalidated, that I forgot that the state of the singleton would be persisted for as long as the worker process was alive.

Cache.Insert has an overload that allows you to specify a event handler for when the cache item is removed, my first test was to create a dummy handler and set a breakpoint within it. Once I saw that the cache was being cleared, I realized that "_data" was not being reset to null, so the next request to the singleton loaded the lazy loaded value.

In a sense, I was double caching, though the singleton cache was very short lived, but long enough to be annoying.

The solution?

 HttpContext.Current.Cache.Insert(
     "Key",
      SomeObject,
      new CacheDependency(Server.MapPath("SomeFile.txt")),
      DateTime.MaxValue,
      TimeSpan.Zero,
      CacheItemPriority.High,
      delegate(string key, object value, CacheItemRemovedReason reason)
      {
          _data = null;
      }
 );

When the cache is cleared, the state within the singleton must also be cleared...problem solved.

Lesson learned here? Don't put state in a singleton.

FlySwat