I will not guarantee that this is the most effective way to do this, but I tested it and it works. You may need to tweak the GetRequestKey () logic, and you may want to choose an alternative temporary storage depending on your scenario. I did not implement any caching for files, as it seemed to you something that did not interest you. It would not be difficult to add whether it was normal that the time was short and you wanted to avoid the overhead of accessing files with each request.
First, extend RazorViewEngine with a viewer that tracks the longest last modified time for all views displayed during this request. We do this by storing the last time in the session with the session key and request the timestamp. You can just as easily do this with any other viewing engine.
public class CacheFriendlyRazorViewEngine : RazorViewEngine { protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { UpdateLatestTime(controllerContext, GetLastModifiedForPath(controllerContext, viewPath)); var pathToMaster = masterPath; if (string.IsNullOrEmpty(pathToMaster)) { pathToMaster = "~/Views/Shared/_Layout.cshtml"; // TODO: derive from _ViewStart.cshtml } UpdateLatestTime(controllerContext, GetLastModifiedForPath(controllerContext, pathToMaster)); return base.CreateView(controllerContext, viewPath, masterPath); } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { UpdateLatestTime(controllerContext, GetLastModifiedForPath(controllerContext, partialPath)); return base.CreatePartialView(controllerContext, partialPath); } private DateTime GetLastModifiedForPath(ControllerContext controllerContext, string path) { return System.IO.File.GetLastWriteTime(controllerContext.HttpContext.Server.MapPath(path)).ToUniversalTime(); } public static void ClearLatestTime(ControllerContext controllerContext) { var key = GetRequestKey(controllerContext.HttpContext); controllerContext.HttpContext.Session.Remove(key); } public static DateTime GetLatestTime(ControllerContext controllerContext, bool clear = false) { var key = GetRequestKey(controllerContext.HttpContext); var timestamp = GetLatestTime(controllerContext, key); if (clear) { ClearLatestTime(controllerContext); } return timestamp; } private static DateTime GetLatestTime(ControllerContext controllerContext, string key) { return controllerContext.HttpContext.Session[key] as DateTime? ?? DateTime.MinValue; } private void UpdateLatestTime(ControllerContext controllerContext, DateTime timestamp) { var key = GetRequestKey(controllerContext.HttpContext); var currentTimeStamp = GetLatestTime(controllerContext, key); if (timestamp > currentTimeStamp) { controllerContext.HttpContext.Session[key] = timestamp; } } private static string GetRequestKey(HttpContextBase context) { return string.Format("{0}-{1}", context.Session.SessionID, context.Timestamp); } }
Then replace the existing engine with a new one on global.asax.cs
protected void Application_Start() { System.Web.Mvc.ViewEngines.Engines.Clear(); System.Web.Mvc.ViewEngines.Engines.Add(new ViewEngines.CacheFriendlyRazorViewEngine()); ... }
Finally, in some global filters or based on each controller add OnResultExecuted. Notice, I believe that OnResultExecuted in the controller starts after sending the response, so I think you should use a filter. My testing shows that this is true.
Also note that I clear the value outside the session after it is used so as not to pollute the session with timestamps. You might want to keep it in the cache and set a short expiration for it, so you donβt need to explicitly clean things or if your session is not stored in memory to avoid transaction costs for storing it in the session.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class UpdateLastModifiedFromViewsAttribute : ActionFilterAttribute { public override void OnResultExecuted(ResultExecutedContext filterContext) { var cache = filterContext.HttpContext.Response.Cache; cache.SetLastModified(CacheFriendlyRazorViewEngine.GetLatestTime(filterContext.Controller.ControllerContext, true)); } }
Finally, apply the filter to the controller that you want to use, or as a global filter:
[UpdateLastModifiedFromViews] public class HomeController : Controller { ... }