在前面做 MVC 流程分析的時候,我曾提到過,我更喜歡用 Velocity 來代替 WebForm。這倒不是說 WebForm 本身有什麼不好,更不是某些人所說的 "效能" 原因。在實際開發中,我們所面對的頁面往往很複雜,有諸多看上去很頭疼的 div、style、span 什麼的,我想沒有幾個 "程式員" 願意去調整某些個邊框布局或者顏色設定。WebForm 基於設計和封裝的原因,會使用大量的 "非標準 HTML 程式碼",這就造成了一個尷尬的局面 —— 美工和頁面程式員的矛盾。
美 工: "你把我設計的頁面全搞亂了,你應該用這個樣式……"
程式員: "廢話,早跟你說過了。你必須按這樣的格式來寫……"
美 工: "我修改了頁面,已經發給你了……"
程式員: "沒空,你自己不會改啊?"
Velocity 是什麼,無需我多言。使用一種非常簡單的標記或邏輯控制就可以將我們的資料 "嵌入" 到美工設計的頁面中。我們可以讓美工事先將需要輸出資料的位置用 "<!-- 資料1 -->" 之類的東東標註出來,我們也只需關心資料是否正確輸出,至於頁面的調整和我們基本沒有關係。當然,對於有一定興趣的美工或者組裡面的新人,完全可以在一天甚至半天內掌握 Velocity 的使用方法,何樂而不為?要知道,多數網站會頻繁調整頁面顯示,不像我們通常用來示範的那幾個 Example 那麼簡單。
要使用自訂視圖引擎,我們實現需要找到 "注入" 位置。我們在前面的流程分析時,就已經鎖定目標 —— ControllerBuilder。
public class GlobalApplication : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
ControllerBuilder.Current.SetControllerFactory(new NVelocityContrillerFactory());
}
}
NVelocityContrillerFactory 可以直接繼承自 DefaultControllerFactory,我們 override 它的方法,將產生的 Controller.ViewEngine 賦值為我們自訂的視圖引擎即可。
public class NVelocityContrillerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(Type controllerType)
{
var controller = base.GetControllerInstance(controllerType);
(controller as Controller).ViewEngine = new NVelocityViewEngine();
return controller;
}
}
接下來,我們開始寫我們自己的視圖引擎。
public class NVelocityViewEngine : IViewEngine
{
static NVelocityViewEngine()
{
Velocity.SetProperty("resource.loader", "file");
Velocity.SetProperty("file.resource.loader.path", HttpContext.Current.Server.MapPath("~"));
Velocity.SetProperty("file.resource.loader.cache", true);
//Velocity.SetProperty("file.resource.loader.modificationCheckInterval", 10L); //seconds
Velocity.SetProperty("input.encoding", "utf-8");
Velocity.Init();
}
public void RenderView(ViewContext viewContext)
{
var locator = new NVelocityViewLocator() as IViewLocator;
var templatePath = locator.GetViewLocation(viewContext, viewContext.ViewName);
var template = Velocity.GetTemplate(templatePath);
var context = new VelocityContext();
if (viewContext.ViewData != null)
{
var properties = viewContext.ViewData.GetProperties(); // 擴充方法,擷取全部屬性。
foreach (var item in properties.Keys)
{
context.Put(item, properties[item]);
}
}
template.Merge(context, viewContext.HttpContext.Response.Output);
}
}
也很簡單,除了在靜態構造裡面初始化 NVelocity 以外,我們還對 ViewData 做了些處理。當然,我們最好再來一個 NVelocityViewLocator,以便支援從多個目錄中找尋模板檔案。
public class NVelocityViewLocator : ViewLocator
{
public NVelocityViewLocator()
{
this.ViewLocationFormats = new[]
{
"/Views/{1}/{0}.vm",
"/Views/{1}/{0}.htm",
"/Views/{1}/{0}.html",
"/Views/Shared/{0}.vm",
"/Views/Shared/{0}.htm",
"/Views/Shared/{0}.html",
};
this.MasterLocationFormats = new string[0];
}
}
我們可以支援更多的副檔名,呵呵~~~~~
好了,測試一下。
public class HomeController : Controller
{
public void Index()
{
this.RenderView("index", new { Title = "TemplateEngine Test", Name = "Tomcat" });
}
}
~/View/Home/index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/....dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>$Title</title>
</head>
<body>
Hello, $Name!
</body>
</html>
輸出結果
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/....dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>TemplateEngine Test</title>
</head>
<body>
Hello, Tomcat!
</body>
</html>
忘了一件重要的事情,NVelocity 可以在 Castle MonoRail 的包裡找到。