首先解釋一下什麼是動態處理頁面靜態化html
對於須要靜態化的頁面,第一次訪問某個Action時,會先執行Action,並在頁面渲染後向Response和服務器中網站的目錄下都寫入須要返回的html,而第二次訪問此頁時,在執行Action前,程序會先在指定目錄下尋找是否存在當前請求對應的靜態頁面,若是有,則直接返回靜態頁面,若是沒有,則按第一次訪問此請求進行處理,即執行Action,並向Response和服務器中網站的目錄下都寫入須要返回的html。利用這種方式,能夠在網站在請求的過程當中,會動態的生成靜態頁面,而無需人工干預,方便快捷。服務器
/// <summary> /// 頁面靜態化過濾器 /// 思路:在執行Action前,先判斷此Action返回的View()的靜態文件是否存在 /// 若是存在,則直接返回靜態文件。 /// 若是不存在,則利用OnResultExecuting,替換Response中的輸出流,讓渲染後的html寫入到 /// 本過濾器定義的StringBuilder中,而後在OnResultExecuted中(頁面渲染後),從StringBuilder /// 中獲取html,並同時寫入到靜態文件和Response的內置輸出流中 /// /// 注:因爲我沒找到直接從Response中獲取輸出流的html的方法,所以我這裏是替換了Response中的output屬性, /// 替換後,渲染後的結果就會輸出到咱們本身寫的StringBuilder中(此時Response中並無html),此時在渲染後, /// 再把StringBuilder中的html,分別寫入靜態文件和Response中(tw.write方法),完成這次請求。 /// </summary> public class StaticHtmlFilter : ActionFilterAttribute { //用於保存渲染後的html文本 static StringBuilder sb; //這幾個Writer照着寫就好了 static StringWriter sw; static HtmlTextWriter hw; static TextWriter tw; //自定義的靜態頁面的後綴名 static string ext = ".html"; //靜態頁面的絕對路徑(包括後綴名) string fileName = null; ///靜態頁面的絕對路徑(不包括後綴名) static string path = null; //靜態文件是否存在 bool FileExists = false; /// <summary> /// Action執行前,判斷當前頁面是否已經被靜態化(Views路徑下是否存在html文件) /// 若是存在靜態文件則直接設置filterContext的result,即返回html做爲結果,而不執行Action中代碼 /// 若是不存在靜態頁面文件,則不設置filterContext的result,此時將會執行Action中的代碼 /// </summary> /// <param name="filterContext"></param> public override void OnActionExecuting(ActionExecutingContext filterContext) { //根據controller和action信息 string controller = filterContext.RouteData.Values["controller"].ToString(); string action = filterContext.RouteData.Values["action"].ToString(); object id=null; //路由中是否包含可選參數id,若是有,則在文件名也要體現 if (!filterContext.RouteData.Values.TryGetValue("id", out id)) { path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Views", controller, action); fileName = string.Format("{0}{1}", path, ext); } else { path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Views", controller, action); fileName = string.Format("{0}{1}{2}", path, id.ToString(),ext); } //拼裝後綴名 FileExists = File.Exists(fileName); //若是文件存在,直接返回結果 if (FileExists) { filterContext.Result = new FileContentResult(File.ReadAllBytes(fileName), "text/html; charset=utf-8"); } } /// <summary> /// 執行完Action後,但渲染頁面前執行此處 /// 渲染頁面的意思是將cshtml中的後臺代碼,翻譯爲前臺代碼 /// /// </summary> /// <param name="filterContext"></param> public override void OnResultExecuting(ResultExecutingContext filterContext) {
if (!FileExists) { //保存html sb = new StringBuilder(); //兩個writer sw = new StringWriter(sb); hw = new HtmlTextWriter(sw); //記住Response中本來輸出流,用於返回本次請求的html,與下一句配合使用 //在渲染結束後,向tw內寫入html內容 tw = filterContext.RequestContext.HttpContext.Response.Output; //過濾器本身輸出流,用於獲取渲染後的html內容 filterContext.RequestContext.HttpContext.Response.Output = hw; } } public override void OnResultExecuted(ResultExecutedContext filterContext) { //若是是靜態文件不存在 if (!FileExists) { //獲取渲染後的html文本 string res = sb.ToString(); //將文本寫入到靜態文件中 new Action(() => File.WriteAllText(fileName, res)).BeginInvoke(null, null); //向Response的輸出流中寫入本次請求的html tw.Write(sb.ToString()); } } }
我認爲有兩種Action須要使用靜態化async
1.登陸頁面等無需向Action中傳入參數而直接返回View的Action須要靜態處理。ide
/// <summary> /// 登陸 /// </summary> /// <returns></returns> [AllowAnonymous] [StaticHtmlFilter] public ActionResult Login() { var model = new LoginDto { ReturnUrl = Request.QueryString["ReturnUrl"], LoginName = "admin", Password = "qwaszx" }; if (User.Identity.IsAuthenticated) { if (model.ReturnUrl.IsNotBlank()) return Redirect(model.ReturnUrl); return RedirectToAction("Index"); } return View(model); }
2.經過一個參數進行查詢的Action(注意是查詢,非編輯)網站
/// <summary> /// 編輯 /// </summary> /// <param name="id"></param> /// <returns></returns> [StaticHtmlFilter] public async Task<ActionResult> Edit(string id) { var model = await _menuService.Find(id); return View(model); }
對於常常須要編輯的內容的查詢頁面,如商品列表如使用動態處理靜態化頁面,則應在編輯商品信息後,刪除服務器指定目錄下的靜態頁面,以便於頁面更新。ui
固然咱們也能夠在上面的過濾器中的 OnActionExecuting 方法在判斷文件是否存在時,先判斷本次請求是不是編輯操做,若是是則刪除相應的靜態文件從新生成便可。spa