Beyond Basics - ASP.Net Core Adding Caching

Starting the journey of going past the basics of beginner training resources to appy software development practices and design patterns.

Published on 04 June 2019

Introduction

When I was early in my career I would read a blog or watch a video about a relatively basic concept and then the author would suggest an advanced idea and "leave it to the reader" to investigate and implement.

I was watching another great beginner video from Scott Allen "ASP.NET Core Fundamentals" (highly recommend) the other day and something similar came up again.

During the section "Building the User Interface" Scott goes on to create a View Component which can be added into individual pages or onto the layout page so that all pages get the display. The View Component gets the count of resturants from the data store and displays it in the UI. During the development of this Scott says "If you're in a performance sensitive scenario you might want to cache this value" and this got me thinking.

The likelyhood of the number of resturants changing reguarly will be mimimal. Also the need to have the count up to date in real time is very low so to reduce additional data queries caching is a solution. Caching will store the data initially calculated from the database and then replay the value until the cache expires.

I thought if I use to struggle to apply these new concepts then I'm sure others will as well. During this series I will work through taking the initial example from Scott and move through various steps and refactorings to apply software design patterns to improve functionality but also improve maintainability whilst applying good development practices.

I hope you follow along; let's get started.

Distributed Cache

In ASP.Net Core there is a built in IDistributedCache interface which provides methods to manipulate items in the implementation of choice. I'm not going to go into detail about different types now but maybe later in the series. The point to note at this time is there is an abstraction already available to you, the developer, to use a cache. During development we will use the Distributed Memory Cache.

Note: Further reading can be found Distributed caching in ASP.NET Core

Adding the Dependency

First step is to get access to the distributed cache in the ViewComponent and to do that we add it through dependency injection. The IDistributedCache interface, and Memory Cache implementation, are registered automatically for us and we can request it through constructor injection.

private readonly IRestaurantData _restaurantData;
private readonly IDistributedCache _cache;

public RestaurantCountViewComponent(
    IRestaurantData restaurantData, 
    IDistributedCache cache)
{
    _restaurantData = restaurantData;
    _cache = cache;
}

Once we've got a reference to it through constructor injection we can use it in the Invoke method.

Basic Caching Pattern

To work with caching the basic pattern is:

  1. Make a request to the cache using a specific key.
  2. If a value is still valid the cache will return it so use it.
  3. If a value isn't available then execute the service calls to get the value and stash that away for the next request.
  4. Return the value, whether it is the cached value or the value we've just calculated, to the user.

Note: the below is a basic implementation and it will be improved through out the series. I just want to give an idea of the steps a developer can go through.

So let's take a look at the basic implementation.

Basic Implementation

public IViewComponentResult Invoke()
{
    byte[] countBytes = _cache.Get("Count");
    int count;
    if (countBytes == null)
    {
        count = _restaurantData.GetCountOfRestaurants();
        var options = new DistributedCacheEntryOptions
            { AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(15) };
        _cache.Set("Count", BitConverter.GetBytes(count), options);
    }
    else
    {
        count = BitConverter.ToInt32(countBytes);
    }
    return View(count);
}

Walk through

As you can see there is limited code, so let's walk through it so there aren't any unknowns by the end. It's important that we understand the key concepts as these will be built on as we move forward.

byte[] countBytes = _cache.Get("Count");

This is the intial check to see if there is a cache value for the key we have specified. In this case we are using the string value "Count" as the key.

If there is not a byte[] value returned from the cache we need to call the service which will give us our value.

count = _restaurantData.GetCountOfRestaurants();

This is the same restaurantData call that was originally in the ViewComponent.

var options = new DistributedCacheEntryOptions
    { AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(15) };
_cache.Set("Count", BitConverter.GetBytes(count), options);

At this point we will have the count of resturants from the data store. Using that value, before returning it to the caller, we need to stash it away into the cache for the next time around. To do this we have to create an intance of DistributedCacheEntryOptions where we can set various settings, but the one we're currently interested in is AbsoluteExpiration. This "Gets or sets an absolute expiration date for the cache entry." so we want to, in this example, make sure that it will expire in 15 seconds time.

Note: In the real world the cache time would be longer depending on scenario and probably determined by configuration.

Once we've got the options instantiated we can now set the value into the cache. Using the same key as before ("Count") we convert the value into a byte[] and set the value.

else
{
    count = BitConverter.ToInt32(countBytes);
}

If there was a value in the cache then we need to convert it back to an int to return to the ViewComponent.

return View(count);

And finally we return the count value, whether it was from the cache or calculated, so the ViewComponent markup can display it to the user.

Conclusion

In this post we have walked through the initial problem which we wanted so solve. We have introduced the basics of caching and a basic caching flow. We've also looked at how a cache value can expire using the DistributedCacheEntryOptions instance.

All the source code for this post can be found in the AddedCaching branch on my fork in Github.

Any questions/comments then please contact me on Twitter @WestDiscGolf

Other Posts in this Series

Part 1 - This Post

Part 2 - Beyond Basics - ASP.Net Core Adding Caching

Part 3 - Beyond Basics - ASP.Net Core Using the Decorator Pattern