OutputCache (1), In ASP.NET MVC

Introduction

 
Caching can improve the performance of the application. Caching basically allows one to store the output of a particular request in the memory. Hence, any future request coming for the same action will be returned from the cached result. That way, the same content does not need to be generated each and every time from the server to reduce the server round trips and provide a better performance, especially when the data is queried from a database the performance improved would be a lot.
 
In ASP.NET MVC, there is an OutputCache filter attribute that one can apply for caching. The output cache enables one to cache the content returned by a controller action. This is the same concept as output caching in web forms and in Web API, therefore, the discussion in this article can be applied in Web Form app and Web API. 
 
OutputCache attribute has several properties.
  • CacheProfile
  • Duration
  • Location
  • VaryByParam
  • VaryByHeader
  • NoStore
Where,
 
 
We will cover some major properties in this article with the followings:
  • preset for Sample app
  • Enabling Output Caching
  • Where Content is Cached: Server or Client
  • Varying the Output Cache: VaryByParam
  • Cache Profile

PreSetup for Sample app

 
For convinience, we will use the app created in the article: Entity Framework with .Net MVC (1), Code-First, except for Step 1, where we add an authentication feature that will be used for our test later on:
 
Step 1 - Create an ASP.NET MVC app
 
We use the current version of Visual Studio 2019 16.8 and .NET Framework 4.8 to build the app:
  • Start Visual Studio and select Create a new project.
  • In the Create a new project dialog, select ASP.NET Web Application (.NET Framework) > Next.
  • In the Configure your new project dialog, enter MvcMovie for Project name > Create.
  • In the Create a new ASP.NET Web Application dialog, select MVC and   
  • Then Click Change on the right upper corner under Authentication.  (see graph below top)
  • In the Change Authentication dialog box, Select Individual User Accounts > OK. (see graph below bottom)
  • In the Create a new ASP.NET Web Application dialog,> Creat
 
 
Then, we got with Register and Log in choices,
 
 
The final result after 6 steps will be like this,
 
 

Enabling Output Caching

 
To enable output caching, add an [OutputCache] attribute to either an individual controller action or an entire controller class.
 
Add a empty controller named as OutputCacheController:
  • Right-click the Controllers folder.
  • Select Add > New Scaffolded Item Or Controller to open the window Add New Scaffolded Item
  • In the Add New Scaffold Item dialog box, click MVC 5 Controller Empty, and then click Add.
Add code below into the OutputCacheController:
  1. [OutputCache(Duration = 10)  ]  
  2. public string Index()  
  3. {  
  4.     return DateTime.Now.ToString("T");  
  5. }  
  6.   
  7. public string NoCache()  
  8. {  
  9.     return DateTime.Now.ToString("T");  

The output of the Index() action is cached for 10 seconds, while NoCache() action is not cached for comparison.
 
For convinience, replace the code in Views/home/index.chtml:
  1. @{  
  2.     ViewBag.Title = "Home Page";  
  3. }  
  4.   
  5.   
  6. <div class="row">  
  7.     <div class="col-md-4">  
  8.         <h2>Time Cached</h2>  
  9.         <li>@Html.ActionLink("No Cache""NoCache""OutputCache")</li>  
  10.         <li>@Html.ActionLink("Outpu Cache: duration = 10 senc""Index""OutputCache")</li>  
  11.     </div>  
  12.   
  13.   
  14. </div> 
Run the app,
 
 
 Click one of two links, you will get
 
 
If we invoke the cached action multiple times by entering the URL /OutputCache/Index or /OutputCache in the address bar of the browser and hitting the Refresh/Reload button in the browser repeatedly, then the time displayed in browser won't change for 10 seconds. The same time is displayed because the data is cached.
 
It is important to understand that the same data (DateTime here) is cached for everyone who visits the application. Anyone who invokes the action will get the same cached version of the data. This means that the amount of work that the web server must perform to serve the data is dramatically reduced, especially if this is not just a DateTime display, but with huge data requested from a database server.
 
When we click the non cached link, refresh the browser, the time will be changed for every second --- the data is requested from server every time and for every body.
 

Where Content is Cached: Server or Client

 
When we use the [OutputCache] attribute, content could be cached in three locations: the web server, any proxy servers, and the web browser. We can control exactly where content is cached by modifying the Location property of the [OutputCache] attribute.
 
The Location property can be set to any one of the following values,
  • Any
  • Client
  • Downstream
  • Server
  • None
  • ServerAndClient
By default, the Location property has the value Any. However, there are situations in which one might want to cache only on the browser or only on the server. For example, if you are caching information that is personalized for each user then you should not cache the information on the server. If you are displaying different information to different users then you should cache the information only on the client.
 
Add code below into the OutputCacheController,
  1. [OutputCache(Duration = 3600, VaryByParam = "none")]  
  2. public string GetName()  
  3. {  
  4.     return "Hi " + User.Identity.Name();  
  5. }  
  6.   
  7. [OutputCache(Duration = 3600, VaryByParam = "none", Location = OutputCacheLocation.Client, NoStore = true)]  
  8. public string GetName1()  
  9. {  
  10.     return "Hi " + User.Identity.Name();  

The output of the GetName() action is cached with no locaiton (location by default: Any), while GetName1() action is cached with location at Client. Notice that the [OutputCache] attribute in GetName1() action also includes a NoStore property. The NoStore property is used to inform proxy servers and browser that they should not store a permanent copy of the cached content.
 
Update the code in Views/home/index.chtml:
  1. @{  
  2.     ViewBag.Title = "Home Page";  
  3. }  
  4.   
  5.   
  6. <div class="row">  
  7.     <div class="col-md-4">  
  8.         <h2>Time Cached</h2>  
  9.         <li>@Html.ActionLink("No Cache""NoCache""OutputCache")</li>  
  10.         <li>@Html.ActionLink("Outpu Cache: duration = 10 senc""Index""OutputCache")</li>  
  11.     </div>  
  12.     <div class="col-md-4">  
  13.         <h2>Client Side Cache</h2>  
  14.         <li>@Html.ActionLink("Get Name | Server Cache""GetName""OutputCache")</li>  
  15.         <li>@Html.ActionLink("Get Name | Client (Browser)Cache""GetName1""OutputCache")</li>  
  16.     </div>  
  17.   
  18. </div> 
Run the app,
 
 
Register at least one user such as:
 
 
Click one of two links (Get Name | Server Cache or Get Name | Client (Browser)Cache), we will get
 
 
If we logout, click these two links again, then we will see, the one, that the content is cached on the client browser and not on the server, will get the correct name, i.e.
While another one, that the content by default is cached on the server, will get the same name for both login or log out situations as "Hi, [email protected]", which is the result of the first request, but not correct for the second request, the log out situation.
 

Varying the Output Cache: VaryByParam

 
In some situations, one might want different cached versions of the very same content. Imagine, for example, that you are creating a master/detail page. The master page displays a list of movie titles. When you click a title, you get details for the selected movie.
 
If you cache the details page, then the details for the  same movie will be displayed no matter which movie you click. The first movie selected by the first user will be displayed to all future users.
 
You can fix this problem by taking advantage of the VaryByParam property of the [OutputCache] attribute. This property enables you to create different cached versions of the very same content when a form parameter or query string parameter varies.
 
Examine our MoviesController created from the PreSetup app, the action named Index() is the Master and the action Details(int? id) is the Detail.  The Master action returns a list of movie titles and the Details(int? id) action returns the details for the selected movie.
 
We add a VaryByParam property with the value "none" to both Master action, Index(), and Details action, Details(int? id).
  1. // GET: Movies  
  2. [OutputCache(Duration = int.MaxValue, VaryByParam = "none")]  
  3. public ActionResult Index()  
  4. {  
  5.     return View(db.Movies.ToList());  
  6. }  
  7.   
  8. // GET: Movies/Details/5  
  9. [OutputCache(Duration = int.MaxValue, VaryByParam = "none")]  
  10. public ActionResult Details(int? id)  
  11. {  
  12.     if (id == null)  
  13.     {  
  14.         return new HttpStatusCodeResult(HttpStatusCode.BadRequest);  
  15.     }  
  16.     Movie movie = db.Movies.Find(id);  
  17.     if (movie == null)  
  18.     {  
  19.         return HttpNotFound();  
  20.     }  
  21.     return View(movie);  
  22.  
When the Master action is invoked, the same cached version of the Master view is returned. Any form parameters or query string parameters are ignored.  If you refresh the browser, it will not make a request from the server to execute the code to get a new version of data, instead to use the server chached version. If you add a debug point in the code, you will see exactly what the process is doing.
 
 
The Details() action also includes a VaryByParam property with the value "none" in our code now. We expected that the behavior is the same as the Master action that the same cached version of the detailed view is returned for any different parameters. However, it is not, for exampe, when id = 1 and id = 2, you got two different versions:
 
 
 
Exame the link, we found out that those are two different addresses, one is
  • https://localhost:44374/Movies/Details/1, another is
  • https://localhost:44374/Movies/Details/2,
instead of one address with two different parameters.  Therefore, they must have an independent version cached by themselves.
 
Now, if we change the address like this
  • https://localhost:44374/Movies/Details?id=1, and
  • https://localhost:44374/Movies/Details?id=2,
then we will get the same cached version: the one that the first time be requested.
 
 
For example, we choose the parameter id = 3 the first time; if we change the id, and refresh the browser, we will get exactly the same version as the case: id = 3.
 
Now,  we modify the VaryByParam property with the value "Id" for the detias action. When different values of the Id parameter are passed to the controller action, different cached versions of the Details view are generated.
  1. // GET: Movies    
  2. [OutputCache(Duration = int.MaxValue, VaryByParam = "none")]    
  3. public ActionResult Index()    
  4. {    
  5.     return View(db.Movies.ToList());    
  6. }    
  7.     
  8. // GET: Movies/Details/5    
  9. [OutputCache(Duration = int.MaxValue, VaryByParam = "id")]    
  10. public ActionResult Details(int? id)    
  11. {    
  12.     if (id == null)    
  13.     {    
  14.         return new HttpStatusCodeResult(HttpStatusCode.BadRequest);    
  15.     }    
  16.     Movie movie = db.Movies.Find(id);    
  17.     if (movie == null)    
  18.     {    
  19.         return HttpNotFound();    
  20.     }    
  21.     return View(movie);    
  22. }  
It is important to understand that using the VaryByParam property results in more caching and not less for one specific caching (with one address). A different cached version of the Details view is created for each different version of the Id parameter.
 
Finally, one can set the VaryByParam property to the following values,
* = Create a different cached version whenever a form or query string parameter varies.
none = Never create different cached versions
Semicolon list of parameters = Create different cached versions whenever any of the form or query string parameters in the list varies

Cache Profile

 
As an alternative to configuring output cache properties by modifying properties of the [OutputCache] attribute, one can create a cache profile in the web configuration (web.config) file. This will offers a couple of important advantages,
  • We can make one Cache profile in a central location, the web.config file, and use it globally, anywhere needed for Controllers or actions.
  • We can change the cache options by modifying the web configuration file without recompiling the application.
For example, we can define the Cache prfile in configue file,
  1. <caching>  
  2. <outputCacheSettings>  
  3.     <outputCacheProfiles>  
  4.         <add name="Cache1Hour" duration="3600" varyByParam="none"/>  
  5.     </outputCacheProfiles>  
  6. </outputCacheSettings>  
  7. </caching> 
Appy it into Controller or action,
  1. using System;  
  2. using System.Web.Mvc;  
  3.   
  4. namespace MvcApplication1.Controllers  
  5. {  
  6.     public class ProfileController : Controller  
  7.     {  
  8.         [OutputCache(CacheProfile="Cache1Hour")]  
  9.         public string Index()  
  10.         {  
  11.             return DateTime.Now.ToString("T");  
  12.         }  
  13.     }  

Summary

 
Output caching provides you with a very easy method of dramatically improving the performance of your ASP.NET MVC applications. The ways discussed here could be used in both Web Application or Web API.
 
References


Similar Articles