閱讀794 返回首頁    go 阿裏雲 go 技術社區[雲棲]


ASP.NET MVC的View是如何呈現出來的[實例篇]

在《[設計篇]》篇中我們通過對View引擎的總體介紹講述了從ViewResult的創建到View呈現的原理,為了讓讀者對View引擎及其View呈現機製具有一個深刻的認識,我們自定義一個簡單的用於呈現靜態HTML的。在一個通過Visual Studio的ASP.NET MVC項目模板創建的空Web應用中,我們定義了如下一個針對於靜態HTML內容呈現的自定義StaticFileView。StaticFileView實現了IView接口,在實現的Render方法中讀取製定文件的內容寫入作為參數的TextWriter。 [本文已經同步到《How ASP.NET MVC Works?》中]

   1: public class StaticFileView:IView
   2: {
   3:     public string FileName { get; private set; }
   4:     public StaticFileView(string fileName)
   5:     {
   6:         this.FileName = fileName;
   7:     }
   8:     public void Render(ViewContext viewContext, TextWriter writer)
   9:     {
  10:         byte[] buffer;
  11:         using (FileStream fs = new FileStream(this.FileName, FileMode.Open))
  12:         { 
  13:             buffer = new byte[fs.Length];
  14:             fs.Read(buffer, 0, buffer.Length);
  15:         }
  16:         writer.Write(Encoding.UTF8.GetString(buffer));
  17:     }
  18: }

由於StaticFileView中定義的內容完全是靜態的,所以緩存顯得很有必要。我們隻需要基於Controller和View名稱對View實施緩存,為此我們定義了如下一個作為Key的數據類型ViewEngineResultCacheKey。

   1: internal class ViewEngineResultCacheKey
   2: {
   3:     public string ControllerName { get; private set; }
   4:     public string ViewName { get; private set; }
   5:  
   6:     public ViewEngineResultCacheKey(string controllerName, string viewName)
   7:     {
   8:         this.ControllerName = controllerName ?? string.Empty;
   9:         this.ViewName = viewName ?? string.Empty;
  10:     }
  11:     public override int GetHashCode()
  12:     {
  13:         return this.ControllerName.ToLower().GetHashCode() ^ this.ViewName.ToLower().GetHashCode();
  14:     }
  15:  
  16:     public override bool Equals(object obj)
  17:     {
  18:         ViewEngineResultCacheKey key = obj as ViewEngineResultCacheKey;
  19:         if (null == key)
  20:         {
  21:             return false;
  22:         }
  23:         return key.GetHashCode() == this.GetHashCode();
  24:     }
  25: }

具有如下定義的StaticFileViewEngine代表StaticFileView對應的ViewEngine。我們通過一個字典類型的字段viewEngineResults作為對ViewEngineResult的緩存,而View的獲取操作最終實現在InternalFindView方法中。通過StaticFileView表示的View定義在一個以View名稱作為文件名的文本文件中,該文件的擴展名為.shtml(Static HTML)。

   1: public class StaticFileViewEngine : IViewEngine
   2: {
   3:     private Dictionary<ViewEngineResultCacheKey, ViewEngineResult> viewEngineResults = new Dictionary<ViewEngineResultCacheKey, ViewEngineResult>();
   4:     private object syncHelper = new object();
   5:     public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
   6:     {
   7:         return this.FindView(controllerContext, partialViewName, null, useCache);
   8:     }
   9:  
  10:     public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
  11:     {
  12:         string controllerName = controllerContext.RouteData.GetRequiredString("controller");
  13:         ViewEngineResultCacheKey key = new ViewEngineResultCacheKey(controllerName, viewName);
  14:         ViewEngineResult result;
  15:         if (!useCache)
  16:         { 
  17:             result = InternalFindView(controllerContext, viewName, controllerName);
  18:             viewEngineResults[key] = result;
  19:             return result;
  20:         }
  21:         if(viewEngineResults.TryGetValue(key, out result))
  22:         {
  23:             return result;
  24:         }
  25:         lock (syncHelper)
  26:         {
  27:             if (viewEngineResults.TryGetValue(key, out result))
  28:             {
  29:                 return result;
  30:             }
  31:  
  32:             result = InternalFindView(controllerContext, viewName, controllerName);
  33:             viewEngineResults[key] = result;
  34:             return result;
  35:         }             
  36:     }
  37:  
  38:     private ViewEngineResult InternalFindView(ControllerContext controllerContext, string viewName, string controllerName)
  39:     {
  40:         string[] searchLocations = new string[]
  41:         {
  42:             string.Format( "~/views/{0}/{1}.shtml", controllerName, viewName),
  43:             string.Format( "~/views/Shared/{0}.shtml", viewName)
  44:         };
  45:  
  46:         string fileName = controllerContext.HttpContext.Request.MapPath(searchLocations[0]);
  47:         if (File.Exists(fileName))
  48:         {
  49:             return new ViewEngineResult(new StaticFileView(fileName), this);
  50:         }
  51:         fileName = string.Format(@"\views\Shared\{0}.shtml", viewName);
  52:         if (File.Exists(fileName))
  53:         {
  54:             return new ViewEngineResult(new StaticFileView(fileName), this);
  55:         }
  56:         return new ViewEngineResult(searchLocations);
  57:     }
  58:  
  59:     public void ReleaseView(ControllerContext controllerContext, IView view)
  60:     { }
  61: }

在InternalFindView中,我們先在目錄下尋找View文件,如果不存在則在尋找。如果對應View文件被找到,則以此創建一個StaticFileView對象,並最終返回封裝該View對象的ViewEngineResult。如果目標View文件找不到,則根據基於這兩個目錄的搜尋地址列表創建並返回對應的ViewEngineResult。 現在我們在Global.asax通過如下的代碼對自定義的StaticFileViewEngine進行注冊,我們將創建的StaticFileViewEngine作為第一個使用的ViewEngine。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     protected void Application_Start()
   4:     {
   5:         //其他操作
   6:         ViewEngines.Engines.Insert(0, new StaticFileViewEngine());
   7:     }
   8: }

然後我們定義了如下一個簡單的HomeController,Action方法ShowNonExistentView中通過調用View方法呈現一個不存在的View(NonExistentView),而ShowStaticFileView方法則將對應的StaticFileView呈現出來。

   1: public class HomeController : Controller
   2: {
   3:     public ActionResult ShowNonExistentView()
   4:     {
   5:         return View("NonExistentView");
   6:     }
   7:  
   8:     public ActionResult ShowStaticFileView()
   9:     {
  10:         return View();
  11:     }
  12: }

我們為Action方法ShowStaticFileView創建一個StaticFileView類型的View文件ShowStaticFileView.shtml(該View文件保存在“~/Views/Home”目錄下,擴展名不是.cshtml,而是shtml),其內容就是如下一段完整的HTML。

   1: <!DOCTYPE html>
   2: <html>
   3:     <head>
   4:         <title>Static File View</title>
   5:     </head>
   6:     <body>
   7:         這是一個自定義的StaticFileView!
   8:     </body>
   9: </html>

現在運行我們的程序,在瀏覽器中輸入相應的地址訪問Action方法ShowNonExistentView,會得到如下圖所示的輸出結果。圖中列出的View搜尋位置列表中的前兩項正是我們自定義的StaticFileViewEngine尋找對應.shtml文件的兩個地址。

image

如果我們改變瀏覽器的地址來訪問另一個Action方法ShowStaticFileView,會呈現出如下圖所示的輸出結果,不難看出呈現出來的正是定義在ShowStaticFileView.shtml中的HTML。

image

ASP.NET MVC的View是如何被呈現出來的?[設計篇]
ASP.NET MVC的View是如何被呈現出來的?[實例篇]


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 16:33:59

  上一篇:go  ASP.NET MVC的View是如何被呈現出來的?[設計篇]
  下一篇:go  ASP.NET MVC的Razor引擎:View編譯原理