So I create my partial view like this - here we generate the time to show when the view is being cached
MyPartial.cshtml
Timestamp: @DateTime.Now.ToLongTimeString()
Then I create the controller action:
HomeController.cs
[OutputCache(Duration = 60)] public ActionResult MyPartial() { return PartialView(); }
Then I amend my layout page to include the call to the PartialView:
MyMaster.cshtml
Some header stuff ... @Html.Action("MyPartial", "Home") ... Some footer stuff
Well, that's all there is to it. The pitfalls I faced revolved around accidentally calling Html.Partial() from the layout page, which didn't work because it bypasses the controller attributes (i.e. it misses the OutputCache directive).
HOWEVER
Then I started wanting to reset the output cache when the underlying data changed, or when some user setting changed. So I knocked up this little solution using the OutputCache attribute's VaryByCustom property.
First a modification to the controller:
HomeController.cs
[OutputCache(Duration = 60, VaryByCustom = "WebApp.OutputCacheKey")] public ActionResult MyPartial() { return PartialView(); }
Every time OutputCache is called, it will check to see if that string has changed since last time. If it has changed, it invalidates the cache. "But it's a static string! How can it change?" I hear you cry. Yup, here's some voodoo magic. You can override a special method in global.asax that intercepts the call and returns ANOTHER string, wooo!
global.asax
public override string GetVaryByCustomString(HttpContext context, string arg) { if (arg == "WebApp.OutputCacheKey") { return WebApp.OutputCacheKey; } else { return base.GetVaryByCustomString(context, arg); } }Now, we need to implement this WebApp thing. It's just an approach I like to have a static class knocking around my web application that allows me to access stuff from one central place, like strongly-typed accessors for session variables, or my per-request EF datacontext. But for these purposes it would just look like this:
WebApp.cs
public static class WebApp { internal static string OutputCacheKey { get { if (HttpContext.Current.Session["WebApp.OutputCacheKey"] == null) { ResetOutputCache(); } return HttpContext.Current.Session["WebApp.OutputCacheKey"].ToString(); } } internal static void ResetOutputCache() { HttpContext.Current.Session["WebApp.OutputCacheKey"] = Guid.NewGuid().ToString(); } }So, what we have now is an easy way to programatically tell the OutputCache to clear itself, with a simple call from anywhere in your controller code:
WebApp.ResetOutputCache();Simples!
Excellent!!!
ReplyDeleteyou are the man.
I tried almost everything, and your solution is the only one that works and is simple.
Power to the people!
After searching on the internet for a simple way to cache my MVC 3 Razor application, I came across your blog and thought it would finally put an end to my coding frustrations. Unfortunately, I could not get your code to function properly within my application. Basically, I have an Index controller action that grabs data from a database but it’s very expensive at this time. So, I thought I would cache the controller action for 10 minutes but provide a link to the user to refresh the data at will. I wired the link up to the Refresh controller action then called your code snippet to reset the outputcache key but it’s not working. The initial call to the Index controller action works and caches the data in the browser. The next time I call the Index controller action it doesn’t utilize the cache but it pulls the data down from the server. As for the Refresh controller action, the first time I call the action controller via a link it doesn’t clear the cache but if I call the action a second time it clears the cache. Here’s my code:
ReplyDeleteStatic Class
Public Class MyAppHelpers
Friend Shared ReadOnly Property OutputCacheKey() As String
Get
If HttpContext.Current.Session("OutputCacheKey") Is Nothing Then
ResetOutputCache()
End If
Return HttpContext.Current.Session("OutputCacheKey").ToString()
End Get
End Property
Friend Shared Sub ResetOutputCache()
HttpContext.Current.Session("OutputCacheKey") = Guid.NewGuid().ToString()
End Sub
End Class
Global.asax.vb
Public Overrides Function GetVaryByCustomString(context As HttpContext, arg As String) As String
If arg = "OutputCacheKey" Then
Return MyAppHelpers.OutputCacheKey
Else
Return MyBase.GetVaryByCustomString(context, arg)
End If
End Function
View
'
' GET: /Home
Function Index(searchModel As SearchModel) As ActionResult
End Function
'
' GET: /Home/RefreshData
Function RefreshData(searchModel As SearchModel) As ActionResult
MyAppHelpers.ResetOutputCache()
Return RedirectToAction("Index", New SearchModel With {.id = SearchModel.id})
End Function
Here's the correct Index controller action:
ReplyDelete'
' GET: /Home
Function Index(searchModel As SearchModel) As ActionResult
End Function
“OutputCache(Location:=OutputCacheLocation.Client, Duration:=600, VaryByParam:="id", VaryByCustom:="OutputCacheKey")”
ReplyDeleteI am using similar approach for partial views. However, instead of using OutPutCache attribute with controller action, i wanted to use cache profile, that i can define in configuration file. But, while doing that, i face problem,
ReplyDelete"utputCacheAttribute 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"
Thnz for sharing. Its very useful
ReplyDeleteIt does not invalidate the cache, It only makes new one. So the resources it costs would be bigger and bigger every time you "clear the cache". And dependency on session? Huh, what a crazy technic.
ReplyDeleteI would strongly not recommend this solution at all.