2012-06-13 3 views
6

렌더링하기 전에 뷰의 특정 부분의 마지막 수정 시간을 가져 오려고합니다. 이 레이아웃 페이지, 부분적인 전망 등 내가 제대로 HTTP 캐싱을 처리하는ASP.NET MVC3 뷰의 최종 수정 시간 가져 오기?

Response.Cache.SetLastModified(viewLastWriteUtcTime); 

을위한 적절한 시간을 설정하려면

이 포함되어 있습니다. 그

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

에 의해 선택되지 않은 레이아웃 페이지의 변경, 또는 아이 부분 뷰 내가 전체 마지막으로 수정 한 시간을 얻을 수있는 방법이 있나요이있는 경우 현재 내가이 그러나 뷰 자체에 대한 작업을했다?

사용자가 일관성없는 동작을하게 될 것이므로 관련 부분을 수정 한 배포 후에 304 Not Modified으로 응답하고 싶지 않습니다.

+0

재미있는 문제 :

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

마지막으로, 또는 글로벌 필터로 사용하고자하는 컨트롤러에 필터를 적용합니다. 내 솔루션이 가장 쉬운 방법인지는 모르지만 그걸 털어 놓는 것은 재미있었습니다. – tvanfosson

답변

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가 실행되므로 필터를 사용해야한다고 생각합니다. 내 테스트 결과이 사실을 나타냅니다.

또한 타임 스탬프를 사용하여 세션을 오염시키지 않으므로 세션에서 값을 지우고 있습니다. 캐시에 저장하고 짧은 만료를 설정하여 명시 적으로 정리할 필요가 없도록하거나 세션에 세션을 저장하는 트랜잭션 비용을 피하기 위해 세션을 메모리에 보관하지 않을 수도 있습니다.

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

나에게 좋을 것 같은데, 아마 세션 대신 캐시를 사용하고 뷰 이름이나 그와 비슷한 것을 기반으로 키를 입력 할 것입니다. 보기/부분 생성에 연결하는 것이 가능한 방법이며 쓰기 시간이 현재 최대 값을 초과하면 업데이트되는 백업 저장소를 사용하는 것이 좋습니다. –