最近剛好有要寫寄Email的程式,在代碼中寫HTML覺得很呆,抽出代碼外寫到txt或html檔當範本,由程式執行時在載入檔案時用Regex換關鍵字又覺得不夠好用,而且因為有時會有要判斷一些條件,就會寫一堆if esle在代碼中看了就討厭,因為寫MVC久了,就很希望範本也可以像MVC中的View,傳Model過去,在View層決定如何呈現,而更希望是使用Razor語法來寫範本,花了時間研究,找到RazorEngine,使用它來載入檔案,由它來編譯與執行並輸入結果。
RazorEngine
官網網址:http://razorengine.codeplex.com/
在找到RazorEngine之前曾經想過其他的方案,如T4與V8 Engine載jquery.template,但T4如果要獨立於MSBuild或Visual Studio執行有點麻煩,而V8 Engine我又不想在Class Library專案中放一堆js檔,後來就想到Razor,因為Razor的相關處理都是寫在System.Web.Razor,雖然Namespace叫System.Web,但根本沒有載入任何的System.Web相關的組件(如圖一),所以我肯定它可以獨立在非Web環境於中使用,在研究如何運用的期間,就發現早在去年就有人寫好放在CodePlex上,我太落伍了。
圖一 System.Web.Razor的參考,只有載入基本的三個組件
使用範例(部份直接使用官網的範例)
一般用法
1 | string template = "Hello @Model.Name! Welcome to Razor!" ; |
2 | string result = Razor.Parse(template, new { Name = "World" }, "Sample" ); |
最後一個參數Name是選項參數,但建議給值因為關係到快取,如果有給,下次使用相同名稱的範本會用快取的,而且關係到範本的Include,雖然RazorEngine不能用RanderAction或RanderPartial但有提供Include可以載入,也是使用此Name為關鍵字。
使用.cshtml檔案
只要是副檔名為.cshtml,就算不在ASP.NET MVC3專案中,編輯.cshtml的方式都相同(但缺web.config中的設定,所以有些功能出不來),當然範本檔副檔名不一定要為.cshtml,但有IDE支援總比沒有好。
只是寫法要變,因為ASP.NET MVC 3的BaseType是System.Web.Mvc.WebViewPage,而RazorEngine的BaseType是RazorEngine.Templating.TemplateBase,除了Model屬性外其他的Html、Url、Ajax等等屬性都不沒有,但是C#的語法都支援。
1 | string result = Razor.Parse(File.ReadAllText( "test.cshtml" ), new { IsVip = true , Name = "Wade" }); |
進階用法
Template的Include
RazorEngine雖然不支援RanderAction或RanderPartial,不過他有提供Include方法,載入已經Compile(或Parse)過的範本,以下是使用範例:
1 | string template1 = "Hello @Model.Name" ; |
2 | string template2 = "This is my sample template, @Include(\"Template1\",Model)" ; |
3 | Razor.Compile(template1, "Template1" ); |
4 | string result = Razor.Parse(template2, new { Name = "Wade" }); |
內嵌方法
如果只有單一個範本會用到的方法可以使用內嵌方法,以下是使用範例:
2 | @helper MyMethod(string name) { |
6 | @MyMethod(Model.Name)! Welcome to Razor!" ; |
8 | string result = Razor.Parse(template, new { Name = "World" }); |
BaseType
每一個範本,最後都會使用System.Web.Razor.RazorTemplateEngine編譯成Class,而且繼承BaseType,所以BaseType決定了範本能使用的功能,如果還是不清楚以第一個範例為例:
1 | @"@{var name=""Would"";} |
這些內容經剖析後會變成這樣
01 | //與第一個範例拿掉註解後所產生的test.cs檔案相同 |
02 | namespace RezorCodeDomSample |
04 | public class MyTemplateRsult : RezorCodeDomSample.MyTemplate |
06 | public MyTemplateRsult() |
10 | public override void Execute() |
14 | WriteLiteral( "Hello " ); |
18 | WriteLiteral( "!!\r\n" ); |
這樣有比較清楚BaseType的功用了嗎?
所以如果希望所有的範本都可以使用的功能,可以繼承RazorEngine.Templating.TemplateBase,將擴充功能寫在子類別,然後註冊子類別,以下是使用範例:
01 | public abstract class MyCustomTemplateBase<T> : TemplateBase<T> |
03 | public string ToUpperCase( string name) |
05 | return name.ToUpperCase(); |
10 | Razor.SetTemplateBase( typeof (MyCustomTemplateBase<>)); |
12 | string template = "My name in UPPER CASE is: @ToUpperCase(Model.Name)" ; |
13 | string result = Razor.Parse(template, new { Name = "Matt" }); |
載入組件與命名空間
RazorEngine註冊CodeDom所使用的組件是使用AppDomain.CurrentDomain.GetAssemblies()的方式,所以只要在專案中所參考的組件,範本中都可以使用,還有為了增加撰寫的便利性,可以增加命名空間,以下是使用範例:
1 | Razor.DefaultTemplateService.Namespaces.Add( "System.Xml" ); |
4 | var xml = new XmlDocument(); |
8 | string result = Razor.Parse< string >(template, "<Test>test</Test>" ); |
設定檔
跟ASP.NET MVC 3一樣,命名空間或BaseType等等都可以寫在設定檔中,詳情請參考官網
增加區段
1 | <?xml version= "1.0" encoding= "UTF-8" ?> |
4 | <section name= "razorEngine" type= "RazorEngine.Configuration.RazorEngineConfigurationSection, RazorEngine" requirePermission= "false" /> |
增加設定
01 | <razorEngine activator= "RazorEngineSamples.Activators.MySampleActivator, RazorEngineSamples" |
02 | factory= "RazorEngine.Web.WebCompilerServiceFactory, RazorEngine.Web" > |
04 | <add namespace = "System.Linq" /> |
06 | <templateServices default = "myCustomTemplateService" > |
07 | <add name= "myCustomTemplateService1" language= "CSharp" /> |
08 | <add name= "myCustomTemplateService2" templateBase= "MyTemplateBase" /> |
TemplateService可以讓某些範本使用不同的設定,以下是使用範例:
1 | var service = Razor.Services[ "myCustomTemplateService" ]; |
2 | string result = service.Parse( "Hello @Model.Name" , new { Name = "World" }); |