21

I have an issue with a partial View being cached when it shouldn't be. This partial View is used to display the Logon/Logoff on a page. It uses the simple code below to figure out which link to display

@if(Request.IsAuthenticated) {    
    <a href="@Url.Action("LogOff", "Account", new { area = "" })">Log Off</a> 
}
else {
    <a href="@Url.Action("LogOn", "Account", new { area = "" })">Log On</a>
}

This partial View is called from with all pages in my MVC3 application, using

@Html.Partial("_HeaderView")  

In most of my controllers, I have the output cache defined, so I can take advantage of caching my content.

[OutputCache(Duration = 86400, VaryByParam = "*")]

Now my issue is that the entire page is being cached when I don't want the partial view to be. This is causing wrong behavior where in it sometimes displays LogOff even if the user is not logged in etc. Is there a way to cache all the content, except for the partial view in question?

MuKa
  • 165
  • 2
  • 2
  • 13
SimpleUser
  • 1,341
  • 2
  • 16
  • 36
  • Nick, sorry had to add the comment here. Anyhow, it works when I remove the Nostore and changed the duration to 1. The only issue now is when the user logs in, they are taken to the home page but I have to explicitly refresh it for changes to take effect (to show logout instead of logon). – SimpleUser Jan 09 '12 at 11:26

6 Answers6

18

You can disable caching by decorating the controller which displays your _HeaderView partial with the following:

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public ActionResult HeaderView()
{
    return PartialView("_HeaderView");
}
Nick
  • 5,844
  • 11
  • 52
  • 98
  • Hi Nick. Thanks for your input. But I do not have a controller action associated with the partial view (for now anyway). I am calling it using Html.Partial(). Is the only workaround to have a controller action with the above? – SimpleUser Jan 09 '12 at 10:31
  • Maybe you could add a shared controller and add a HeaderView action which returns PartialView("_HeaderView"); and is decorated with the annotation in my answer above. Hopefully this will give you the control you need and isn't a massive change. Let me know if that works for you or you need more clarification. – Nick Jan 09 '12 at 10:35
  • Thanks Nick. I will implement the changes and let you know how it goes. – SimpleUser Jan 09 '12 at 10:36
  • HI Nick. When I put in the outputcache annotation, the partial view doesn't display at all. This is how my 2 controller actions looks like [OutputCache(Duration=86400, VaryByParam="*")] public ActionResult Index() { return View(); } [OutputCache(Duration = 0, VaryByParam = "*")] [ChildActionOnly] public ActionResult HeaderView() { return PartialView("_HeaderView"); } From within the Index page, I am calling Html.Action to display the partial view – SimpleUser Jan 09 '12 at 10:53
  • Use @Html.Partial("HeaderView") to display the partial and make sure _HeaderView.cshtml is in your shared views folder. Also, make sure you use the NoStore property as this is used to inform proxy servers and browsers that they should not store a permanent copy of the view. – Nick Jan 09 '12 at 11:02
  • Nick, if I use Html.Partial, it wouldn't touch the controller action would it? Then the cache annotation wouldn't be used? – SimpleUser Jan 09 '12 at 11:07
  • Monday morning syndrome! @Html.RenderAction("HeaderView"); What output are you getting now? – Nick Jan 09 '12 at 11:12
  • Initial testing suggests you only need to specify the `NoStore` and `Duration` parameters. I actually only specified the `NoStore = true` at first but got an error saying that `Duration` was required. After placing `Duration = 0` everything seems to work as intended already. Can you confirm that? I like to have the least amount of code as possible, even more so when we are dealing with hardcoded strings. – julealgon Oct 07 '14 at 15:34
  • 3
    Just made a few more tests and it seems that I can use `Location = OutputCacheLocation.None` instead of `Duration = 0` also, which is even more clear :) – julealgon Oct 07 '14 at 16:14
  • I'm getting `System.InvalidOperationException: OutputCacheAttribute for child actions only supports Duration, VaryByCustom, and VaryByParam values. Please do not set CacheProfile, Location, NoStore, SqlDependency, VaryByContentEncoding, or VaryByHeader values for child actions.` – SepehrM Jun 15 '16 at 06:52
  • And `System.InvalidOperationException: Duration must be a positive number` – SepehrM Jun 15 '16 at 06:53
  • @SepehrM you can have a look at julealgon's comment, it should sort you out – stoic Jun 24 '16 at 12:13
  • It doesn't allow using `Location` either as mentioned in the error message. We ended up using the donut caching technique. – SepehrM Jun 25 '16 at 13:10
9

What you are looking for is called Donut Caching. Here's a great article explaining what it is and how to make it work http://www.devtrends.co.uk/blog/donut-output-caching-in-asp.net-mvc-3

Paweł Staniec
  • 3,151
  • 3
  • 29
  • 41
  • I did try this one, but it didn't work for me as expected. I still had the same issues as using the outputcache attribute on the partial action, namely refreshing the home page manually. – SimpleUser Jan 09 '12 at 11:52
  • I'd love to help, but not seeing the code you had that's going to be hard :) It worked for me though. When you test, please remember about proper browser settings (sometimes they override the server settings, and case a false impression something doesnt' work ) – Paweł Staniec Jan 09 '12 at 12:05
  • Nope, it was just me having a really slow moment. Working with a flu obviously not a good idea. But this works now. Thanks for your help – SimpleUser Jan 09 '12 at 12:38
3

We should set cache profile in the Web.config file instead of setting cache values individually in pages to avoid redundant code. We can refer the profile by using the CacheProfile property of the OutputCache attribute. This cache profile will be applied to all pages unless the page/method overrides these settings.

<system.web>
  <caching>
    <outputCacheSettings>
      <outputCacheProfiles>
        <add name="CacheProfile" duration="60" varyByParam="*" />
      </outputCacheProfiles>
    </outputCacheSettings>
  </caching>
</system.web>

And if you want to disable the caching from your action which returns partial view [_HeaderView], you can override the config cache settings by decorating that specific action method like shown below:

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public ActionResult RenderPartialView()
{
    return PartialView("_HeaderView");
}

Hope this would help you!

Swati Gupta
  • 546
  • 3
  • 8
1

this working for me..

 public ActionResult LogOff()
 {
     AuthenticationManager.SignOut();  
     var url = Url.Action("Index", "Web"); 
     HttpResponse.RemoveOutputCacheItem(url);           
     return RedirectToAction("Index", "Web");
 }
Alex
  • 5,240
  • 1
  • 31
  • 38
Miroslav
  • 61
  • 7
  • +1 I tried all other solutions, they didn't work well, this worked like a charm for me... Thanks :) Note: I didn't have to use AuthenticationManager.Signout().; – Prince Ashitaka Nov 25 '16 at 06:28
1

Took a little while to figure this one out after getting back into MVC. Just put the Cache setting directly in the Partial Header View. As in when displaying the user name. No need for global or server-side code. Only problem is once a page is cached, it will not refresh right away after login. But we keep the speed when needed in the initial browsing for products. Ok trade off in our case.


    @if ( Request.IsAuthenticated)
    {
            @* when we are authenticated, don't cache any more! *@
    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    HttpContext.Current.Response.Cache.SetNoStore();
    HttpContext.Current.Response.Cache.SetNoServerCaching();
            @*@Html.Raw(DateTime.Now.ToString())*@
    @Html.ActionLink("Welcome " + ( String.IsNullOrEmpty(((System.Security.Claims.ClaimsIdentity)User.Identity).FindFirstValue("UserName")) ? User.Identity.Name : ((System.Security.Claims.ClaimsIdentity)User.Identity).FindFirstValue("UserName")), "Index", "Manage", routeValues: new { Area = "Store" }, htmlAttributes: new { title = "Manage"})
    }
    else
    {
    }

Jeff
  • 29
  • 1
1

Fast forwarding to 2023, in .NET Core you can do something like this to turn off cache for some parts of the page:

<cache enabled="false">
@if(Request.IsAuthenticated) {    
    <a href="@Url.Action("LogOff", "Account", new { area = "" })">Log Off</a> 
}
else {
    <a href="@Url.Action("LogOn", "Account", new { area = "" })">Log On</a>
}
</cache>

Read more about the <cache>-tag helper here: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/built-in/cache-tag-helper?view=aspnetcore-7.0

Sha
  • 2,185
  • 1
  • 36
  • 61