Ninject Binding and the Decorator Pattern

I am really enjoying Ninject as an IOC container. If you want to get started with Ninject check out my previous posts:

Ninject – Getting Started, Resources, Basic Binding

Ninject – Life Cycle Management or Scoping

Recently, I was not clear on how to bind my types using Ninject when the types were objects that were extended using the decorator pattern. The decorator pattern is a really nice way to extend existing objects without changing the original. You know the open / closed principle. In the post, I will provide a specific decorator pattern example and then show how to set up the binding in Ninject.

Decorator Pattern Example

I have a repository pattern that defines the following interface:

public interface IReadOnlyRepository<out TEntity>
{
    IQueryable<TEntity> FetchAll();
}

In my application, I have repository of quotes that exists in an XML file. The following concrete implementation reads the quote data.

public class XmlQuoteRepository : IReadOnlyRepository<Quote>
{
    private const string _relativePath = "~/App_Data/Quotes.xml";

    public string FileName { get; private set; }
    public XmlLoader Loader { get; private set; }

    public XmlQuoteRepository(HttpContextBase httpContext, XmlLoader xmlLoader)
    {
        FileName = httpContext.Server.MapPath(_relativePath);
        Loader = xmlLoader;
    }

    public IQueryable<Quote> FetchAll()
    {
        return Loader.DeserializeFromFile<QuoteCollection>(FileName).AsQueryable();
    }
}

The ‘XmlQuoteRepository’ takes two constructor parameters (dependency injection). The ‘HttpContextBase’ parameter provides access to the ‘MapPath’ method needed to translate the URL relative path to a physical path. The ‘XmlLoader’ object handles the serialization of XML to and from the file.

Here is the ASP.NET MVC controller that is consuming this repository:

public class QuoteController : NinjectControllerBase
{
    public IReadOnlyRepository<Quote> QuoteRepository { get; private set; }

    public QuoteController(IReadOnlyRepository<Quote> quoteRepository)
    {
        QuoteRepository = quoteRepository;
    }

    public string RandomQuote()
    {
        int numQuotes = QuoteRepository.FetchAll().Count();
        Random rnd = new Random(DateTime.Now.Millisecond);
        Quote quote = QuoteRepository.FetchAll().ElementAt(rnd.Next(0, numQuotes - 1));
        return "<span id='quote-value'>" + quote.Value + "</span> - <span id='quote-origin'>" + quote.Origin + "</span>";
    }

}

This controller uses constructor injection to get an instance of the ‘IReadOnlyRepository’ for quotes. I am using AJAX to fetch a random quote from the ‘RandomQuote’ action method. A new random quote is fetched every 5 seconds.

The problem is that banging the file system every time I fetch a new quote is not scalable. We need a way to cache (in memory) the quote data. I don’t want to implement caching as a new responsibility of the ‘XmlQuoteRepository’. It already has a responsibility (single responsibility principle). This is where the decorator pattern comes in.

The following code defines a decorator for ‘IReadOnlyRepository’ objects:

public class CachedReadOnlyRepository<T> : IReadOnlyRepository<T>
{
    private readonly TimeSpan _refreshInterval;
    private readonly IReadOnlyRepository<T> _readOnlyRepositoryToCache;
    private DateTime _lastRefresh = DateTime.MinValue;
    private IQueryable<T> _cache;

    public CachedReadOnlyRepository(TimeSpan refreshInterval, IReadOnlyRepository<T> readOnlyRepositoryToCache)
    {
        _refreshInterval = refreshInterval;
        _readOnlyRepositoryToCache = readOnlyRepositoryToCache;
    }

    public IQueryable<T> FetchAll()
    {
        if(_cache==null || (DateTime.Now-_lastRefresh)>_refreshInterval)
        {
            _cache = _readOnlyRepositoryToCache.FetchAll();
            _lastRefresh = DateTime.Now;
        }
        return _cache;
    }
}

The ‘CachedReadOnlyRepository’ object implements the ‘IReadOnlyRepository’. So where ever I could use a ‘IReadOnlyRepository’, I can now use an instance of ‘CachedReadOnlyRepository’. Notice the constructor takes two parameters. A ‘TimeSpan’ parameter defines the refresh interval for the cache. And the instance of the ‘IReadOnlyRepository’ is the repository that needs caching. The implementation of the ‘FetchAll’ method delegates to the original ‘IReadOnlyRepository’ if the cache needs to be refreshed, otherwise it fetches it from the cache (local memory).

Ninject Binding

Ninject is an inversion of control container. IOC containers will provide you with an instance of a type based upon the bindings you set up. The advantage of this is that Ninject will control the lifetime of these objects. Here is the binding necessary to wire up my dependencies:

public class RepositoryModule : NinjectModule
{
    public override void Load()
    {
        Bind<XmlLoader>().ToSelf();
        Bind<IReadOnlyRepository<Quote>>().To<XmlQuoteRepository>()
            .WhenInjectedInto<CachedReadOnlyRepository<Quote>>();
        Bind<IReadOnlyRepository<Quote>>().To<CachedReadOnlyRepository<Quote>>()
            .InSingletonScope()
            .WithConstructorArgument("refreshInterval", new TimeSpan(0, 0, 10, 0));
    }
}

One of the things I love about Ninject is the fluent binding. There are three binding calls in the above code:

  • The first binding is to wire up an instance of the ‘XmlLoader’ class to itself. This is a generic class that serializes / de-serializes data from XML files.
  • The second binding wires up a ‘XmlQuoteRepository’ instance to any requested typed of ‘IReadOnlyRepository<Quote>’ when it is requested by an instance of ‘CachedReadOnlyRepository<Quote>’ type.
  • The final binding wires up the ‘CachedReadOnlyRepository<Quote>’ object to a request for the ‘IReadOnlyRepository<Quote>’ type for all other requests. This is the binding that is used to provide the parameter for the controller constructor. This binding uses the ‘InSingletonScope’ option to create a singleton instance of this type for use during the application life time (life time of the Ninject container actually). The ‘WithConstructorAgrument’ option is used to configure the time interval for the cache. In the above case, the quotes are read from the file every 10 minutes.

Summary

Ninject provides a number of binding options to enable binding in complex scenarios. In this example, we setup the binding for a decorated object.

Comments
  1. toys for men

Leave a Reply

Your email address will not be published.

*