Simplify the use of the EPiServer Cache

I am a huge fan of PostSharp. One of the reasons is that I don’t like to do the same thing over and over again.

I use the EPiServer Cachemanager quite a lot, but do not like to keep writing the same code over and over again. If not in cache, get stuff, put it in cache, return stuff, and so on.

With PostSharp you can write an Aspect that does all of this for you. You can add the aspect on a complete Assembly, a Class or a Method.

I will not go into detail about PostSharp and how it works, check it out and make your life a lot easier.

The free version supports this Caching Aspect.

Code
I have created a base class which inherits from the "OnMethodBoundaryAspect" form PostSharp.
I have added some compile time validation so the aspect can only be used on methods that actually return something.

In the OnEntry interceptor I create a unique key based on the namespace, method name and parameters. 
Next I check i there is something in the cache or this key.
If so, PostSharp changes the flow of the method and returns the cached content.
If not,  PostSharp stores the key, and executes the method.

public override void OnEntry(MethodExecutionArgs args)
        {
            if (args == null)
            {
                throw new ArgumentNullException("args");
            }

            // Compute the cache key. 
            string cacheKey = GetCacheKey(this.methodName, args.Instance, args.Arguments);

            // Fetch the value from the cache. 
            object value = CacheManager.RuntimeCacheGet(cacheKey);

            if (value != null)
            {
                // The value was found in cache. Don't execute the method. Return immediately.
                args.ReturnValue = value;
                args.FlowBehavior = FlowBehavior.Return;
            }
            else
            {
                // The value was NOT found in cache. Continue with method execution, but store 
                // the cache key so that we don't have to compute it in OnSuccess.
                args.MethodExecutionTag = cacheKey;
            }
        }

In the OnSucess interceptor, the value is added to the cache with the stored key.

public override void OnSuccess(MethodExecutionArgs args)
        {
            if (args == null)
            {
                throw new ArgumentNullException("args");
            }

            if (this.Dependency == null)
            {
                return;
            }

            string cacheKey = (string)args.MethodExecutionTag;
            CacheManager.RuntimeCacheInsert(cacheKey, args.ReturnValue, this.Dependency);
        }

I have added a Dependency property that you can set with e.g. the VersionKey, so it gets invalidated when the EPiServer cache is updated on e.g. a publish of a page. Or you can add a Page Dependency.

public override CacheDependency Dependency
        {
            get
            {
                return new CacheDependency(null, new[] { DataFactoryCache.VersionKey });
            }
        }
public override CacheDependency Dependency
        {
            get
            {
                PageBase page = HttpContext.Current.Handler as PageBase;

                return page == null ? null : DataFactoryCache.CreateDependency(page.CurrentPage.PageLink);
            }
        }

That’s it basically. Complete code can be found on GitHub

2 thoughts on “Simplify the use of the EPiServer Cache

    1. Well, it’s not very readable, as it is decompiled, but a sample from a Unit Test I’m writing (when I finally figure out how to fake
      the EPiServer Cache, and how to stop FakeItEasy to stop faking my InstanceMethod helpers)

      Code before:
      public static int InstanceMethodWithParameter(string param)
      {
      return invocations++;
      }

      The code after:
      public static int InstanceMethodWithParameter(string param)
      {
      MethodExecutionArgs args = new MethodExecutionArgs((object) null, (Arguments) new Arguments()
      {
      Arg0 = param
      });
      // ISSUE: reference to a compiler-generated field
      \u003C\u003Ez__a_1.a1.OnEntry(args);
      int num;
      if (args.FlowBehavior == FlowBehavior.Return)
      {
      num = (int) args.ReturnValue;
      }
      else
      {
      num = CachingSpecs.invocations++;
      args.ReturnValue = (object) num;
      // ISSUE: reference to a compiler-generated field
      \u003C\u003Ez__a_1.a1.OnSuccess(args);
      }
      return num;
      }

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s