Robin Hames

Hints, tricks and tips relating to MS SQL Server and .NET
posts - 14 , comments - 45 , trackbacks - 0

Loosely coupled .NET Cache Provider using Dependency Injection

I have recently been reading the excellent book “Dependency Injection in .NET”, written by Mark Seemann, which I strongly recommend.

Reading the ideas around Dependency Injection made me realise that the Cache Provider code I wrote about earlier (see http://geekswithblogs.net/Rhames/archive/2011/01/10/using-the-asp.net-cache-to-cache-data-in-a-model.aspx) could be refactored to use Dependency Injection, which should produce cleaner code.

The goals are to:

  • Separate the cache provider implementation (using the ASP.NET data cache) from the consumers (loose coupling). This will also mean that the dependency on System.Web for the cache provider does not ripple down into the layers where it is being consumed (such as the domain layer).
  • Provide a decorator pattern to allow a consumer of the cache provider to be implemented separately from the base consumer (i.e. if we have a base repository, we can decorate this with a caching version). Although I used the term repository, in reality the cache consumer could be just about anything.
  • Use constructor injection to provide the Dependency Injection, with a suitable DI container (I use Castle Windsor).

The sample code for this post is available on github, https://github.com/RobinHames/CacheProvider.git

ICacheProvider

In the sample code, the key interface is ICacheProvider, which is in the domain layer.

using System;
using System.Collections.Generic;
 
namespace CacheDiSample.Domain
{
    public interface ICacheProvider<T>
    {
        T Fetch(string key, Func<T> retrieveData, DateTime? absoluteExpiry, TimeSpan? relativeExpiry);
        IEnumerable<T> Fetch(string key, Func<IEnumerable<T>> retrieveData, DateTime? absoluteExpiry, TimeSpan? relativeExpiry);
    }
}
 

This interface contains two methods to retrieve data from the cache, either as a single instance or as an IEnumerable. the second paramerter is of type Func<T>. This is the method used to retrieve data if nothing is found in the cache.

The ASP.NET implementation of the ICacheProvider interface needs to live in a project that has a reference to system.web, typically this will be the root UI project, or it could be a separate project. The key thing is that the domain or data access layers do not need system.web references adding to them.

In my sample MVC application, the CacheProvider is implemented in the UI project, in a folder called “CacheProviders”:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using CacheDiSample.Domain;
 
namespace CacheDiSample.CacheProvider
{
    public class CacheProvider<T> : ICacheProvider<T>
    {
        public T Fetch(string key, Func<T> retrieveData, DateTime? absoluteExpiry, TimeSpan? relativeExpiry)
        {
            return FetchAndCache<T>(key, retrieveData, absoluteExpiry, relativeExpiry);
        }
 
        public IEnumerable<T> Fetch(string key, Func<IEnumerable<T>> retrieveData, DateTime? absoluteExpiry, TimeSpan? relativeExpiry)
        {
            return FetchAndCache<IEnumerable<T>>(key, retrieveData, absoluteExpiry, relativeExpiry);
        }
 
        #region Helper Methods
 
        private U FetchAndCache<U>(string key, Func<U> retrieveData, DateTime? absoluteExpiry, TimeSpan? relativeExpiry)
        {
            U value;
            if (!TryGetValue<U>(key, out value))
            {
                value = retrieveData();
                if (!absoluteExpiry.HasValue)
                    absoluteExpiry = Cache.NoAbsoluteExpiration;
 
                if (!relativeExpiry.HasValue)
                    relativeExpiry = Cache.NoSlidingExpiration;
 
                HttpContext.Current.Cache.Insert(key, value, null, absoluteExpiry.Value, relativeExpiry.Value);
 
            }
            return value;
        }
 
        private bool TryGetValue<U>(string key, out U value)
        {
            object cachedValue = HttpContext.Current.Cache.Get(key);
            if (cachedValue == null)
            {
                value = default(U);
                return false;
            }
            else
            {
                try
                {
                    value = (U)cachedValue;
                    return true;
                }
                catch
                {
                    value = default(U);
                    return false;
                }
            }
        }
 
        #endregion
 
    }
}

 

The FetchAndCache helper method checks if the specified cache key exists, if it does not, the Func<U> retrieveData method is called, and the results are added to the cache.

Using Castle Windsor to register the cache provider

In the MVC UI project (my application root), Castle Windsor is used to register the CacheProvider implementation, using a Windsor Installer:

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
 
using CacheDiSample.Domain;
using CacheDiSample.CacheProvider;
 
namespace CacheDiSample.WindsorInstallers
{
    public class CacheInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                Component.For(typeof(ICacheProvider<>))
                .ImplementedBy(typeof(CacheProvider<>))
                .LifestyleTransient());
        }
    }
}
 

Note that the cache provider is registered as a open generic type.

Consuming a Repository

I have an existing couple of repository interfaces defined in my domain layer:

IRepository.cs

using System;
using System.Collections.Generic;
 
using CacheDiSample.Domain.Model;
 
namespace CacheDiSample.Domain.Repositories
{
    public interface IRepository<T>
        where T : EntityBase
    {
        T GetById(int id);
        IList<T> GetAll();
    }
}
 

IBlogRepository.cs

using System;
using CacheDiSample.Domain.Model;
 
namespace CacheDiSample.Domain.Repositories
{
    public interface IBlogRepository : IRepository<Blog>
    {
        Blog GetByName(string name);
    }
}

 

These two repositories are implemented in the DataAccess layer, using Entity Framework to retrieve data (this is not important though). One important point is that in the BaseRepository implementation of IRepository, the methods are virtual. This will allow the decorator to override them.

The BlogRepository is registered in a RepositoriesInstaller, again in the MVC UI project.

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
 
using CacheDiSample.Domain.CacheDecorators;
using CacheDiSample.Domain.Repositories;
using CacheDiSample.DataAccess;
 
namespace CacheDiSample.WindsorInstallers
{
    public class RepositoriesInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For<IBlogRepository>()
                .ImplementedBy<BlogRepository>()
                .LifestyleTransient()
                .DependsOn(new 
                                {
                                    nameOrConnectionString = "BloggingContext"
                                }));
        }
    }
}
 

Now I can inject a dependency on the IBlogRepository into a consumer, such as a controller in my sample code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
using CacheDiSample.Domain.Repositories;
using CacheDiSample.Domain.Model;
 
namespace CacheDiSample.Controllers
{
    public class HomeController : Controller
    {
        private readonly IBlogRepository blogRepository;
 
        public HomeController(IBlogRepository blogRepository)
        {
            if (blogRepository == null)
                throw new ArgumentNullException("blogRepository");
 
            this.blogRepository = blogRepository;
        }
 
        public ActionResult Index()
        {
            ViewBag.Message = "Welcome to ASP.NET MVC!";
 
            var blogs = blogRepository.GetAll();
 
            return View(new Models.HomeModel { Blogs = blogs });
        }
 
        public ActionResult About()
        {
            return View();
        }
    }
}
 

Consuming the Cache Provider via a Decorator

I used a Decorator pattern to consume the cache provider, this means my repositories follow the open/closed principle, as they do not require any modifications to implement the caching. It also means that my controllers do not have any knowledge of the caching taking place, as the DI container will simply inject the decorator instead of the root implementation of the repository.

The first step is to implement a BlogRepository decorator, with the caching logic in it. Note that this can reside in the domain layer, as it does not require any knowledge of the data access methods.

BlogRepositoryWithCaching.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using CacheDiSample.Domain.Model;
using CacheDiSample.Domain;
using CacheDiSample.Domain.Repositories;
 
namespace CacheDiSample.Domain.CacheDecorators
{
    public class BlogRepositoryWithCaching : IBlogRepository
    {
        // The generic cache provider, injected by DI
        private ICacheProvider<Blog> cacheProvider;
        // The decorated blog repository, injected by DI
        private IBlogRepository parentBlogRepository;
 
        public BlogRepositoryWithCaching(IBlogRepository parentBlogRepository, ICacheProvider<Blog> cacheProvider)
        {
            if (parentBlogRepository == null)
                throw new ArgumentNullException("parentBlogRepository");
 
            this.parentBlogRepository = parentBlogRepository;
 
            if (cacheProvider == null)
                throw new ArgumentNullException("cacheProvider");
 
            this.cacheProvider = cacheProvider;
        }
 
        public Blog GetByName(string name)
        {
            string key = string.Format("CacheDiSample.DataAccess.GetByName.{0}", name);
            // hard code 5 minute expiry!
            TimeSpan relativeCacheExpiry = new TimeSpan(0, 5, 0);
            return cacheProvider.Fetch(key, () =>
            {
                return parentBlogRepository.GetByName(name);
            },
                null, relativeCacheExpiry);
        }
 
        public Blog GetById(int id)
        {
            string key = string.Format("CacheDiSample.DataAccess.GetById.{0}", id);
 
            // hard code 5 minute expiry!
            TimeSpan relativeCacheExpiry = new TimeSpan(0, 5, 0);
            return cacheProvider.Fetch(key, () =>
            {
                return parentBlogRepository.GetById(id);
            },
                null, relativeCacheExpiry);
        }
 
        public IList<Blog> GetAll()
        {
            string key = string.Format("CacheDiSample.DataAccess.GetAll");
 
            // hard code 5 minute expiry!
            TimeSpan relativeCacheExpiry = new TimeSpan(0, 5, 0);
            return cacheProvider.Fetch(key, () =>
            {
                return parentBlogRepository.GetAll();
            },
                null, relativeCacheExpiry)
            .ToList();
        }
    }
}

 

The key things in this caching repository are:

  1. I inject into the repository the ICacheProvider<Blog> implementation, via the constructor. This will make the cache provider functionality available to the repository.
  2. I inject the parent IBlogRepository implementation (which has the actual data access code), via the constructor. This will allow the methods implemented in the parent to be called if nothing is found in the cache.
  3. I override each of the methods implemented in the repository, including those implemented in the generic BaseRepository. Each override of these methods follows the same pattern. It makes a call to the CacheProvider.Fetch method, and passes in the parentBlogRepository implementation of the method as the retrieval method, to be used if nothing is present in the cache.

Configuring the Caching Repository in the DI Container

The final piece of the jigsaw is to tell Castle Windsor to use the BlogRepositoryWithCaching implementation of IBlogRepository, but to inject the actual Data Access implementation into this decorator. This is easily achieved by modifying the RepositoriesInstaller to use Windsor’s implicit decorator wiring:

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
 
using CacheDiSample.Domain.CacheDecorators;
using CacheDiSample.Domain.Repositories;
using CacheDiSample.DataAccess;
 
namespace CacheDiSample.WindsorInstallers
{
    public class RepositoriesInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
 
            // Use Castle Windsor implicit wiring for the blog repository decorator
            // Register the outermost decorator first
            container.Register(Component.For<IBlogRepository>()
                .ImplementedBy<BlogRepositoryWithCaching>()
                .LifestyleTransient());
            // Next register the IBlogRepository implementation to inject into the outer decorator
            container.Register(Component.For<IBlogRepository>()
                .ImplementedBy<BlogRepository>()
                .LifestyleTransient()
                .DependsOn(new 
                                {
                                    nameOrConnectionString = "BloggingContext"
                                }));
        }
    }
}

 

This is all that is needed. Now if the consumer of the repository makes a call to the repositories method, it will be routed via the caching mechanism. You can test this by stepping through the code, and seeing that the DataAccess.BlogRepository code is only called if there is no data in the cache, or this has expired.

The next step is to add the SQL Cache Dependency support into this pattern, this will be a future post.

Print | posted on Tuesday, September 11, 2012 2:11 PM | Filed Under [ C# ASP.NET Design Patterns Data Caching MVC 3 Razor ]

Feedback

Gravatar

# re: Loosely coupled .NET Cache Provider using Dependency Injection

Thank you for Brilliant post. It helped me lot understanding DI, caching and loosely coupled domain model.
Hope to see your more post like this in future.
9/9/2013 11:13 PM | vijay shiyani
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 
 

Powered by: