Caching your product catalog for faster response times

There are a lot of applications that are built these days with data that doesn't change all that much. A product catalog is usually a good example of that. There may be updates here or there, but not every five minutes. With fairly static data like this, why do we feel the need to make a request from the controller, through a service class of some type, to a repository, which then reaches out to some form of infrastructure such as a database or the filesystem? In cases like these, we can save ourselves a lot of pain by doing that big reach once and then stuffing the results into the cache for a while. With the next request, we can then just fetch the data directly from the cache.

In this recipe, we will see how to create a simple wrapper for the standard .NET cache. We will create the ability to stash and get a single item from the cache. We will also create some methods that allow us to fetch a group of items, as well as store a group of items. Similar to other wrapper classes, the one key thing that we have created for these wrappers to do is expose strongly typed results rather than cache-oriented items. This will mean that our application won't know anything about the idea of cache. Ultimately, this gives us the ability to pick and choose how we want to cache our items down the road.

How to do it...

  1. Start by first creating a new default ASP.NET MVC 2 application.
  2. Then add a reference to NBuilder, which we will use to generate some product data for us.
  3. Next, because we are building a product catalog, we need to build the idea of a product. We will do this by adding a new Product class to the Models directory.

    Models/Product.cs:

    public class Product {
    public string Name { get; set; }
    public decimal Price { get; set; }
    }
    
  4. Then we can create a ProductService class to help us generate some products to work with. This class will use NBuilder to generate 100 fictitious products to work with.

    Models/ProductService.cs:

    public class ProductService {
    public List<Product> GetProducts() {
    List<Product> result = Builder<Product>
    .CreateListOfSize(100)
    .Build()
    .ToList();
    return result;
    }
    }
    
  5. With the added ability, we easily get a list of products. We can then create the wrapper we will use for accessing the cache. This class will provide us with methods to add items to the cache both one at a time and in groups. It will also allow us to get items from the cache one at a time and in groups.

    Models/CacheWrapper.cs:

    public class CacheWrapper
    public class CacheWrapper {
    private List<object> GetCacheItems(string[] keys) {
    IDictionaryEnumerator theCache = HttpContext.Current.Cache.GetEnumerator();
    List<object> results = new List<object>();
    while (theCache.MoveNext()) {
    if (keys.Contains(theCache.Key))
    results.Add(theCache.Value);
    }
    return results;
    }
    private object GetCachItem(string key) {
    if (HttpContext.Current.Cache[key] != null)
    return HttpContext.Current.Cache[key];
    return null;
    }
    private void AddCacheItems(Dictionary<string, object> items) {
    foreach (KeyValuePair<string, object> item in items) {
    AddCacheItem(item.Key, item.Value);
    }
    }
    private void AddCacheItem(string key, object item) {
    HttpContext.Current.Cache.Add(key, item, null, //dependencies DateTime.MaxValue, //absolute expiration new TimeSpan(0, 1, 0, 0), //sliding expiration CacheItemPriority.Default, //priority null); //callback
    }
    }
    
  6. Once we have the ability to put items into and get them out of the cache, we can start to add additional abilities. Specifically, we want to be able store a list of products in the cache and get a list of products out of the cache. To do this, we will need two new methods.

    Models/CacheWrapper.cs:

    public class CacheWrapper {
    public void PutProducts(List<Product> products) {
    Dictionary<string, object> itemsToCache = new Dictionary<string, object>();
    foreach (Product product in products) {
    itemsToCache.Add(product.Name, product);
    }
    AddCacheItems(itemsToCache);
    }
    public List<Product> GetProducts(string[] productNames) {
    List<Product> results = new List<Product>();
    List<object> cacheItems = GetCacheItems(productNames);
    foreach (object cacheItem in cacheItems) {
    results.Add(cacheItem as Product);
    }
    return results.OrderBy(p => p.Name).ToList();
    }
    ...
    }
    
  7. From this point, there are various ways to actually put our products in the cache. My preferred way is to use an IoC container such as StructureMap, so that when my presentation code calls for products, it first goes through a cache layer and, if the cache doesn't have the needed items, it then goes through the products service. In this case, we are keeping things a bit more simplified though. We will load up our entire product catalog (100 products in all) when the application first starts. Being a demo of how the cache wrapper works, this should suit our purposes just fine!

    Global.asax:

    protected void Application_Start() {
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    PreLoadProductCatalog();
    }
    private void PreLoadProductCatalog() {
    List<Product> products = new ProductService().GetProducts();
    new CacheWrapper().PutProducts(products);
    }
    
  8. Once our Product catalog is placed firmly into the cache, we are ready to create a new action and view to display our catalog. We will start by first adding a new action called CachedProducts to the home controller. This action will put in a query to our CacheWrapper for a select set of products based on their names, and then pass that result down to the view.

    Controllers/HomeController.cs:

    public ActionResult CachedProducts() {
    List<Product> products = new CacheWrapper().GetProducts(new[] {
    "Name1", "Name5", "Name98", "Name39", "Name88", "Name34"
    });
    return View(products);
    }
    
  9. Now we are ready to generate a view from this action. Be sure to compile your code so that your Product is in the list of strongly typed items. Set this view to be a list of products.
    How to do it...
  10. Now you can build and run your application. If you debug your application, you will see that the products are generated only once when the application starts up. When you browse to the cached products page, you will see that the products are actually retrieved from cache only.
    How to do it...

    Note

    Be aware that, in this recipe, the cached items are set to expire after an hour if they are not used. You would want to expose this property to the methods in your cache wrapper, so that it could be set depending on the required usage. Also, for items that are loaded when the app starts, you would want to make sure that, if the item is not in the cache, you can get them from some other source.

How it works...

In this recipe, we created a cache wrapper, which we then used to preload our product catalog when the application first started. Then down the road when we accessed our CacheWrapper to get a list of products, we were working directly with products loaded in memory. This allows you to bypass calls to a database server where you then run possibly complex queries to get a set of products.

Keep in mind that when working with a cached set of data, the data could disappear out from under you. This means that you usually need your full cache implementation (not the cache wrapper per se) to first check the cache for items and then take action upon those results. If cached items are found, they are returned. If they are not found, then you need to get the items in another manner and then cache those results, so that they are ready for the next request.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset