NVelocity for ASP.NET MVC

來源:互聯網
上載者:User
文章目錄
  • NVelocityViewEngine
  • NVelocityView
  • ExtensionDuck

在我的這篇博文中,有這麼一段話:“我一直在想,有沒有辦法可以單獨限制View中的代碼的存取權限,類似於trust level,只是這個trust level是用來限制模板中的代碼。”。有讀者johngeng問,為什麼要用trust level來鎖住view,他不是很理解。我的本意是,希望在view中,開發人員只能寫某一些特定功能的代碼,調用某一些特定開放的API,對於大部分安全級比較高的代碼,比如讀寫檔案等API或類庫,不允許在view當中使用。這對於我們將模板開放出來,線上提供給我們的使用者去修改的需求下是非常重要的。而目前,不管WebForm還是Razor,都是非常自由的模板,在View能做的事情等同於Controller或其它地方所寫的代碼,這樣View就不允許開放出來由使用者線上修改。

在相同的博文裡面,還是那位讀者johngeng提到它更喜歡$而不是@,由於我之前並不瞭解NVelocity,所以我誤解為它是在說用戶端開發包jquery。現在看來,他說的應該就是NVelocity,也許他覺得此人不可教,他並沒有直接回複我的疑問,這也只能怪自己知識面太窄了。

若不是最近在為項目添加多模板引擎的支援,或許我永遠也無法得到以上兩個問題的答案,而這兩個答案都與NVelocity有關。雖然我平常肯定也見過NVelocity這個詞,但到要選擇除WebForm以外的模板引擎,我還是完完全全沒有記起他,還是同事@浪子提醒我NVelocity這個模板引擎值得一試。看了官方的文法介紹後,我不得不說它是一種非常簡潔且實用的模板,同時又不失它的靈活性和安全性。我所指的靈活性是它不像StringTemplate那樣,限制的那麼死,連個對象的函數都不允許調用。安全性方面又可以滿足我希望模板上限制開發人員只能在模板上調用指定的API。到目前為止,NVelocity仍然讓我非常滿意。

在ASP.NET MVC切換視圖引擎非常簡單,在ASP.NET MVC1.0出來以後,MvcContrib就曾經提供了多種視圖引擎的切換選擇,但是在最近的版本中,我卻始終沒有找到相關的代碼,應該是這些代碼已經被移出去了,但它的介紹文檔中還沒有刪掉相關的主題。還好在@重典童鞋的部落格上找到了他從MvcContrib中提取出來的實現。但是這個實現相對於MVC3來說,已經相對過時了,有些介面已經改變或被移除了,比如IViewLocator這個介面就已經不存在了。還有就是,它去掉了原先支援的調用HtmlHelper擴充方法的功能,而我最重要的就是要支援擴充函數,因為我自訂了一些必須的擴充方法。下面我們就來看看NVelocity for ASP.NET MVC幾個類的詳細情況:

NVelocityViewEngine

在之前的實現中,直接實現了IViewEngine這個介面,尋找View的路徑是通過實現IViewLocator來定位。在MVC2當中,修改了這部分的實現,MVC內部提供了VirtualPathProviderViewEngine這個模板方法類,在子類當中,我們中需要設定一下我們要尋找的路徑格式,其它的事件就可以交給模板方法類來完成,這樣一方面可以簡化我們的實現,另一方面還可以和預設的路徑尋找方式統一。

同時,由於我使用Nvelocity內建的相對檔案路徑的方式來尋找模板,而使用VirtualPath的風格,因此在找到VirtualPath後,我們需要轉換成實際的實體路徑,直接通過實體路徑來載入模板內容,而內建的FileResourceLoader並不支援從實體路徑載入模板,所以我們還要額外實現一下FileResourceLoader,讓支援從實體路徑的載入方法。這兩個類的代碼如下:

public class FileResourceLoaderEx : FileResourceLoader{    public FileResourceLoaderEx() : base() { }    private Stream FindTemplate(string filePath)    {        try        {            FileInfo file = new FileInfo(filePath);            return new BufferedStream(file.OpenRead());        }        catch (Exception exception)        {            base.runtimeServices.Debug(string.Format("FileResourceLoader : {0}", exception.Message));            return null;        }    }    public override long GetLastModified(global::NVelocity.Runtime.Resource.Resource resource)    {        if (File.Exists(resource.Name))        {            FileInfo file = new FileInfo(resource.Name);            return file.LastWriteTime.Ticks;        }        return base.GetLastModified(resource);    }    public override Stream GetResourceStream(string templateName)    {        if (File.Exists(templateName))        {            return FindTemplate(templateName);        }        return base.GetResourceStream(templateName);    }    public override bool IsSourceModified(global::NVelocity.Runtime.Resource.Resource resource)    {        if (File.Exists(resource.Name))        {            FileInfo file = new FileInfo(resource.Name);            return (!file.Exists || (file.LastWriteTime.Ticks != resource.LastModified));        }        return base.IsSourceModified(resource);    }}public class NVelocityViewEngine : VirtualPathProviderViewEngine, IViewEngine{    public static NVelocityViewEngine Default = null;    private static readonly IDictionary DEFAULT_PROPERTIES = new Hashtable();    private readonly VelocityEngine _engine;    static NVelocityViewEngine()    {        string targetViewFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "views");        //DEFAULT_PROPERTIES.Add(RuntimeConstants.RESOURCE_LOADER, "file");        DEFAULT_PROPERTIES.Add(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, targetViewFolder);        DEFAULT_PROPERTIES.Add("file.resource.loader.class", "NVelocityEngine.FileResourceLoaderEx\\,NVelocityEngine");        Default = new NVelocityViewEngine();    }    public NVelocityViewEngine()        : this(DEFAULT_PROPERTIES)    {    }    public NVelocityViewEngine(IDictionary properties)    {        base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.vm", "~/Views/Shared/{0}.vm" };        base.AreaMasterLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.vm", "~/Areas/{2}/Views/Shared/{0}.vm" };        base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.vm", "~/Views/Shared/{0}.vm" };        base.AreaViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.vm", "~/Areas/{2}/Views/Shared/{0}.vm" };        base.PartialViewLocationFormats = base.ViewLocationFormats;        base.AreaPartialViewLocationFormats = base.AreaViewLocationFormats;        base.FileExtensions = new string[] { "vm" };        if (properties == null) properties = DEFAULT_PROPERTIES;        ExtendedProperties props = new ExtendedProperties();        foreach (string key in properties.Keys)        {            props.AddProperty(key, properties[key]);        }        _engine = new VelocityEngine();        _engine.Init(props);    }    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)    {        Template viewTemplate = GetTemplate(viewPath);        Template masterTemplate = GetTemplate(masterPath);        NVelocityView view = new NVelocityView(controllerContext, viewTemplate, masterTemplate);        return view;    }    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)    {        Template viewTemplate = GetTemplate(partialPath);        NVelocityView view = new NVelocityView(controllerContext, viewTemplate, null);        return view;    }    public Template GetTemplate(string viewPath)    {        if (string.IsNullOrEmpty(viewPath))        {            return null;        }        return _engine.GetTemplate(System.Web.Hosting.HostingEnvironment.MapPath(viewPath));    }    }
NVelocityView

主要實現IView介面,實現Render方法來將模板和當前的上下文結合之後輸出出來。這個類還實現了,IViewDataContainer好像不是特別必要。NVelocity的Render也很簡單,只是把所需要的對像塞到NVelocity執行的上下文當中,然後調用一下Merge方法就OK了。這裡要特別說明的是,在NVelocity模板上面,我們可以調用內容物件的中的任何屬性和方法,但是沒有辦法調用到對象上的擴充方法,這時候,我們就需要藉助NVelocity所提供的IDuck這個介面來提供擴充方法的支援,如下代碼的:new HtmlExtensionDuck(context, this); 。完全代碼如下:

public class NVelocityView : IViewDataContainer, IView{    private ControllerContext _controllerContext;    private readonly Template _masterTemplate;    private readonly Template _viewTemplate;    public NVelocityView(ControllerContext controllerContext, string viewPath, string masterPath)        : this(controllerContext, NVelocityViewEngine.Default.GetTemplate(viewPath), NVelocityViewEngine.Default.GetTemplate(masterPath))    {    }    public NVelocityView(ControllerContext controllerContext, Template viewTemplate, Template masterTemplate)    {        _controllerContext = controllerContext;        _viewTemplate = viewTemplate;        _masterTemplate = masterTemplate;    }    public Template ViewTemplate    {        get { return _viewTemplate; }    }    public Template MasterTemplate    {        get { return _masterTemplate; }    }    private VelocityContext CreateContext(ViewContext context)    {        Hashtable entries = new Hashtable(StringComparer.InvariantCultureIgnoreCase);        if (context.ViewData != null)        {            foreach (var pair in context.ViewData)            {                entries[pair.Key] = pair.Value;            }        }        entries["viewdata"] = context.ViewData;        entries["tempdata"] = context.TempData;        entries["routedata"] = context.RouteData;        entries["controller"] = context.Controller;        entries["httpcontext"] = context.HttpContext;        entries["viewbag"] = context.ViewData;        CreateAndAddHelpers(entries, context);        return new VelocityContext(entries);    }    private void CreateAndAddHelpers(Hashtable entries, ViewContext context)    {        entries["html"] = entries["htmlhelper"] = new HtmlExtensionDuck(context, this);        entries["url"] = entries["urlhelper"] = new UrlHelper(context.RequestContext);        entries["ajax"] = entries["ajaxhelper"] = new AjaxHelper(context, this);    }    public void Render(ViewContext viewContext, TextWriter writer)    {        this.ViewData = viewContext.ViewData;        bool hasLayout = _masterTemplate != null;        VelocityContext context = CreateContext(viewContext);        if (hasLayout)        {            StringWriter sw = new StringWriter();            _viewTemplate.Merge(context, sw);            context.Put("childContent", sw.GetStringBuilder().ToString());            _masterTemplate.Merge(context, writer);        }        else        {            _viewTemplate.Merge(context, writer);        }    }    private ViewDataDictionary _viewData;    public ViewDataDictionary ViewData    {        get        {            if (_viewData == null)            {                return _controllerContext.Controller.ViewData;            }            return _viewData;        }        set        {            _viewData = value;        }    }}
ExtensionDuck

ExtensionDuck就是對IDuck介面的實現,它是我們需要提供擴充方法支援的Duck對象的基類。所有需要接供擴充方法的對象,通過繼承該方法可以簡化大部分的工作:

public class ExtensionDuck : IDuck{    private readonly object _instance;    private readonly Type _instanceType;    private readonly Type[] _extensionTypes;    private Introspector _introspector;    public ExtensionDuck(object instance)        : this(instance, Type.EmptyTypes)    {    }    public ExtensionDuck(object instance, params Type[] extentionTypes)    {        if(instance == null) throw new ArgumentNullException("instance");        _instance = instance;        _instanceType = _instance.GetType();        _extensionTypes = extentionTypes;    }    public Introspector Introspector    {        get        {            if(_introspector == null)            {                _introspector = RuntimeSingleton.Introspector;            }            return _introspector;        }        set { _introspector = value; }    }    public object GetInvoke(string propName)    {        throw new NotSupportedException();    }    public void SetInvoke(string propName, object value)    {        throw new NotSupportedException();    }    public object Invoke(string method, params object[] args)    {        if(string.IsNullOrEmpty(method)) return null;        MethodInfo methodInfo = Introspector.GetMethod(_instanceType, method, args);        if(methodInfo != null)        {            return methodInfo.Invoke(_instance, args);        }        object[] extensionArgs = new object[args.Length + 1];        extensionArgs[0] = _instance;        Array.Copy(args, 0, extensionArgs, 1, args.Length);        foreach(Type extensionType in _extensionTypes)        {            methodInfo = Introspector.GetMethod(extensionType, method, extensionArgs);            if(methodInfo != null)            {                return methodInfo.Invoke(null, extensionArgs);            }        }        return null;    }}

接下,我們就可以來實現一個HtmlExtensionDuck,指定一下,View中可以調用到HtmlHelper的哪些擴充方法,需要被開放的擴充方法可以在HTML_EXTENSION_TYPES中提供擴充方法所在的靜態類名:

public class HtmlExtensionDuck : ExtensionDuck{    public static readonly Type[] HTML_EXTENSION_TYPES =        new Type[]            {                typeof(DisplayExtensions),                typeof(DisplayTextExtensions),                typeof(EditorExtensions),                typeof(FormExtensions),                 typeof(InputExtensions),                 typeof(LabelExtensions),                typeof(LinkExtensions),                 typeof(MvcForm),                typeof(PartialExtensions),                typeof(RenderPartialExtensions),                typeof(SelectExtensions),                typeof(TextAreaExtensions),                typeof(ValidationExtensions)            };    public HtmlExtensionDuck(ViewContext viewContext, IViewDataContainer container)        : this(new HtmlHelper(viewContext, container))    {    }    public HtmlExtensionDuck(HtmlHelper htmlHelper)        : this(htmlHelper, HTML_EXTENSION_TYPES)    {    }    public HtmlExtensionDuck(HtmlHelper htmlHelper, params Type[] extentionTypes)        : base(htmlHelper, extentionTypes)    {    }}

完整的NVelocity for ASP.NET MVC的實現就是以上幾個類就可以完成。然後,我們就可以直接註冊到系統中來。我們不需要重寫任何Conroller,我們直接把ViewEngine註冊到MVC中來就可以被用到,也可以支援一個程式支援多種視圖引擎共存的和諧場面。簡單的註冊代碼放在Global.asax檔案中:

ViewEngines.Engines.Add(NVelocityViewEngine.Default);

詳細的使用樣本,下載附件查看詳細。

最後總結一下,NVelocity確實是一種簡單實用的模板引擎,特別是它的文法非常簡潔,而且API的可擴充性還是挺強的。Razor的文法的基本風格應該是有借鑒了它的文法風格。我現在雖然也很喜歡NVelocity,但如果不是特殊情況的特殊需要,在普通的ASP.NET MVC程式中,我還是會側向於使用Razor。它更自由一點,由於是直接的C#編譯支援,所以我們可以做任何的事情,這對於很多開發人員來說很重要。另一個不可忽視的就是它的IDE支援,特別是代碼提示確實是讓人相當的舒服。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.