asp.net core mvc實現偽靜態功能


  在大型網站系統中,為了提高系統訪問性能,往往會把一些不經常變得內容發布成靜態頁,比如商城的產品詳情頁,新聞詳情頁,這些信息一旦發布后,變化的頻率不會很高,如果還采用動態輸出的方式進行處理的話,肯定會給服務器造成很大的資源浪費。但是我們又不能針對這些內容都獨立制作靜態頁,所以我們可以在系統中利用偽靜態的方式進行處理,至於什么是偽靜態,大家可以百度下。我們這里就來介紹一下,在asp.net core mvc中實現偽靜態的方式。

  mvc框架中,view代表的是視圖,它執行的結果就是最終輸出到客戶端瀏覽器的內容,包含html,css,js等。如果我們想實現靜態化,我們就需要把view執行的結果保存成一個靜態文件,保存到指定的位置上,比如磁盤、分布式緩存等,下次再訪問就可以直接讀取保存的內容,而不用再執行一次業務邏輯。那asp.net core mvc要實現這樣的功能,應該怎么做?答案是使用過濾器,在mvc框架中,提供了多種過濾器類型,這里我們要使用的是動作過濾器,動作過濾器提供了兩個時間點:動作執行前,動作執行后。我們可以在動作執行前,先判斷是否已經生成了靜態頁,如果已經生成,直接讀取文件內容輸出即可,后續的邏輯就執行跳過。如果沒有生產,就繼續往下走,在動作執行后這個階段捕獲結果,然后把結果生成的靜態內容進行保存。

  那我們就來具體的實現代碼,首先我們定義一個過濾器類型,我們成為StaticFileHandlerFilterAttribute,這個類派生自框架中提供的ActionFilterAttribute,StaticFileHandlerFilterAttribute重寫基類提供的兩個方法:OnActionExecuted(動作執行后),OnActionExecuting(動作執行前),具體代碼如下:

  

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class StaticFileHandlerFilterAttribute : ActionFilterAttribute
{
      public override void OnActionExecuted(ActionExecutedContext context){}
      public override void OnActionExecuting(ActionExecutingContext context){}
}

  在OnActionExecuting中,需要判斷下靜態內容是否已經生成,如果已經生成直接輸出內容,邏輯實現如下:

//按照一定的規則生成靜態文件的名稱,這里是按照area+"-"+controller+"-"+action+key規則生成
string controllerName = context.RouteData.Values["controller"].ToString().ToLower();
string actionName = context.RouteData.Values["action"].ToString().ToLower();
string area = context.RouteData.Values["area"].ToString().ToLower();
//這里的Key默認等於id,當然我們可以配置不同的Key名稱
string id = context.RouteData.Values.ContainsKey(Key) ? context.RouteData.Values[Key].ToString() : "";
if (string.IsNullOrEmpty(id) && context.HttpContext.Request.Query.ContainsKey(Key))
{
    id = context.HttpContext.Request.Query[Key];
}
string filePath = Path.Combine(AppContext.BaseDirectory, "wwwroot", area, controllerName + "-" + actionName + (string.IsNullOrEmpty(id) ? "" : ("-" + id)) + ".html");
//判斷文件是否存在
if (File.Exists(filePath))
{
  //如果存在,直接讀取文件
   using (FileStream fs = File.Open(filePath, FileMode.Open))
   {
       using (StreamReader sr = new StreamReader(fs, Encoding.UTF8))
       {
        //通過contentresult返回文件內容
             ContentResult contentresult = new ContentResult();
             contentresult.Content = sr.ReadToEnd();
             contentresult.ContentType = "text/html";
             context.Result = contentresult;
        }
    }
}

  

  在OnActionExecuted中我們需要結果動作結果,判斷動作結果類型是否是一個ViewResult,如果是通過代碼執行這個結果,獲取結果輸出,按照上面一樣的規則,生成靜態頁,具體實現如下         

  

         //獲取結果
         IActionResult actionResult = context.Result;
          //判斷結果是否是一個ViewResult
                if (actionResult is ViewResult)
                {
                    ViewResult viewResult = actionResult as ViewResult;
                    //下面的代碼就是執行這個ViewResult,並把結果的html內容放到一個StringBuiler對象中
                    var services = context.HttpContext.RequestServices;
                    var executor = services.GetRequiredService<ViewResultExecutor>();
                    var option = services.GetRequiredService<IOptions<MvcViewOptions>>();
                    var result = executor.FindView(context, viewResult);
                    result.EnsureSuccessful(originalLocations: null);
                    var view = result.View;
                    StringBuilder builder = new StringBuilder();

                    using (var writer = new StringWriter(builder))
                    {
                        var viewContext = new ViewContext(
                            context,
                            view,
                            viewResult.ViewData,
                            viewResult.TempData,
                            writer,
                            option.Value.HtmlHelperOptions);

                        view.RenderAsync(viewContext).GetAwaiter().GetResult();
                        //這句一定要調用,否則內容就會是空的
                        writer.Flush();
                    }
                    //按照規則生成靜態文件名稱
                    string area = context.RouteData.Values["area"].ToString().ToLower();
                    string controllerName = context.RouteData.Values["controller"].ToString().ToLower();
                    string actionName = context.RouteData.Values["action"].ToString().ToLower();
                    string id = context.RouteData.Values.ContainsKey(Key) ? context.RouteData.Values[Key].ToString() : "";
                    if (string.IsNullOrEmpty(id) && context.HttpContext.Request.Query.ContainsKey(Key))
                    {
                        id = context.HttpContext.Request.Query[Key];
                    }
                    string devicedir = Path.Combine(AppContext.BaseDirectory, "wwwroot", area);
                    if (!Directory.Exists(devicedir))
                    {
                        Directory.CreateDirectory(devicedir);
                    }

                    //寫入文件
                    string filePath = Path.Combine(AppContext.BaseDirectory, "wwwroot", area, controllerName + "-" + actionName + (string.IsNullOrEmpty(id) ? "" : ("-" + id)) + ".html");
                    using (FileStream fs = File.Open(filePath, FileMode.Create))
                    {
                        using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
                        {
                            sw.Write(builder.ToString());
                        }
                    }
                    //輸出當前的結果
                    ContentResult contentresult = new ContentResult();
                    contentresult.Content = builder.ToString();
                    contentresult.ContentType = "text/html";
                    context.Result = contentresult;
                }

  上面提到的Key,我們直接增加對應的屬性

        public string Key
        {
            get;set;
        } 

  這樣我們就可以使用這個過濾器了,使用的方法:在控制器或者控制器方法上增加 [StaticFileHandlerFilter]特性,如果想配置不同的Key,可以使用 [StaticFileHandlerFilter(Key="設置的值")]

  靜態化已經實現了,我們還需要考慮更新的事,如果后台把一篇文章更新了,我們得把靜態頁也更新下,方案有很多:一種是在后台進行內容更新時,同步把對應的靜態頁刪除即可。我們這里介紹另外一種,定時更新,就是讓靜態頁有一定的有效期,過了這個有效期自動更新。要實現這個邏輯,我們需要在OnActionExecuting方法中獲取靜態頁的創建時間,然后跟當前時間對比,判斷是否已過期,如果未過期直接輸出內容,如果已過期,繼續執行后面的邏輯。具體代碼如下:

  

//獲取文件信息對象
FileInfo fileInfo=new FileInfo(filePath);
//結算時間間隔,如果小於等於兩分鍾,就直接輸出,當然這里的規則可以改
TimeSpan ts = DateTime.Now - fileInfo.CreationTime;
if(ts.TotalMinutes<=2)
{
   using (FileStream fs = File.Open(filePath, FileMode.Open))
   {
       using (StreamReader sr = new StreamReader(fs, Encoding.UTF8))
       {
            ContentResult contentresult = new ContentResult();
            contentresult.Content = sr.ReadToEnd();
            contentresult.ContentType = "text/html";
            context.Result = contentresult;
       }
    }
}

  生成了靜態內容后,就剩下地址規則修改,這個只需要在Startup調用UseMvc時,規則中使用.html結尾的地址即可。

  到此偽靜態就實現好了。目前的處理方法,只能在一定程度上能夠提高訪問性能,但是針對大型的門戶系統來說,可能遠遠不夠。按照上面介紹的方式,可以再進行其他功能擴展,比如生成靜態頁后可以發布到CDN上,也可以發布到單獨的一個內容服務器,等等。不管是什么方式,實現思路都是一樣的。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM