The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

ASP.NET MVC 開發心得分享 (26):關於 Razor 的運作原理與改進特性

ASP.NET MVC 3 開始提供的 Razor V1 改變了以往使用 WebFormView 的寫作習慣,讓我們在 檢視頁面 (View Page) 中的 HTML 與伺服器端的語法 (Razor) 混和得更漂亮,不再有醜陋的 <% … %> 符號了。然而從 ASP.NET MVC 4 開始,在 Razor 推出了第二版,有許多增強的特性,本篇文章將介紹這��增強的特性。而到了 ASP.NET MVC 5 推出了 Razor V3 版,這版只有修正一些程式錯誤,並沒有功能增強。

由於使用 Razor 語法的檢視頁面,背後採用的是 ASP.NET Web Pages 技術,整個頁面都會交由 ASP.NET Web Pages 進行控管。而事實上,Razor 頁面則是一種「動態編譯」的技術,當 ASP.NET MVC 網站在執行的時候,會自動監視所有 *.cshtml 檔案的變更,只要有偵測到檔案異動,就會自動重新產生新的、相對應的 *.cs 程式碼,預設這些動態產生的程式碼會被寫入到 Temporary ASP.NET Files 資料夾下,可能的路徑有:

  • 執行在 x64 架構下:
    C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files
  • 執行在 x86 架構下:
    C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files

這個暫存路徑其實也可以透過 web.config 進行設定,設定的範例如下:

<system.web>
<compilation tempDirectory="G:\TempASP.NETFiles\">
...
</compilation>
</system.web>
若你想看看這些 Razor 頁面產生的 *.cs 檔案在哪,你可以隨便找一張 View 並在 Razor 頁面的最上面第一行加上 @model <> 故意寫出一個錯誤的 Razor 語法,當顯示例外訊息時,就會看到該檔案產生的 *.cs 檔案路徑了,如下圖示:
執行該頁面就會得到以下錯誤,錯誤訊息下方的「原始程式檔」就是這張 Razor 所建立的 *.cs 檔案,檔名當然是一串看不懂的亂碼。
接著你將該程式碼打開,就可以看到完整的 *.cs 檔案:
從這個檔案你就可以大致上猜到 Razor 使怎樣的運作原理:
  1. 網站執行時,先找出所有 *.cshtml 頁面,並將所有頁面轉換成 *.cs 格式的程式碼。
  2. 轉換成 *.cs 格式的程式碼的過程中,會解析 Razor 專屬的語法,並轉換成標準的 C# 程式碼。
    • 例如 @model, @section, @Html.Action, .. 諸如此類的語法。
  3. 轉換成功後,會交由 C# 編譯器進行程式碼編譯,然後轉成 .NET 組件 (*.dll) 並自動載入到 AppDomain 裡
  4. 因此 ASP.NET MVC 就可以順利執行這些 Razor 頁面。

所以,ASP.NET MVC 的 Razor 頁面,會有一個轉換成 C# 的過程,也就是說他並不是一個單純的 HTML,而是可以設計出一些好用的功能與特性。例如 ASP.NET MVC 6 開始將會有個 TagHelper 功能,這個功能可以伺服器端的輸出更加「標籤化」,這部分我之後會有專文介紹。

當瞭解了 Razor 的潛力無窮後,我就開始來介紹這些新增的特性:

更強大的 URL 解析能力

早期的 ASP.NET MVC 要在 View 裡面載入一個 JavaScript 檔案,必須用以下語法:
  • <script src='@Url.Content("~/Scripts/jquery-1.10.2.js")'></script>
現在,你可以很輕鬆地改寫成以下語法:
  • <script src="~/Scripts/jquery-1.10.2.js"></script>
不僅僅是 JavaScript 而已,只要 HTML 標籤內的任意屬性 (Attributes),都可以使用 ~/ 語法來代表網站根目錄。例如我們用 Angular 的 ng-src 屬性,他也是可以認得的:
  • <img ng-src="~/Content/test.jpg"/>
不過要特別注意的地方是,如果你用 HTML5 的 data-* 屬性 Razor 是不會幫你轉換 ~/ 的!
以下範例用到的 data-uri 由於屬於 HTML5 的自訂擴充屬性,所以 Razor 預設不會幫你解析 ~/ 這個語法:
  • <button data-uri="~/Content/test.txt">My Button</button>
 

條件式屬性輸出 (Conditional Attribute)

我們以一個 HTML 表單欄位為例,假設我們要透過一個 ViewBag.IsAutoFocus 屬性來控制 View 頁面上的某個 input 欄位是否要設定為自動聚焦,我們可以在 input 標籤中加上 autofocus 屬性 (這是 HTML5 的新增屬性),例如:
  • <input type="text" value="" autofocus />
  • <input type="text" value="" autofocus="autofocus" />

※ 上述兩行都是有效的 HTML5 語法。

然後要透過 ViewBag.IsAutoFocus 來控制是否輸出 autofocus 屬性,以前就非常麻煩,你可能會這樣寫:

<input type="text" value=""
       @if(ViewBag.IsAutoFocus) { <text>autofocus</text> } />

而在 Razor V2 之後,你可以改成以下語法:

@{
ViewBag.IsAutoFocus = true;
}

<input type="text" value="" autofocus="@ViewBag.IsAutoFocus" />
這段輸出的 HTML 將會是:
<input type="text" value="" autofocus="autofocus" />
如果是以下程式,也就是說如果將 ViewBag.IsAutoFocus 改為 false:
@{
ViewBag.IsAutoFocus = false;
}

<input type="text" value="" autofocus="@ViewBag.IsAutoFocus" />
這段輸出的 HTML 將會是:
<input type="text" value="" />

是不是語法漂亮許多呢! ;-)

我還另外在 .NET Fiddle 建立了一個範例,你也可以看看!

基本上這個例子是展示,當你想輸出一個 CSS 的 class 到 HTML 標籤中時,如果該 class 屬性的內容為 null 時,那麼 Razor 將會連同 class 一起隱藏起來,這也算是非常貼心的功能!

 

 

相關連結