在非MVC项目中使用Razor引擎生成html
Razor模板引擎是ASP.NET MVC项目中的核心模块,用于模板文件渲染工作。它完美接管了传统项目中的Web Forms的职能,并且在新的跨平台框架.NET Core中延续。在官方提供的项目模板中,它都是配合MVC作为Views层工作的。那么,能不能把它单独提取出来为其它场景服务呢?
答案当然是肯定的!
需求背景
前段时间接手了一个旧项目改造,由于预算有限,老板并不打算另起炉灶开新项目。针对旧项目做了些评估后,有一项优化涉及到模板引擎改的改进,原项目使用的是NVelocity引擎,这是一个非常古老的asp.net模板引擎,功能改进中受到的限制有两个:
一个是它不支持向模板中传递实例化类对象(可以加静态类,但加动态类后调用是失败的)。
另一个是在方法调用时不支持可选参数(提示的错误是匹配不到对应的方法)。
因为本次调整涉及到网址格式优化,如果这两个特性支持不到,修改时会非常费时费力,并且可维护性很差(旧的网址格式是直接在模板中拼装的)。要解决这个问题,首先想到的是看下这个模板引擎有没有改进过的新版,查了下资料,官方版本已经没更新了。github上有一个项目,最后更新也是停留在3年前,看记录,也没改到什么实质性的内容。把项目源码下载来大概看了下,结构比较复杂,改造起来也没太大把握,那么下一步方案,就是看看有没有替代品了。
解决方案
首先想到的也是razor,但是没有单独使用过它,之前都是在mvc项目中直接用的。于是先在网上查了些相关的文章,当时找到过两篇,有一篇还是国外的,但实现代码看起来比较复杂,总觉得有些不必要的内容。不过得到了一个关键信息,razor模板引擎有一个独立项目,名叫 RazorEngine ,于是找到官方文档 https://antaris.github.io/RazorEngine/ 里面有不少示例代码,跟着摸索下来,要点功能一一测试通过,核心实现上其实很简单。
下面帖出本人实现代码和调用方式。由于是从旧项目改造,这个通用类的方法延用了原接口。
思路有几条
创建模板引擎相关的几个静态属性,初始化时根据视图目录创建公用的模板引擎服务
创建一个动态模型,另外创建一个Dictionary 关联到这个动态模型上,以便动态绑定键值
// ITemplateDriver 是原模板引擎提取出来的接口文件 public class RazorTemplate : ITemplateDriver { protected static ITemplateServiceConfiguration config = null; protected static bool isEngineCreated = false; protected static IRazorEngineService service; private dynamic model; private IDictionary<String, Object> modelDict; public RazorTemplate(string viewPath, bool isDebug = false) { if (!isEngineCreated) { viewPath = viewPath.TrimEnd('/', '\\') + "\\"; ITemplateManager manager = null; if (isDebug) { manager = new WatchingResolvePathTemplateManager(new List<string> { viewPath }, new InvalidatingCachingProvider()); } else { manager = new ResolvePathTemplateManager(new List<string> { viewPath }); } config = new FluentTemplateServiceConfiguration( c => c.WithEncoding(RazorEngine.Encoding.Html) .IncludeNamespaces("MyCMS.Common") // 这里引需要在模板中调用的模块 .ManageUsing(manager) .UseDefaultCompilerServiceFactory()); service = RazorEngineService.Create(config); isEngineCreated = true; } model = new ExpandoObject(); modelDict = model; } public void Put(string key, object value) { if (modelDict.ContainsKey(key)) { modelDict[key] = value; } else { modelDict.Add(key, value); } } /// <summary> /// 构建模板 /// </summary> /// <param name="templateFile"></param> /// <param name="isPartial"></param> /// <param name="masterName"></param> /// <returns></returns> public string BuildString(string templateFile) { var sw = new StringWriter(); DateTime startTime = DateTime.Now; service.RunCompile(config.TemplateManager.GetKey(templateFile,ResolveType.Global,null), sw, null, model); ILog log = log4net.LogManager.GetLogger("log"); if (log.IsDebugEnabled) { log.Debug("模板("+ templateFile + ")编译耗时 " + (DateTime.Now - startTime)); } modelDict.Clear(); return sw.ToString(); } /// <summary> /// 输出到Response /// </summary> /// <param name="templateFile"></param> public void Display(string templateFile) { //从文件中读取模板 string html = BuildString(templateFile); //输出 HttpContext.Current.Response.Clear(); HttpContext.Current.Response.Write(html); HttpContext.Current.Response.Flush(); HttpContext.Current.Response.End(); } /// <summary> /// 输出到文件 /// </summary> /// <param name="templateFile"></param> /// <param name="filename"></param> /// <param name="htmlpath"></param> /// <returns></returns> public string saveFile(string templateFile, string filename, string htmlpath) { //从文件中读取模板 string html = BuildString(templateFile); //输出到文件 FileOperate.FolderCreate(htmlpath); FileOperate.WriteFile(htmlpath + filename, html, false); return html; } }
调用方法如下:
var razor = new RazorTemplate(Server.MapPath("~/views/")); razor.Put("title","网站标题"); razor.Put("dataList",new List<Article>(){ new Article(){ ... }, new Article(){ ... }, new Article(){ ... }, }); // 直接输出 razor.Display("article_list"); // 保存html到文件 razor.saveFile("article_list", "article_list_1.html","~/cache/");
模板文件按照mvc中的写法一样,可以使用Layout,Include等
本机测试首次编译模板文件耗时1s左右 ,后续渲染都是在毫秒级的,这个跟模板渲染的内容有关