Nie gwarantuję, że jest to najskuteczniejszy sposób na zrobienie tego, ale przetestowałem go i działa. Konieczna może być korekta logiki GetRequestKey() i może być konieczne wybranie alternatywnej lokalizacji tymczasowego przechowywania w zależności od scenariusza. Nie zaimplementowałem buforowania plików, ponieważ wydawało się, że nie interesuje Cię to. Nie byłoby trudno dodać, czy było w porządku, aby czasy były niewielkie i chciałbyś uniknąć dostęp do plików na każdym wniosku.
Najpierw należy rozwinąć RazorViewEngine z mechanizmem wyświetlania, który śledzi największy czas ostatniej modyfikacji dla wszystkich widoków renderowanych podczas tego żądania. Robimy to, przechowując najnowszy czas w sesji z kluczem identyfikacyjnym sesji i datownikiem żądania. Równie łatwo można to zrobić z dowolnym innym mechanizmem wyświetlania.
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);
}
}
Następnie zastąpić istniejący silnika (ów) z nowym w Global.asax.cs
protected void Application_Start()
{
System.Web.Mvc.ViewEngines.Engines.Clear();
System.Web.Mvc.ViewEngines.Engines.Add(new ViewEngines.CacheFriendlyRazorViewEngine());
...
}
Wreszcie w pewnym globalnym filtrze lub na zasadzie per-kontrolera dodać OnResultExecuted. Uwaga, wierzę, że OnResultExecuted w kontrolerze działa po wysłaniu odpowiedzi, więc myślę, że musisz użyć filtra. Moje testy wskazują, że jest to prawdą.
Należy również pamiętać, że wyczyszczę wartość z sesji po jej użyciu, aby zapobiec zanieczyszczaniu sesji znacznikami czasu. Możesz zachować go w pamięci podręcznej i ustawić na nim krótki okres wygaśnięcia, aby nie trzeba było jawnie czyścić rzeczy lub jeśli twoja sesja nie była przechowywana w pamięci, aby uniknąć kosztów transakcji przechowywania w sesji.
[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));
}
}
Wreszcie zastosować filtr do kontrolera, którego chcesz użyć go jako filtr lub globalnym:
[UpdateLastModifiedFromViews]
public class HomeController : Controller
{
...
}
ciekawy problem. Nie jestem pewien, czy moje rozwiązanie jest najłatwiejsze, ale fajnie było w to włożyć. – tvanfosson