在非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左右 ,后续渲染都是在毫秒级的,这个跟模板渲染的内容有关