6

我想在渲染之前获取视图任何部分的最后修改时间。这包括布局页面、部分视图等。

我想设定一个合适的时间

 Response.Cache.SetLastModified(viewLastWriteUtcTime);   

正确处理http缓存。目前我已经为视图本身工作,但是如果布局页面有任何更改,或者子部分视图没有被拾取

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

有什么办法可以得到整体的最后修改时间?

我不想304 Not Modified在修改了视图相关部分的部署之后做出响应,因为用户会得到不一致的行为。

4

1 回答 1

5

我不会保证这是最有效的方法,但我已经测试过它并且它有效。您可能需要调整 GetRequestKey() 逻辑,并且您可能希望根据您的方案选择备用临时存储位置。我没有为文件时间实现任何缓存,因为这似乎是您不感兴趣的事情。如果可以将时间减少一点并且您想避免每个请求的文件访问开销。

首先,使用视图引擎扩展 RazorViewEngine,该引擎跟踪在此请求期间呈现的所有视图的最大上次修改时间。我们通过在会话 id 和请求时间戳键入的会话中存储最新时间来做到这一点。您可以使用任何其他视图引擎轻松完成此操作。

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

接下来,用 global.asax.cs 中的新引擎替换现有引擎

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

最后,在一些全局过滤器中或在每个控制器的基础上添加一个 OnResultExecuted。注意,我相信控制器中的 OnResultExecuted 在响应发送后运行,所以我认为你必须使用过滤器。我的测试表明这是真的。

另外,请注意,在用于防止使用时间戳污染会话之后,我将清除会话中的值。您可能希望将其保留在缓存中并对其设置较短的到期时间,这样您就不必明确清除内容,或者如果您的会话未保存在内存中以避免将其存储在会话中的事务成本。

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

}

最后,将过滤器应用到要使用它的控制器或作为全局过滤器:

[UpdateLastModifiedFromViews]
public class HomeController : Controller
{
    ...
}
于 2012-06-15T18:07:39.297 回答