Caching and Session in ASP.NET, part 1

What is caching, what is session and when (not) to use them?

In every modern web application, there is heavy data layer and a lot of request are constantly reading from and writing to the database. While the performance of write operations can be optimized only by creating meaningful and efficient queries, the read operations can be optimized to avoid reaching the db layer altogether. This is done with the use of cache and session, which are basically dictionary structures built-in the application's context and where data can be persisted (in the otherwise stateless http context).

What is the difference between the two and when should they be used:

  • Session: object that is different for each user and lives as long as the user session exist. Login - creates session, Logout (or prolonged inactivity) - expires session. Stored in the server's ram.
  • Cache: object that is shared between all users and exists as long as the server allows it.

Before starting, I would like to note that most of what is written in this article applies strictly to a ASP.NET MVC application. For example, in some places there is Action attribute usage, which naturally does not apply to WebForms. Also in WebAPI there is no session by default and application cache is implemented a bit differently than in MVC. Bear that in mind, because it might lead to some misunderstandings. 

1. Session


Session is the less complicated of the two. It is a dictionary that can be used for each separate user, to persist data without needing to store and/or retrieve it from a database. So how does that work? Look at the example:
//
using System.Web.Mvc;
 
namespace StudentPortal.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
 
        public ActionResult About()
        {
            ViewBag.SessionValue = Session["test"] != null ? Session["test"].ToString() : "Session is empty";
 
            return View();
        }
 
        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";
 
            return View();
        }
 
        [Authorize]
        public ActionResult MyPage()
        {
            var currentUser = User.Identity.Name;
            Session["test"] = "Session contains data for user " + currentUser;
 
            ViewBag.UserName = currentUser;
            return View();
        }
    }
}

I have created a new ASP.NET MVC project and use the default home controller, with the addition of one Action - MyPage. Notice the Authorize attribute on top? This action is only accessible for authenticated users.
So what the code does is: when you open the MyPage route, the session variable is populated with your user's login name. When you go to the About page, the session variable is printed if found in the session, if not - you get a default message. Simple as that.


So lets log out and see what happens to our session



Not quite what we expected right? The session remains despite the user being logged out. Why is that?
Simple: the default settings in an MVC application store the session in a cookie, for 20 min. This can be of course easily changed from the web.config as I will demonstrate later.
But if you open the same page in a different browser you will see that it is working as intended - there is no session object for the provided key. Lets even try to log as the same user and hit the About route (without going to MyPage first):



As you can see, the session is empty. This is because I said before, session is a per-user instance. Now a simple example of how to change the default session settings. Go to Web.Config, locate the <system.web>. Inside we can add our session settings. Example:


     
The timeout attribute is expressed in minutes and tells the server to keep the session alive for this time. The "InProc" value for mode, tells the server to store the session data in memory. The "UseCookies" tells the server to use cookies for storing the session. The last 2 are default settings.

Please refer to the MSDN documentation for complete overview on possible settings.

When to use session:
  • application uses data that is strictly individual for each user and the data rarely changes
  • user data is sensitive and should not be made available to other users

When not to use:
  • application data is shared between all users
  • it is important for the application to always work with fresh data
  • server has limited memory 

2. Application Cache


Cache is a similar structure to Session, in the way that it is a dictionary object stored in the server's memory (though session can be stored in other places as well, like SQL server). But unlike session, the cache object has only a single instance for the whole application, and is thus shared by all users. There are two types of cache: application caching and page caching. The former works similar to session. So lets demonstrate it first, with some simple code. I have modified the above controller a bit:

//
using System.Web.Mvc;
using StudentPortal.Repository;
 
namespace StudentPortal.Controllers
{
    public class HomeController : Controller
    {
        private BooksRepository _booksRepository = new BooksRepository();
 
        public ActionResult Index()
        {
            IEnumerable allBooks = _booksRepository.GetAll();
            return View(allBooks);
        }
 
        public ActionResult About()
        {
            ViewBag.SessionValue = Session["test"] != null ? Session["test"].ToString() : "Session is empty";
           
            return View();
        }
 
        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";
 
            return View();
        }
 
        [Authorize]
        public ActionResult MyPage()
        {
            var currentUser = User.Identity.Name;
            Session["test"] = "Session contains data for user " + currentUser;
 
            ViewBag.UserName = currentUser;
            ViewBag.BooksRead = _booksRepository.GetBooksForUser(currentUser);
 
            return View();
        }
    }
}

As you see, I have added a repository for books, as well as a Book class. The repository has a few methods that get book data from a MSSQL database. Also, MyPage view now shows a list with all books read by the loged-in user and the Index page shows all books by all users.




Nothing special so far. Each user can log-in and view the books that he/she has read. This generates one DB transaction every time MyPage route is hit. Also, the default page for everyone is Index page, where you can see all books that have been read by all users. This also generates one "Get all" sql statement to the database.
But let's imagine that we have a 1000 users who use the portal simultaneously. And each user switches between the homepage and MyPage, 3 times per minute. Our application will generate 3 x 1000 db requests (for each of the two pages) every minute and there is high chance that the application will be slow. This is where cache comes into play to make our server's work easier and faster:

//
using System;
using System.Collections.Generic;
using System.Web.Caching;
using System.Web.Mvc;
using StudentPortal.Repository;

namespace StudentPortal.Controllers
{
    public class HomeController : Controller
    {
        private BooksRepository _booksRepository = new BooksRepository();

        public ActionResult Index()
        {
            if (HttpContext.Cache["allBooks"] == null)
            {
                HttpContext.Cache.Insert("allBooks", _booksRepository.GetAll(), null,
                    DateTime.Now.AddHours(1), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
            }

            IEnumerable allBooks = HttpContext.Cache["allBooks"] as IEnumerable;
            return View(allBooks);
        }

        public ActionResult About()
        {
            ViewBag.SessionValue = Session["test"] != null ? Session["test"].ToString() : "Session is empty";
            
            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }

        [Authorize]
        public ActionResult MyPage()
        {
            var currentUser = User.Identity.Name;
            Session["test"] = "Session contains data for user " + currentUser;

            ViewBag.UserName = currentUser;
            if (HttpContext.Cache[currentUser] == null)
            {
                HttpContext.Cache.Insert(currentUser, _booksRepository.GetBooksForUser(currentUser), null,
                    DateTime.Now.AddHours(1), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
            }

            ViewBag.BooksRead = HttpContext.Cache[currentUser] as IEnumerable;

            return View();
        }
    }
}

Now the first user who visits the portal, will load all the the books from the database, but any subsequent users will read directly from the cache. Also, the first time a user opens MyPage, the books that are read by this user are saved in the cache, with the user's name as a key. So when the user opens MyPage next time, the values will be read from the cache. I have set a cache expiration time to 1 hour from now, but it can be set to any time whatsoever. This change in the code will decrease the calls made to the db substantially.

Naturally, the issue with the calls for the person's data can also be solved by using Session, instead of Cache. But with cache you can optimize it even further - instead of calling GetById for each book and then saving it to the cache, the db call that gets all books can save them in the cache by user name as a key. This means that, essentially, thousands db requests per minute can be reduced to only 1 request for the whole hour! 
The parameters of Cache.Insert method allow for great flexibility:

//
public void Insert(
	string key,      
	Object value,
	CacheDependency dependencies,
	DateTime absoluteExpiration,
	TimeSpan slidingExpiration,
	CacheItemPriority priority,
	CacheItemRemovedCallback onRemoveCallback
)

For example, you can set a sliding expiration of 1 hour, so every time the cache is used, it will be extended with 1 hour from that moment. Please refer to the MSDN documentation for complete overview of the possible cache parameters. 

When to use cache:
  • application uses data that is general for the whole application and the data rarely changes *
  • performance improves exponentially with the introduction of cache

When not to use:
  • application uses data that is strictly individual for each user
  • it is important for the application to always work with fresh data *
  • cache expiration policies are difficult to predict. In our example we use absolute expiration of 1 hour, but we can also set it to 1 week or 1 year. Sometimes it is difficult to decide on the trade-off between performance and data accuracy
  • server has limited memory 

* This is not a necessary condition, if you introduce a proper CacheDependency (check this guide), you can bind your cache object to a specific table in the database and it will monitor for changes and update the cache

That is all (for now) regarding session and caching. In my next article I will cover the other type of cache - Output (or Page) Cache.


Using resource files in ASP.NET MVC project

In any serious project that requires localization and multi-language support, it is essential to be able to quickly switch text and property values for the different languages/cultures. The easiest way to do this is by using resource files (.resx). This is a simple guide to show you how get started.

The traditional way that is built into ASP.NET is to make separate resources file for global and local resources and put them in the predefined folders for this: App_GlobalResources and App_LocalResources. This, however, can lead to several drawbacks, one of the most important of which is that the resources are set to internal and thus cannot be used outside of the scope of the application ((e.g. in a UnitTest project). This article describes more thoroughly the problems of this default setting.

The recommended way to use resource is to put them in a separate folder, e.g. Resources in the root of the MVC application.
So lets start by creating the folder and adding the resource file SurveyLabels.resx (of course you can use whatever name you like). When this is done, open the file and change the access modifier to Public. This will allow you to reference you resources from a unit-test project.

 


Next, open the properties of the resources file make sure the custom tool is set to  PublicResXFileCodeGenerator instead of ResXCodeFileGenerator.




You can also set a custom namespace for the resources file, e.g. Resources.Controller.View - if it is to be used only in this particular controller and view. If you want to use the default namespace, just leave it empty. Now build the project and you are done.
Let's now see how to get resource values in your project. It is actually very simple, because you have a strongly-typed object. And you can call it in a View in the following way:

1
<span>@MyApp.Resources.SurveyLabels.agreeText</span>

where agreeText is a key in my resources file, which corresponds to some value and that value will be displayed in the page. Extremely simple and straightforward. 
There are, of course, other ways to display the value. Lets say that you have a Model called LanguageViewModel, and you want its property Agreement to be displayed with a custom name in the view, which should come from the resource file. You need to add an attribute to the property to make this possible.

1
public class LanguageViewModel
{
	public List<SelectlistItem> LanguagesList { get; set; }

	[Display(Name = "agreementsTitle", ResourceType = typeof(Surveys.Resources.SurveyLabels))]
	public string Agreement { get; set; }

	public bool AgreedToTerts { get; set; }

	public bool FillAdtionalInformation { get; set; }
}

Next step is to create a resource file in a different language. You will have the same keys, but the values will be translated. Please note that the naming is very important if you want ASP.NET to find the proper resource file (runtime) when you switch the current culture. So if you want your survey labels in German, make sure the name of the file is SurveyLables.de.resx (or de-DE if you prefer the full name). Now if you switch the application culture to German, all resources values will be replaced with those from the de file.

How to properly upgrade ASP.NET MVC 3 to MVC 5 project

 This is also the answer to the question "Why intellisense is not working properly in old projects opened with Visual Studio 2013?". The short answer is that some objects(like the Html Helper) in MVC5 use different assemblies than those in MVC 3 and earlier. As a result, you may not be able to see their full functionality. For example, when you type in the View "@Html.ActionLink()", it will be underlined as invalid.  Note that the project will still build and work properly, only the VS intellisense is broken. There are already a number of useful guides on upgrading MVC templates. I found the following very useful: http://www.asp.net/mvc/tutorials/mvc-5/how-to-upgrade-an-aspnet-mvc-4-and-web-api-project-to-aspnet-mvc-5-and-web-api-2 Basically, you have to update all versions of your assemblies to the ones that are shown in the table for MVC 5. Most important are the "System.Web.Mvc" versions, so make sure all of them are set to "5.0.0.0". However, even if you followed all the steps in the guide, it is still possible that the intellisense of the Html Helper does not work properly. The easiest way to fix this:
 1. Open the web.config in the Views folder.
 2. Locate <system.web> --> <pages>, usually in an MVC 3 project it will look like this:
<pages
        validateRequest="false"
        pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
        pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
        userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
  3. In the above-mentioned guide, it is suggested that you change all versions to 5.0.0.0, but this still did not do the trick for me. It is better to delete everything after validateRequest="false". So what is left is:
<system.web>
    <httpHandlers>
      <add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
    </httpHandlers>

    <pages validateRequest="false">
      <controls>
        <add assembly="System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
      </controls>
    </pages>
  </system.web>
  4. As a last step - rebuild your solution and reload the project. Everything should work just fine.

Libraries not loading properly in ASP.NET MVC 5 (Release mode)

Recently I ran into the following problem: I had a perfectly working ASP.NET MVC project, in which I used heavily Telerik - KendoUI (for grids, charts, etc..). However, when deploying the project to a hosting service I noticed that my Kendo bundles were not loading and the page looked weird. The reason for this is that the bundle name matches an actual file system path. For example: I had a bundle called "Content/Kendo" and a folder structure that was exactly the same. The solution is quite simple - rename your bundle! Just add a letter, or an additional prefix/suffix and everything will work just fine (don't forget to change the name also in the view that is loading this bundle).