Microsoft IIS and ASP.NET MVC caching techniques
Ways to cache data and response to improve page performance
Of course, the best way to make you web application work fluent is to write the code properly and design the architecture of application to meet requirements and certain load depending of the type of application. But even though architecture is good, you can always improve performance by reducing processing in application code itself.
Caching is the best way to boost web application performance and reduce load of both application (IIS) and data servers (SQL Server)
- Static content cache (caching data in client's browser)
- Page response cache (caching data on server/client side)
- Data cache (caching data on server side)
1. Static content caching
Static content including all sort of documents which content is not changing during runtime, but mostly image that will be served at some point to a client are a good candidate for caching.
It is extremely suitable for images as it makes perfect sense to keep images on the client side (browser cache) once they are loaded. Doing this, number of requests made to a server during page load is reduced to a minimum which makes the page load a lot faster.
Of course, the IIS server does not know the extensions of the static content files, so you have to declare them as well in web.config file so that IIS knows to apply caching headers when serving those files back to client.
<configuration> <system.webServer> <staticContent> <clear/> <mimeMap fileExtension=".gif" mimeType="image/gif" /> <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="00:00:15"/> </staticContent> </system.webServer> </configuration>
Default value for UseMaxAge mode is one day or 1.00:00:00. For more details about static content caching settings, check MSDN page https://msdn.microsoft.com/en-us/library/ms689443(v=vs.90).aspx
Of course this is suitable for returning visitors. For first time visitors, images will be loaded on first page load. Any other page load which has static content referenced which is previously cached will skip loading and server static content from browser page.
2. Page response cache
Beside static content, your website most likely generates some content dynamically whether it is pulled out from database or some other place. In ASP.NET MVC web sites, data is pulled or generated inside action in controller (at least it should be like that if following best practices). this means that in order to cache the response you need to do it on controller or action level, which in the end returns the response back to client.
This caching approach is really flexible as it allows to cache response on server or client or even both. I'll use simple example to demonstrate this type of caching.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.UI; namespace CacheDemoWebApplication.Controllers { public class DefaultController : Controller { [OutputCache(Duration = 15, Location = OutputCacheLocation.ServerAndClient, VaryByParam = "none")] public ActionResult Index() { return View(); } } }
This action is set to cache output on both server and client. To check this I created a small view which just shows current time.
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div> <img src="~/Content/asp-dot-net-logo.gif" alt=""/> </div> <h1> @DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") </h1> </body> </html>
Action response is set to cache value for 15 seconds, so after 15 seconds you should get the correct time. Before cache expires, it will render out the initial request time before cache value is stored.
Depending on the VaryByParam value, response will be cached for each parameter value. Possible values for this attribute value are "none", "*" or any proper query string key name. If you want to cache based on multiple query string values, you can add them as a string value for VaryByParam separated with ";" character.
Another way to cache response of action or all actions in a controller is to define cache profile in web.config and just reference it in attributes
<configuration> <system.web> <caching> <outputCacheSettings> <outputCacheProfiles> <clear/> <add name="CacheDemo" duration="15" enabled="true" varyByParam="none"/> </outputCacheProfiles> </outputCacheSettings> </caching> <system.web> <configuration>
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.UI; namespace CacheDemoWebApplication.Controllers { public class DefaultController : Controller { [OutputCache(CacheProfile="CacheDemo")] public ActionResult Index() { return View(); } } }
This example is completey identical by functionality with the first one. The differences is that cache configration can be changed without having to recompile the application.
3. Data caching
Although previous two caching approaches are really helpful, in specific cases you need to cache only some calculation results, for example result of really complex LINQ query which is shared among several pages.
This approach especially can improve performance because you are caching performance critical operation results, so it directly means solving the bottle necks of your application.
Even though this approach is related only to server side, it is flexible in terms of when and how cache will expire. It supports sliding and non-sliding expiration of value retained in IIS cache. To be honest, I use this one pretty often, especially when I have to deal with pulling data from 3rd party services like GitHub, Twitter, Facebook...
It just does not make sense to fetch data on each an every request, so I cache the data for couple of minutes and improve the speed of server response.
To demonstrate this type of caching I created a dummy model which has delay in its constructor to simulate complex, time consuming operation
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Web; namespace CacheDemoWebApplication.Models { public class PersonModel { public PersonModel() { //Simulate complex operation Thread.Sleep(1000); } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } } }
Now we need to handle caching of object generated in the controller action
public ActionResult Index2() { var model = HttpContext.Cache.Get("CacheKey") as Models.PersonModel; if (model == null) { model = new Models.PersonModel() { FirstName = "John", LastName = "Doe", Email = "unknown@website.com" }; HttpContext.Cache.Insert("CacheKey", model, null, DateTime.Now.AddSeconds(15), Cache.NoSlidingExpiration); } return View(model); }
To see the real difference, it is enough to compare response time of the first request where model is constructed and second one where model is retrieved from the cache
This is an example without sliding expiation, meaning that cache will expire after some fixed amount of time. With sliding expiration, cache expiration timer will be reset after each object retrieving meaning that item will be cleared from cache only after idle expiration period. Sliding and non sliding can be easily switched off and on by setting the different enumeration value of the last parameter in this method overload.
Disclaimer
Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.
Comments for this article