📜 ⬆️ ⬇️

ASP.NET application caching module

When working on a large, high-load project, it is often necessary to cache a number of pages. This helps to reduce the load, eliminating the re-execution of pages and, as a result, re-loading data. Also a necessary condition is the ability to check the validity of the cache on a specific condition or set of conditions. In principle, the task looks standard, but, as we will see later, its solution is far from trivial.


After several hours of research, the following methods were found to solve this problem:

1) Declarative declaration of the “OutputCache” directive on the page to be cached.
')
<%@ OutputCache Duration="600" VaryByParam="*" VaryByCustom="custom" %> 

2) Setting the caching parameters of the Response.Cache object in the “coudeBehind” page:

  HttpCachePolicy policy = Response.Cache; policy.SetCacheability(HttpCacheability.Server); policy.SetExpires(app.Context.Timestamp.AddSeconds((double)600)); policy.SetMaxAge(new TimeSpan(0, 0, 600)); policy.SetValidUntilExpires(true); policy.SetLastModified(app.Context.Timestamp); policy.VaryByParams.IgnoreParams = true; 

Both of these approaches have significant drawbacks, such as:

The first desire that arose in my analysis of the task was to create a universal caching module, which we will now do.

First, you need to create a new project. This will allow us to implement our module as a library, which will make it possible to connect it to various projects and make it absolutely universal.

Module implementation


Create a class “OutputCacheModule” implementing interface “IHttpModule”, and implement two methods “Dispose” and “Init”. In our case, the “Dispose” method can be left without implementation, and the “Init” method will be considered closer.
The “Init” method accepts “HttpApplication”, this allows you to access global application events, which you need to use by subscribing to several events of interest to us:

 public void Init(HttpApplication app) { app.PreRequestHandlerExecute += new EventHandler(OnApplicationPreRequestHandlerExecute); app.PostRequestHandlerExecute += new EventHandler(OnPostRequestHandlerExecute); } 

The “PostRequestHandlerExecute” method is called immediately after the page being called or another handler has finished processing the request. At this stage, we check a number of conditions and decide on the need for caching and add the necessary “headers”:

 public void OnPostRequestHandlerExecute(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; Int32 duration = CacheHelper.GetCacheDuration(app.Context.Request); HttpCachePolicy policy = app.Response.Cache; if (!InverseContext.User.IsAuthenticated && duration > 0) { policy.SetCacheability(HttpCacheability.Server); policy.SetExpires(app.Context.Timestamp.AddSeconds(duration )); policy.SetMaxAge(new TimeSpan(0, 0, duration )); policy.SetValidUntilExpires(true); policy.SetLastModified(app.Context.Timestamp); policy.VaryByParams["*"] = true; } else { policy.SetCacheability(HttpCacheability.NoCache); policy.SetExpires(app.Context.Timestamp.AddSeconds(0)); } } 

This approach allows you to quite flexibly manage caching conditions and select the configuration required for each individual page, if necessary.

And now we will return to the first method “PreRequestHandlerExecute”, which we deliberately missed, so as not to violate the linearity of the narration. The “PreRequestHandlerExecute” method is called just before the page being called or another handler starts to execute the request. And at this stage we need to specify the “callback” method to check the validity of the pages in our cache:

  public void OnApplicationPreRequestHandlerExecute(object sender, EventArgs e) { HttpApplication app = (HttpApplication) sender; if (!InverseContext.User.IsAuthenticated) { app.Context.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(Validate), app); } } 

This entry means literally the following - every time a page in the cache is requested, the “callback” method will be called first, which will check all necessary conditions and either return the cached version of the page or initiate a new page processing cycle. Let's look at the code for this method:

 public void Validate(HttpContext context, Object data, ref HttpValidationStatus status) { if (InverseContext.User.IsAuthenticated) { status = HttpValidationStatus.IgnoreThisRequest; context.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(Validate), "somecustomdata"); } else { status = HttpValidationStatus.Valid; } } 

In this example, a very simple condition is implemented; if the user is not logged in, he will get the page from the cache. As you can see, the number of conditions is limited only by our needs. But even in this method there is an aspect to which attention should be paid, namely:
“HttpValidationStatus” - determines the status of cached data, has three possible values:


During the testing period, it will be convenient to get service information about the caching parameters that were actually set for this page. To do this, in the “Init” method, you should additionally subscribe to the “Application_EndRequest” event, since at this moment all stages of processing the request have already been completed, and theoretically, no one should change the “headers” anymore. To display information, you can use a similar method:

 private void PrintServiceInformation(HttpApplication app) { app.Context.Response.Write("Rendered at " + DateTime.Now + "<br/>"); var cacheability = app.Context.Response.Cache.GetType().GetField("_cacheability", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(app.Context.Response.Cache); app.Context.Response.Write("HttpCacheability = " + cacheability); var expires = app.Context.Response.Cache.GetType().GetField("_utcExpires", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(app.Context.Response.Cache); app.Context.Response.Write("<br/> UtcExpires = " + expires); } 

There is no reflexion without magic here. But this is only for the testing phase. In the future, you should abandon the use of this method.

Microsoft's last witchcraft

1) Do not use the “Responce.Flush ()” method in places where caching is supposed to be used, since this method calls the “SetCacheability ()” method with the “HttpCacheability.Private” parameter while the site is running under the IIS server control, and rub it not possible (see point 2).

2) The caching parameters in ASP.Net cannot be rubbed, because when you try to set “HttpCacheability”, the method performs a check

 if (s_cacheabilityValues[(int)cacheability] < s_cacheabilityValues[(int)_cacheability]) 

And “enum” looks like this:

 public enum HttpCacheability { NoCache = 1, Private = 2, Server = 3, ServerAndNoCache = 3, Public = 4, ServerAndPrivate = 5, } 

So it is impossible to deliver a larger value by “int” than what is already worth it. The “SetExpires” method works in the same way.

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


All Articles