Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a caching model without violating MVC pattern?

I have an ASP.NET MVC 3 (Razor) Web Application, with a particular page which is highly database intensive, and user experience is of the upmost priority.

Thus, i am introducing caching on this particular page.

I'm trying to figure out a way to implement this caching pattern whilst keeping my controller thin, like it currently is without caching:

public PartialViewResult GetLocationStuff(SearchPreferences searchPreferences) {    var results = _locationService.FindStuffByCriteria(searchPreferences);    return PartialView("SearchResults", results); } 

As you can see, the controller is very thin, as it should be. It doesn't care about how/where it is getting it's info from - that is the job of the service.

A couple of notes on the flow of control:

  1. Controllers get DI'ed a particular Service, depending on it's area. In this example, this controller get's a LocationService
  2. Services call through to an IQueryable<T> Repository and materialize results into T or ICollection<T>.

How i want to implement caching:

  • I can't use Output Caching - for a few reasons. First of all, this action method is invoked from the client-side (jQuery/AJAX), via [HttpPost], which according to HTTP standards should not be cached as a request. Secondly, i don't want to cache purely based on the HTTP request arguments - the cache logic is a lot more complicated than that - there is actually two-level caching going on.
  • As i hint to above, i need to use regular data-caching, e.g Cache["somekey"] = someObj;.
  • I don't want to implement a generic caching mechanism where all calls via the service go through the cache first - i only want caching on this particular action method.

First thought's would tell me to create another service (which inherits LocationService), and provide the caching workflow there (check cache first, if not there call db, add to cache, return result).

That has two problems:

  1. The services are basic Class Libraries - no references to anything extra. I would need to add a reference to System.Web here.
  2. I would have to access the HTTP Context outside of the web application, which is considered bad practice, not only for testability, but in general - right?

I also thought about using the Models folder in the Web Application (which i currently use only for ViewModels), but having a cache service in a models folder just doesn't sound right.

So - any ideas? Is there a MVC-specific thing (like Action Filter's, for example) i can use here?

General advice/tips would be greatly appreciated.

like image 783
RPM1984 Avatar asked Feb 06 '11 22:02

RPM1984


People also ask

What is the correct way to apply caching in MVC?

In ASP.NET MVC, there is an OutputCache filter attribute that you can apply and this is the same concept as output caching in web forms. The output cache enables you to cache the content returned by a controller action. Output caching basically allows you to store the output of a particular controller in the memory.

What are the different caching techniques available in .NET MVC?

ASP.NET supports three types of caching: Page Output Caching [Output caching] Page Fragment Caching [Output caching] Data Caching.

What is meant by caching in MVC?

Caching is used to improve the performance in ASP.NET MVC. Caching is a technique which stores something in memory that is being used frequently to provide better performance. In ASP.NET MVC, OutputCache attribute is used for applying Caching.


2 Answers

An action attribute seems like a good way to achieve this. Here's an example (disclaimer: I am writing this from the top of my head: I've consumed a certain quantity of beer when writing this so make sure you test it extensively :-)):

public class CacheModelAttribute : ActionFilterAttribute {     private readonly string[] _paramNames;     public CacheModelAttribute(params string[] paramNames)     {         // The request parameter names that will be used          // to constitute the cache key.         _paramNames = paramNames;     }      public override void OnActionExecuting(ActionExecutingContext filterContext)     {         base.OnActionExecuting(filterContext);         var cache = filterContext.HttpContext.Cache;         var model = cache[GetCacheKey(filterContext.HttpContext)];         if (model != null)         {             // If the cache contains a model, fetch this model             // from the cache and short-circuit the execution of the action             // to avoid hitting the repository             var result = new ViewResult             {                 ViewData = new ViewDataDictionary(model)             };             filterContext.Result = result;         }     }      public override void OnResultExecuted(ResultExecutedContext filterContext)     {         base.OnResultExecuted(filterContext);         var result = filterContext.Result as ViewResultBase;         var cacheKey = GetCacheKey(filterContext.HttpContext);         var cache = filterContext.HttpContext.Cache;         if (result != null && result.Model != null && cache[key] == null)         {             // If the action returned some model,              // store this model into the cache             cache[key] = result.Model;         }     }      private string GetCacheKey(HttpContextBase context)     {         // Use the request values of the parameter names passed         // in the attribute to calculate the cache key.         // This function could be adapted based on the requirements.         return string.Join(             "_",              (_paramNames ?? Enumerable.Empty<string>())                 .Select(pn => (context.Request[pn] ?? string.Empty).ToString())                 .ToArray()         );     } } 

And then your controller action could look like this:

[CacheModel("id", "name")] public PartialViewResult GetLocationStuff(SearchPreferences searchPreferences) {    var results = _locationService.FindStuffByCriteria(searchPreferences);    return View(results); } 

And as far as your problem with referencing the System.Web assembly in the service layer is concerned, that's no longer a problem in .NET 4.0. There's a completely new assembly which provides extensible caching features : System.Runtime.Caching, so you could use this to implement caching in your service layer directly.

Or even better if you are using an ORM at your service layer probably this ORM provides caching capabilities? I hope it does. For example NHibernate provides a second level cache.

like image 64
Darin Dimitrov Avatar answered Oct 01 '22 03:10

Darin Dimitrov


I will provide general advices and hopefully they will point you to the right direction.

  1. If this is your first stab at caching in your application, then don't cache HTTP response, cache the application data instead. Usually, you start with caching data and giving your database some breathing room; then, if it's not enough and your app/web servers are under huge stress, you can think of caching HTTP responses.

  2. Treat your data cache layer as another Model in MVC paradigm with the all subsequent implications.

  3. Whatever you do, don't write your own cache. It always looks easier than it really is. Use something like memcached.

like image 45
Yuriy Zubarev Avatar answered Oct 01 '22 05:10

Yuriy Zubarev