2012-06-13 14 views
6

Chcę uzyskać czas ostatniej modyfikacji dowolnej części widoku przed renderowaniem. Obejmuje stron układu, częściowy widok itpASP.NET MVC3 Pobierz ostatnią zmodyfikowaną godzinę dowolnej części widoku?

Chcę ustawić odpowiedni czas na

Response.Cache.SetLastModified(viewLastWriteUtcTime); 

prawidłowo obsługiwać buforowanie http. Obecnie mam tę pracę dla samego widoku jednak, czy są jakieś zmiany w stronach układ, lub częściowym widokiem dziecko te nie są zabierani przez

var viewLastWriteUtcTime = System.IO.File.GetLastWriteTime(
    Server.MapPath(
    (ViewEngines.Engines.FindView(ControllerContext, ViewBag.HttpMethod, null) 
      .View as BuildManagerCompiledView) 
     .ViewPath)).ToUniversalTime(); 

Czy jest jakiś sposób mogę uzyskać ogólny czas ostatniej modyfikacji?

Nie chcę odpowiadać 304 Not Modified po wdrożeniach, które zmodyfikowały pokrewną część widoku, ponieważ użytkownicy mogliby uzyskać niespójne zachowanie.

+0

ciekawy problem. Nie jestem pewien, czy moje rozwiązanie jest najłatwiejsze, ale fajnie było w to włożyć. – tvanfosson

Odpowiedz

5

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 
{ 
    ... 
} 
+0

Wygląda dobrze dla mnie, prawdopodobnie używałbym cache zamiast sesji i kluczował je na podstawie nazwy widoku lub podobnego. Ma sens, że zahaczenie w widoku/częściowym stworzeniu byłoby wykonalnym sposobem na zrobienie tego, a następnie użycie magazynu kopii zapasowej, który zostanie zaktualizowany, jeśli czas zapisu przekroczy bieżącą wartość maksymalną. –

Powiązane problemy