How to Cache method calls in ASP.NET

In multi-tier web applications, there is often a single method that gets called a whole lot more compared to others. Such methods are usually ‘get’ methods that are core to your applications domain logic, such as ‘GetTasks()’ or ‘GetUsers()’ …etc. For the purposes of this post, we will refer to such methods as ‘Bulky & Complex’ methods, or BC methods for short.

When your domain logic is complex and your BC method makes use of many service and repository calls, the time needed to execute the method increases.

Setting the scene

Let us say you have a BC method named ‘GetTasks()’. Your ‘GetTasks()’ method might tie into the stages mechanism of your application. So depending on the user’s role within the application and the authorizations rules given for that particular role group, our ‘GetTasks()’ method will return the tasks relevant for the current user and his or her role group.

As you can imagine, this method will call quite a number of services and repositories to achieve its desired outcome, this in turn involves quite a number of tables at the database level of the application.

Because our BC method is so crucial in our web application, it gets called a total of 4 times for every page load (perhaps due to filtering and paging implementations). This makes every page load extremely slow. We had a similar situation not long ago at work where every page load took 58 seconds.

That is terrible. Especially once you become aware of page load time statistics such as the one below from our friends at KISSmetrics.

page load times stats
page load times stats

The above chart clearly demonstrates how slower page response time results in an increase in page abandonment.

How Caching can help

Hmm, I guess we should start by reminding ourselves what caching is! Ok, so a Cache is a high-speed access area that can be either a reserved section of main memory or storage device.

The two main cache types are memory cache and disk cache. We are interested in Memory cache which is a portion on memory of high-speed static RAM (SRAM) which is effective when your application requires access the same data or instructions over-and-over again.

I am sure you know how caching can help now. Yep, we basically make a single call to our BC method and cache its returned data so that for all future calls to that method, we access the super fast cache, instead of retrieving all the data again from the database back-end.

Implementation

Let us look at some code. Below is an example of a BC method named ‘GetTasks()’. This method makes many calls which fire a chain reaction of method calls to services and repositories that all end with querying the database.

Because the method above is so central to the web application’s domain logic, it gets called more than once. As mentioned previously this causes our page loads to be very slow.

Let us think through how we can improve things with caching.


public List GetTasks(int userId = -1, string stage, List severities = null)
{
    // we arrive at bc method
    if(the cache is set for what are after)
    {
        // we retrieve from cache
        return listOfUserTasks from cache;
    }
    // otherwise we continue as before
}


That was not so bad! Let us continue by implementing the behavior described in the pseudo-code above.

In our .NET application, we shall make use of the ObjectCache abstract class, or rather its default implementation, the MemoryCache class.

At the top of the class where our ‘GetTasks()’ BC method sits, we shall add two properties. One is a DataCache property and the other is a property to hold the number of seconds we want our cached data to last:

We know that at some stage, we will need a method that will set the cache for us. A method that will store our retrieved data from the database into our MemoryCache. Let us create that method, we shall call it ‘SetDataCache()’.

Notice above how we are generating a unique cache key for the particular user logged in. This is because our application gets different tasks for different users. Also note the cache policy. We cannot set a data cache without setting a policy for it. In our case, the policy says that the cache should be cleared after 3 minutes.

This is Okay for our application because 3 minutes is enough time for all the initial calls to our ‘GetTasks()’ BC method.

Let us create a method that will retrieve the data from the cache. We will call this method ‘GetTasksFromCache()’.

We are finally ready to update our ‘GetTasks()’ BC method, here is what that would look like now:

Be Careful

Now that our cache is set, the application load time should be much faster. In my case at work, the above reduced the load time from 55 seconds to 4 seconds on initial load, then 0.3 of a second for later loads within the 3 minutes limit, otherwise it goes back up to 4 seconds after the 3 minutes.

Great, so we have our tasks and they are loading fast, but what happens when the user processes a few tasks. For example, a user clicks ‘Send Email’ on one of the tasks and that tasks should be removed from the list of tasks.

The problem is we have the tasks cached, so even though the email has been sent and at the database level that task has moved a stage, in our cache that same task is still in the earlier stage.

To fix this issue, we must empty our data cache whenever a task is processed. Let us create the ‘ClearDataCache()’ method.

We can then simply call our ‘ClearDataCache()’ method whenever a task is processed, like shown below:

This way the data cache will be kept up to date with the database back-end and the user would always see what he / she should be seeing.

I hope you found the above helpful. If you can think of ways to improve the above, please comment below.