摘要:介紹「Razor」— ASP.NET的一個新視圖引擎
[原文發表位置] Introducing 「Razor」 – a new view engine for ASP.NET
[原文發表時間] July 02, 2010 11:01 PM
我的團隊當前正在從事的工作之一就是為ASP.NET添加一個新的繪製引擎。
一直以來,ASP.NET MVC都支援 「繪製引擎」的概念—採用不同語法的模板的可插拔模組。當前ASP.NET MVC 「預設」的繪製引擎是ASP.NET Web表單使用的.aspx/.ascx/.master文件模板。而當今其他一些流行的ASP.NET MVC繪製引擎還包括Spark和NHaml。
我們正在打造一個新的繪製引擎,它是一個在HTML生成方面進行優化,採用專注於程式碼的模版解決方案。它的開發代號是「Razor」,第一個beta版本很快就會發佈出來。
設計目標
在設計和評估「Razor」時,我們牢記以下幾個目標:
· 緊湊、富有表現力和流暢:Razor盡量減少一個文件裡需要敲入的字元數,給你暢快淋漓的編碼體驗。與大部分模板的語法不同,你不會因為需要在HTML中標注伺服端程式碼塊而中斷敲程式碼的快感。程式碼分析器足夠聰明,能夠從你的程式碼裡推斷出是否為伺服端程式碼。這使得其簡潔、富有表現力的語法輸入能夠乾淨,快速,有趣。
· 上手快: Razor非常容易上手,你只需要瞭解很少的新東西就可以掌握它,使用你現有的程式設計語言和HTML知識就足夠了。
· 不是一個新的程式設計語言:我們盡量避免為Razor建立一個新的命令式語言。相反,我們希望讓程式設計師只使用現有的C#/VB(或其它)程式設計語言知識就能使用Razor,我們只不過在你選擇的程式設計語言裡,提供一個非常棒的基於模板生成HTML的標記語法。
· 可以使用任何文字編輯器編寫:Razor不要求什麼特別的工具,使用老古董的文字編輯器也可以高效程式設計(「記事本」就不錯)。
· 很好的智慧感應輸入提示:雖然Razor不是專為某個工具或者程式碼編輯器設計的,但它在Visual Studio裡還是有很讚的智慧提示功能。我們將會升級Visual Studio 2010和Visual Web Developer 2010使得它具有完整的編輯器智慧提示功能。
· 便於單元測試:新的繪製引擎支援對檢視執行單元測試,不需要控制器(controller)或者Web伺服器,而且可以包含在任意的單元測試專案中—不需要單獨的應用程式域(App-Domain)。
過去幾個月,我們一直在用Razor來編寫程式,並邀請了一些志願者(包括好幾組非.NET的 Web程式設計師)來做易用性研究。使用過它的人對它的評價都不錯。
彈性的選擇空間
ASP.NET最讚的地方之一就是大部分元件都是可插拔的。如果你發現有一個元件不好用,隨時都可以換成另外一個。
ASP.NET MVC的下一個版本將會包括一個新的「添加->檢視」對話框,它讓你可以方便地在建立新的檢視模板文件時選擇你想要使用的語法。它還便於你選擇任一個安裝在機器上的繪製引擎—選擇你感覺最自然的檢視方案:
Razor將會是ASP.NET MVC內建的繪製引擎之一。所有的檢視輔助(helper[i])方法和程式設計模型特性同時支援Razor和.ASPX繪製引擎。
你還將可以在單個站點或程式中混合使用多個繪製引擎寫的檢視模板。比如說,你可以編寫一些檢視,有些用.aspx文件,有些用.cshtml或者.vbhtml文件(分別是Razor的C#和VB版本的文件後綴名),而另外一些用Spark或者NHaml。你還可以在採用一種繪製引擎的檢視模板裡包含用另一種引擎編寫的局部檢視模板。總之,你有靈活的選擇空間。
Razor版的「Hello World」
Razor允許你從靜態的HTML頁面(或者任意的文字內容)開始,添加伺服端程式碼使其變成動態頁面。Razor的一個核心設計理念就是使編碼過程更加流暢,並且只要最少的按鍵次數就能快速地在HTML標記中添加伺服端程式碼。
讓我們來建立一個簡單的例子:「hello world」,它的最終輸出如下圖所示:
使用.ASPX「程式碼碎塊[ii]」的方式編寫
如果用現有ASP.NET的.ASPX標記語法來編寫上面的「Hello World」範例的話,我們需要在HTML標籤中使用」<%= %>」來標記「程式碼碎塊」:
仔細觀察就可以發現上例中每一個程式碼碎塊都需要5個字元( "<%= %>" )來標明程式碼的開始和結束位置。而且其中還有幾個字元在鍵盤上不是特別好敲到(特別是「%」鍵—它位於大部分鍵盤的中上部)。
使用Razor的語法編寫
在Razor中,你只需要用一個 "@" 字元就可以標識程式碼塊的開始,與 "<% %>" 程式碼碎塊不一樣,Razor不需要你明確指明程式碼塊的結束位置:
Razor分析器懂得程式碼塊中使用的C#或VB的語法—這就是為什麼上例中我們不需要明確關閉程式碼塊的原因。Razor可以自動識別出上面的語句是獨立的程式碼塊並悄悄地為我們關閉它們。
看看,即使是像「hello world」這樣微不足道的例子就為我們節省了12次鍵盤敲擊。而且在鍵盤上 "@" 字元還比 "%" 字元更容易按,敲起來更快也更流暢。
迴圈和內嵌HTML範例
讓我們來看看另外一個簡單的場景,比如說要列出一些商品(並在每樣商品旁標明價格):
用.ASPX "程式碼碎塊" 編寫
如果用ASP.NET現有的.ASPX標記語法,我們可能需要寫類似下面的程式碼來動態生成一個<ul>列表,裡面包含表示每個商品的<li>元素:
使用Razor語法
下面是生成同樣輸出的Razor版本的程式碼:
請注意上面」@」符號是如何開始一個「foreach」迴圈,並在迴圈內嵌入一行包含程式碼塊的HTML語句的。因為Razor分析器知道我們在程式碼塊裡用的C#語法,它可以識別出<li>標籤裡的內容應該被包含在foreach程式碼塊中,並迴圈處理它們。它甚至還知道末尾的「}」結束foreach迴圈。
Razor很聰明,還能識別出<li>標籤內的@p.Name和@p.Price是伺服端程式碼—並且在每次迴圈時執行它們。另外請留心Razor在HTML和程式碼混合的情況下,還能推導出@p.Name和@p.Price程式碼塊的結束位置。
不需要在你的模板中添加許許多多打開/關閉標記來編寫程式碼的感覺果然是酣暢淋漓。
If程式碼塊和多行語句
下面是其他幾個常見的場景:
If語句
跟上面的foreach範例一樣,你可以在if語句中直接嵌入內容(或其他C#或者VB語言元素),而不需要明確指明程式碼塊的開始和結束位置。例如:
多行語句
你可以像下面這樣,使用「@{ 程式碼 }」 標注多行語句:
請注意上例中,變數可以被多個程式碼塊引用—變數「message」在包含多行語句的「@{}」塊中定義,但也可以被@message程式碼塊使用。這個跟.aspx文件裡的」<% %>」和」<%= %>」的語法類似。
多符號語句
「@()」語法允許程式碼塊中有多個符號,例如,我們可以把上例中連接字串和數字的程式碼使用」@ ()」 程式碼塊這樣覆寫:
整合程式碼和內容
Razor解析器內建了很多語言智慧—髒活累活幫你做。
在HTML裡,它會不會影響」@」符號在電子郵件位址和其他地方的用法?
大部分情況下,Razor解析器都有足夠的能力推導出模板裡的一個「@」字元到底是在程式碼中用到,還是在靜態內容中用到。例如,我在下例中的郵件位址中使用了」@」字元:
當解析文件時,Razor會分析」@」字元右邊的內容來判斷它到底是程式碼(如果是CSHTML文件的話那就是C#程式碼,而如果是VBHTML文件的話那就是VB程式碼)還是靜態文字內容。上例中的程式碼會輸出以下的HTML(郵件位址被當作靜態內容輸出,而@DateTime.Now就被當作程式碼執行了):
如果碰到和程式碼一樣格式的內容(或者你想把程式碼當作內容看待),你可以明確地打@@來用另外一個」@」字元進行轉義。
識別內嵌的內容
當在一個if/else,foreach或者其他塊語句中內嵌HTML文字時,可以考慮用一個HTML或XML標籤將嵌套內容環繞起來,這樣可以更清楚地標明一個文字內容塊的開始。
例如,下例我使用<span>標籤包圍多行文字內容,而文字裡還有一個程式碼塊:
在客戶端的顯示結果如下—注意那個<span>標籤:
如果你不想在顯示文字內容時,把外面的標籤也輸出到客戶端,那可以考慮使用<text>將嵌套內容括起來:
上面的程式碼在客戶端的輸出結果如下—請注意外面的<text>標籤沒有被輸出:
HTML編碼
預設情況下,」@」語句塊生成的內容會自動對HTML程式碼進行過濾和轉換[Yimin1] ,用來更好地防範XSS垮站腳本攻擊。
版面設計/母版頁的情況—基礎篇
在站點中保持一致的頁面觀感風格非常重要。ASP.NET 2.0引入了「母版頁面(master page)」的概念,就是用來幫助在使用基於.aspx的頁面或模板時實現這個功能。Razor同樣也支援這個概念,它用的是「版面設計頁面(layout pages)」—你可以先定義一個通用的站點模板,然後在站點其他檢視或頁面繼承模板定義的統一觀感。
版面設計的一個簡單範例
下面是一個簡單的版面設計頁面範例,文件將會被保存為「SiteLayout.cshtml」。它包含了所有要放在頁面裡的靜態HTML文字內容和動態的伺服端程式碼。接著我們添加了一個名為「RenderBody()」的輔助函數,放在模板中需要根據所請求的URL而「填入」具體內容的地方:
接下來我們再建立一個名為「Home.cshtml」的檢視模板,它只包含了必要的文字內容和程式碼來構成所請求頁面的具體內容,外圍的內容則由版面模板提供:
請留意上面在Home.cshtml文件中我們怎麼來明確地設置「LayoutPage」屬性。它指明了我們期望用SiteLayout.cshtml作為這個檢視的版面設計模板。我們還可以在ASP.NET MVC 控制器(Controller)叫用Home.cshtml這個檢視模板的時候,可以指定這個版面設計文件,或者將其配置為站點預設的版面設計模板(這種情況下,我們只需要在專案中的一個文件中指定它,而所有的檢視模板都會自動採用它)。
當我們將Home.cshtml作為一個檢視模板顯示時,它會合併版面設計頁和子頁面的內容,然後將下面的內容發送到客戶端:
簡潔、清晰、富有表現力的程式碼
上例中值得注意地方的還有一點,就是版面定義和在檢視/頁面中使用它們的語法既清晰又簡練。上面列出的SiteLayout.cshtml和Home.cshtml程式碼截圖已經包含了兩個文件所有的程式碼—沒有額外的配置步驟或冗余的標籤,沒有<%@ Page %>前置詞,也不需要設置其它的標籤或者屬性。
我們盡力使編寫出來的程式碼簡潔流暢。我們還希望任何人都可以在一個文字編輯程式中打開、編輯和調整/自訂它們。不需要程式碼生成或者智慧提示(Intellisense)。
版面設計頁面/母版頁的情況—覆寫部分內容
版面設計頁面可以有選擇性地定義幾個不同的「節」,允許基於這個版面設計的檢視模板通過「填入」自訂的內容來覆寫它。這就允許你在檢視中覆寫版面設計頁裡不連續的內容段落,從而使你的站點版面設計更有彈性。
例如,我們回到SiteLayout.cshtml文件,並在其中定義兩個節,這樣檢視模板可以有選擇性的填充這兩個節。我們將節的名稱命名為「menu」和「footer」—然後在RenderSection()輔助函數傳入optional=true這個參數來表明它們是可選填充節(我們可以用C#最新的optional參數語法來做這件事情,我在前面的部落格裡也提到了這個用法)。
因為兩個節被標記成了「可選」,不需要在Home.cshtml文件中定義它們。即使沒有它們,站點依然可以正常工作。
讓我們回到Home.cshtml並自訂Menu和Footer節。下面的截圖包含了Home.cshtml裡所有的內容—再沒有其它內容了。註:我已經把LayoutPage設置為站點範圍的預設模板了—所以它沒有顯示在裡面。
我們覆寫的」menu」和「footer」節定義在文件裡的以相應的名字命名的@section { }塊裡面。我們刻意不要求你在節裡包含「main/body」內容,相反將它們內聯在頁面裡(除了節省鍵盤敲擊的次數以外,還便於你在版面設計頁面中添加新的節以後,不需要再回去修改所有已有頁面的語法)。
當再次將Home.cshtml以檢視模板的形式顯示的時候,它現在會合併版面設計頁和子頁面裡的內容,並將兩個自訂節覆寫的內容整合進來,最終發送到客戶端的內容如下:
封裝和重用HTML輔助函數
我們剛剛講過如何使用版面設計頁給站點提供統一的觀感。現在讓我們看看如何通過建立可復用的「HTML輔助函數」來把生成HTML的功能封裝成一個函數庫,這樣可以在整個站點復用—甚至在多個站點復用。
基於程式碼的HTML輔助函數
ASP.NET MVC有一個「HTML輔助函數」的概念—封裝了生成HTML的邏輯並可以用在程式碼塊裡的函數。它們當前還都是純粹通過程式碼實現的,一般通過擴展函數(Extension Method)實現。現有的所有內建在ASP.NET MVC裡的HTML擴展函數都可以使用在「Razor」繪製引擎裡(不需要修改任何程式碼):
宣告式的HTML輔助函數
使用純程式碼的類來生成HTML可以工作—但不是很理想。
我們來看Razor的另外一個特性,使用簡單的更具描述性的方法建立可復用的HTML輔助函數。我們計劃讓你可以使用類似下面的 @helper { }宣告式語法建立可復用的輔助函數:
你將可以把包含有這種輔助函數的.cshtml文件放到一個Views\Helpers資料夾,然後就可以在站點上使用它們了(再沒有其它的步驟了):
請注意上例中我們可以為ProductListing()函數定義參數。這樣你就可以為函數傳入任意的參數了(而且還能完全利用選擇性參數,nullable型別,泛型等現有程式設計語言的特性),另外還有Visual Studio強大的偵錯支援。
註:@helper語法在Razor的第一個beta版裡還沒有—但我們希望能在下一個發佈裡把它包含進來。基於程式碼的輔助函數在第一個Beta版本裡就可以使用。
傳入內聯模板作為參數
Razor中另一個很有用(或者可以說是相當了得)的功能就是允許向輔助函數傳入「內聯模板」參數。這些「內聯模板」可以同時包含HTML和程式碼,而且可以被輔助函數即行叫用。
下例中的「Grid」HTML輔助函數就是通過這種技術在客戶端呈現一個DataGrid:
上例中Grid.Render()函數叫用使用的是C#語法。我們使用了新的語法—C#命名參數把強型別的參數傳給Grid.Render函數。這同時也意味著我們可以使用全部智慧提示,還有編譯時的語法檢查功能。
在定義列的時候傳給「format」參數的就是一個「內聯模板」—它同時包含了自訂的html和程式碼,它們是用來自訂資料的顯示方式的。更厲害的是,Grid輔助函數可以把我們的 「內聯模板」當作一個委派來叫用,想幾時叫用就幾時叫用,想叫用多少次就叫用多少次。在上面的場景裡,每呈現grid的一行,就會叫用它一次—並通過傳入「item」變數,以便我們的模板可以顯示恰當的內容。
這個功能允許你開發功能更強大的HTML輔助函數。今後你既可以使用程式碼方式(跟現在建立擴展函數的方式一樣),也可以使用宣告式的@helper { }方式來編寫HTML輔助函數。
對Visual Studio的支援
前面說過,Razor的一個目標就是盡量減少鍵盤敲擊次數,而且使用普通的文字編輯器就可以編寫(記事本就很不錯了)。我們通過保證語法清晰簡單幹練來實現這個目標。
Visual Studio也支援Razor, 使你在裡面編寫Razor程式碼時感覺更豐富。對於基於Razor的原始程式文件,我們提供了完整的HTML、JavaScript和C#/VB程式碼智慧提示功能:
請注意,上面我們甚至為嵌入在foreach迴圈體裡面<li>元素的Product對象的「@p.」啟用了智慧提示。另外還要注意的地方是,在「解決方案資源管理器」的「\View」資料夾裡,同時存放了.aspx和.cshtml檢視模板。你可以在單個程式中使用多個繪製引擎—便於你選擇最適合你的引擎。
總結
我們認為「Razor」是一個非常好的新的繪製引擎,它提供了流暢的專注於程式碼的模版方案。它的編碼工作流快速、富有表現力並有趣。語法簡練並節省按鍵次數,同時還提升了程式碼的可讀性。它會作為內建的繪製引擎並隨下一個版本的ASP.NET MVC發佈。你也可以把單獨的.cshtml/.vbhtml放到程式裡,並把它們當作獨立的頁面執行—這樣你就也可以在ASP.NETWeb表單程式中使用它了。
前幾個月裡,試用過它的開發人員對它的評價都非常好。我們馬上就要發佈它的第一個beta版,並期待收到您對它的回饋。
希望這能對您有所幫助。
附:[除了寫部落格以外,我現在也使用推特(Twitter)來及時更新狀態和分享連結,您可以到這個位址「推」我一下:twitter.com/scottgu]
標籤: ASP.NET, .NET, Community News, MVC
[i] HTML Helper: 本文都翻譯成HTML輔助函數。
[ii] 原文為Code Nuggets,其實就是Code Block的意思,Code Nuggets最先是ASP.NET團隊叫出來的。Nugget原意是金塊,肯德基的「上校雞塊」的英文原名也是「Nugget」,之所以取這個名字,是因為炸雞塊和金塊都是黃色的……也許這就是為什麼程式碼塊被稱作「Nugget」的原因吧,為了區別程式碼塊,翻譯成「程式碼碎塊」。