2012-06-13 10 views
6

Quiero obtener la última hora modificada de cualquier parte de una vista antes de su representación. Esto incluye páginas de diseño, vistas parciales etc.ASP.NET MVC3 ¿Obtener la última hora modificada de cualquier parte de la vista?

Quiero establecer un tiempo adecuado para

Response.Cache.SetLastModified(viewLastWriteUtcTime); 

para manejar correctamente el almacenamiento en caché HTTP. Actualmente tengo esta trabajando para la vista en sí, sin embargo, si hay algún cambio en las páginas de diseño, o vistas parciales del niño los que no son recogidos por

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

¿Hay alguna manera de conseguir la última vez global modificado?

No deseo responder con 304 Not Modified después de las implementaciones que modificaron una parte relacionada de la vista ya que los usuarios obtendrían un comportamiento incoherente.

+0

Problema interesante. No estoy seguro si mi solución es la manera más fácil, pero fue divertido meterme en ella. – tvanfosson

Respuesta

5

No voy a garantizar que esta sea la forma más efectiva de hacerlo, pero lo he probado y funciona. Es posible que deba ajustar la lógica GetRequestKey() y que desee elegir una ubicación de almacenamiento temporal alternativa según su escenario. No implementé ningún almacenamiento en caché para los tiempos de archivo ya que parecía algo en lo que no estaría interesado. No sería difícil agregar si estaba bien tener los tiempos como una pequeña cantidad y quería evitar el sobrecarga de acceso a archivos en cada solicitud.

En primer lugar, extienda RazorViewEngine con un motor de vista que rastrea la mayor hora de la última modificación para todas las vistas presentadas durante esta solicitud. Hacemos esto almacenando la última hora en la sesión marcada por la identificación de la sesión y la marca de tiempo de la solicitud. Podrías hacer esto con cualquier otro motor de visualización.

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); 
    } 
} 

A continuación, sustituir el motor (s) existente con su nuevo uno de cada global.asax.cs

protected void Application_Start() 
{ 
    System.Web.Mvc.ViewEngines.Engines.Clear(); 
    System.Web.Mvc.ViewEngines.Engines.Add(new ViewEngines.CacheFriendlyRazorViewEngine()); 
    ... 
} 

Finalmente, en algún filtro global o sobre una base per-controlador añadir un OnResultExecuted. Tenga en cuenta que creo que OnResultExecuted en el controlador se ejecuta después de que se envió la respuesta, por lo que creo que debe usar un filtro. Mi prueba indica que esto es cierto.

Además, tenga en cuenta que estoy liberando el valor de la sesión después de que se utiliza para no contaminar la sesión con las marcas de tiempo. Es posible que desee mantenerlo en la memoria caché y establecer una caducidad breve para que no tenga que limpiar explícitamente o si su sesión no se guarda en la memoria para evitar los costos de transacción de almacenarla en la sesión.

[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)); 
    } 

} 

Por último, aplicar el filtro al controlador que desea utilizar en o como un filtro global:

[UpdateLastModifiedFromViews] 
public class HomeController : Controller 
{ 
    ... 
} 
+0

Me parece bien, probablemente usaría el caché en lugar de la sesión y las escribiría según el nombre de vista o similar. Tiene sentido que conectarse a la vista de creación/parcial sea una forma viable de hacerlo y luego usar una tienda de respaldo que se actualice si el tiempo de escritura excede el máximo actual. –

Cuestiones relacionadas