2012-06-13 10 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

私にはうまく見えますが、セッションの代わりにキャッシュを使用し、ビュー名などに基づいてキーを使用している可能性があります。作成ビュー/パーシャルにフックすることはこれを実行する実行可能な方法であり、書き込み時間が現在の最大値を超えた場合に更新されるバッキングストアを使用することが理にかなっています。 –

関連する問題