📜 ⬆️ ⬇️

Using Caching Infrastructure in ASP.NET

A year and a half ago, I wrote an article about caching in ASP.NET MVC , in which I described how to improve the performance of an ASP.NET MVC application by caching both on the server and on the client. In the comments to the article, many additional ways were mentioned for managing caching in ASP.NET.

In that post I will tell you how to use the capabilities of the ASP.NET infrastructure to manage caching.


HTTP caching (revisited)


In the last post there was a monstrous example of code for implementing HTTP caching when returning the state of the basket:
')
Code example
[HttpGet] public ActionResult CartSummary() { //   ,     this.Response.Cache.SetCacheability(System.Web.HttpCacheability.Private); this.Response.Cache.SetMaxAge(TimeSpan.Zero); var cart = ShoppingCart.GetCart(this.HttpContext); var cacheKey = "shooting-cart-" + cart.ShoppingCartId; var cachedPair = (Tuple<DateTime, int>)this.HttpContext.Cache[cacheKey]; if (cachedPair != null) //       { // Last-Modified this.Response.Cache.SetLastModified(cachedPair.Item1); var lastModified = DateTime.MinValue; // Conditional Get if (DateTime.TryParse(this.Request.Headers["If-Modified-Since"], out lastModified) && lastModified >= cachedPair.Item1) { return new NotModifiedResult(); } ViewData["CartCount"] = cachedPair.Item2; } else //       { // ,    var now = DateTime.Now; now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); // Last-Modified this.Response.Cache.SetLastModified(now); var count = cart.GetCount(); this.HttpContext.Cache[cacheKey] = Tuple.Create(now, count); ViewData["CartCount"] = count; } return PartialView("CartSummary"); } 


for comparison, the original version (without caching)
  public ActionResult CartSummary() { var cart = ShoppingCart.GetCart(this.HttpContext); ViewData["CartCount"] = cart.GetCount(); return PartialView("CartSummary"); } 


If you see this code for the first time and do not know where it came from, read the previous article .


It is natural for each caching case to write such code is very inconvenient. The ASP.NET infrastructure already has a ready-made infrastructure that allows you to achieve the same result with a much smaller amount of code.

Cache dependencies


In ASP.NET, you can bind server responses to items in the cache ( System.Web.Caching.Cache ).

This is done with one function:
 Response.AddCacheItemDependency(cacheKey); 

But the binding itself does not give anything. In order to process Conditional-GET, you must give the Last-Modified header and \ or E-Tag . There are also functions for this:
 Response.Cache.SetLastModifiedFromFileDependencies(); Response.Cache.SetETagFromFileDependencies(); 

Despite the word File in the function name, any dependencies of the response are analyzed. Moreover, if the server response has many dependencies, then Last-Modified set to the highest value, and the E-Tag is formed from all dependencies.

The next step is to enable response caching on the server and on the client, because ASP.NET can handle Conditional-GET only for responses cached on the server:
 Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate); 

When executing these four lines of code, ASP.NET returns the headers Last-Modified , E-Tag , Cache-Control: private and stores the response on the server. But there is a problem - IE does not request a new version of the page, caching the default response for a day or until the browser restarts. In general, response caching time without specifying max-age or Expires header can vary greatly between browsers.

To defeat this problem, you must specify max-age=0 . In ASP.NET, this can be done with the following function:
 Response.Cache.SetMaxAge(TimeSpan.FromSeconds(0)); 

But this function also exposes the response cache time on the server, and, in fact, ASP.NET ceases to send cached server responses.

The right way to achieve results:
 Response.Cache.AppendCacheExtension("max-age=0") 

Then the response is cached on the server, but the Cache-Control: private, max-age=0 header is given to the client Cache-Control: private, max-age=0 , which causes the browser to send a request every time. Unfortunately, this method is not documented anywhere.

As a result, ASP.NET processes Conditional-GET and returns answers from the server cache while an ASP.NET cache is stored and does not change the element with the cacheKey key.

Full action code:
 [HttpGet] public ActionResult CartSummary() { var cart = ShoppingCart.GetCart(this.HttpContext); var cacheKey = "shopping-cart-" + cart.ShoppingCartId; ViewData["CartCount"] = GetCachedCount(cart, cacheKey); this.Response.AddCacheItemDependency(cacheKey); this.Response.Cache.SetLastModifiedFromFileDependencies(); this.Response.Cache.AppendCacheExtension("max-age=0"); this.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate); return PartialView("CartSummary"); } private int GetCachedCount(ShoppingCart cart,string cacheKey) { var value = this.HttpContext.Cache[cacheKey]; int result = 0; if (value != null) { result = (int) value; } else { result = cart.GetCount(); this.HttpContext.Cache.Insert(cacheKey,result); } return result; } 

Agree, it is much less code than in the previous article.

Cache variation


By default, ASP.NET stores in the cache one answer for any user by one url (excluding the querystring). This leads to the fact that in the example above for all users the same answer will be given.

By the way, ASP.NET behavior differs from HTTP protocol, which caches the response by full url. The HTTP protocol provides the ability to vary the cache using the Vary response header. In ASP.NET, you can also vary the response by the parameters in the QueryString, by the encoding ( Accept-Encoding header), and also by the custom parameter associated with the response.

Variation in the custom parameter allows you to save the cache for different users. In order to give different baskets to different users need:

1) Add a call to the controller
 Response.Cache.SetVaryByCustom("sessionId"); 


2) In Global.asax override the GetVaryByCustomString method
 public override string GetVaryByCustomString(HttpContext context, string custom) { if (custom == "sessionId") { var sessionCookie = context.Request.Cookies["ASP.NET_SessionId"]; if (sessionCookie != null) { return sessionCookie.Value; } } return base.GetVaryByCustomString(context, custom); } 

Thus, for different sessions, different instances of the cache will be given.

With this implementation, we must remember that on the server in the cache every server response is saved. If you keep large pages for each user, they will often be forced out of the cache and this will lead to a drop in the effectiveness of caching.

Dependencies between cache items


The dependency mechanism in ASP.NET allows you to bind not only the response to an element of the internal cache, but also to bind one cache element to another. The CacheDependency class and its descendants are responsible for this.

For example:
 HttpContext.Cache.Insert("cacheItemKey",data, new CacheDependency(null, new[] { "anotherCacheItemKey" })); 


If the item with the anotherCacheItemKey key is changed or deleted from the cache, the item with the cacheItemKey key will be automatically deleted from the cache.

This allows you to build systems with multi-level synchronized cache.

Additional features


The cache dependency mechanism in ASP.NET is extensible. By default, you can create dependencies on internal cache entries, dependencies on files and folders, as well as dependencies on tables in the database. In addition, you can create your own cache dependency classes, for example for Redis.

But about it all in the following articles.

Source: https://habr.com/ru/post/227129/


All Articles