学习ASP.NET MVC设计模式(5):ViewEngine 深入解析与应用实例(2)
四.开发自定义ViewEngine
下面通过示例演示如何开发自己的ViewEngine.其中要用到StringTemplate这个模板引擎, 在老赵的的MVC视频教程中也使用的此引擎演示ViewEngine. StringTemplate负责翻译一个模板页上面的占位符(aspx页面中的内嵌代码), 输出HTML.目前在StringTemplate的官方网站上已经提供了针对Asp.Net Mvc的ViewEngine.但是官方的ViewEngine模板没有使用VirtualPathProviderViewEngine基类.下面我将提供一种不能说更好但至少是另一种实现的StringTemplateViewEngine.其中需要使用StringTemplate的模版功能.
1. 实现IView接口
要开发一个自己的ViewEngine, 首先要创建实现了IView接口的类, 在此我们创建了名为StringTemplateView的类
public class StringTemplateView : IView { #region 属性 Properties /// <summary> /// StringTemplate 对象, 在构造函数中创建 /// </summary> private StringTemplate StringTemplate { get; set; } #endregion #region 构造函数 Constructed Function private StringTemplateView() { //不用于使用不带参数的构造函数 this.StringTemplate = new StringTemplate(); } public StringTemplateView(StringTemplate template) { //null check if (template == null) throw new ArgumentNullException("template"); //set template this.StringTemplate = template; } #endregion #region IView 成员 void IView.Render(ViewContext viewContext, System.IO.TextWriter writer) { foreach(var item in viewContext.ViewData) { this.StringTemplate.SetAttribute(item.Key.ToString(), item.Value.ToString()); } //为StringTemplate设置HttpContext this.StringTemplate.SetAttribute("context", viewContext.HttpContext); //输出模板 NoIndentWriter noIndentWriter = new NoIndentWriter(writer); this.StringTemplate.Write(noIndentWriter); } #endregion }
StringTemplateView是在StringTemplate视图引擎中View角色的抽象, 所以功能是实现呈现页面的Render方法. StringTemplate的核心功能就是一套自己定义的模板输出引擎, 所以在构造StringTemplateView对象是必须传入一个StringTemplate实例,在Render时只是调用StringTemplate对象的模板输出方法.
2. 实现IViewEngine接口
有了IView对象. 接下来就要实现最核心的IViewEngine接口. 在具体的StringTemplateViewEngine类中, 要返回一个带有StringTemplateView对象的ViewEngineResult.
在我的实现方法中,使用了ASP.NET MVC已经提供的VirtualPathProviderViewEngine类作为我们的基类. VirtualPathProviderViewEngine类实现了IViewEngine接口的方法, 提供了在程序中寻找View物理文件路径的机制, 搜索时要使用在派生类中赋值的搜索路径.
下面是我们的StringTemplateViewEngine类实现:
public class StringTemplateViewEngine : VirtualPathProviderViewEngine { private string _AppPath = string.Empty; #region 属性 Properties public static FileSystemTemplateLoader Loader { get; private set; } public static StringTemplateGroup Group { get; private set; } #endregion public StringTemplateViewEngine(string appPath) { _AppPath = appPath; Loader = new FileSystemTemplateLoader(appPath); Group = new StringTemplateGroup("views", Loader); MasterLocationFormats = new[] { "/Views/{1}/{0}.st", "/Views//Shared/{0}.st" }; ViewLocationFormats = MasterLocationFormats; PartialViewLocationFormats = MasterLocationFormats; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { return this.CreateView(controllerContext, partialPath, String.Empty); } protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { StringTemplate stringTemplate = Group.GetInstanceOf(viewPath.Replace(".st", "")); StringTemplateView result = new StringTemplateView(stringTemplate); return result; } }
注意首先在我们的StringTemplateViewEngine中,提供了搜索模板文件的路径,即先从View/{controller}中搜索,再从View/Share中搜索. 这样VirtualPathProviderViewEngine基类的方法就可以找到我们.st模板文件的具体路径, 然后使用StringTemplateViewEngine中提供的创建StringTemplateView的方法, 根据具体路径创建StringTemplateView对象.
在一些开源的ViewEngine中,尤其是MvcContrib项目中的ViewEngine都将创建View对象的功能放在一个ViewFactory类中, 个人认为这个更好的设计, 但是由于我们的StringTemplateViewEngine要继承VirtualPathProviderViewEngine, 所以没办法拆分创建View的方法.
至此我们已经完成了StringTemplateViewEngine的全部工作.