DIY RazorEngine 的程序集生成方式
最近遇到一个项目,要使用RazorEngine做模板引擎,然后完成简易的CMS功能,以减轻重复的CDRU操作,同时复用管理后台。没错,使用的正是GIT HUB上的开源项目:https://github.com/Antaris/RazorEngine 。模板编译过程非常耗时,所以Razor提供了Compile和Parse的带key参数的重载,以实现从缓存中加载编译后的模板的功能。不过这里还是有一个问题,对于web项目而言,应用程序池会周期性的回收(即使设置了不自动回收,不知为何)。所以,仍然会存在重新编译而导致页面长时间挂起的问题。或许可以提供一个进度条,告诉客户这是一个高大上的东西,需要热身....不过应该有更好的办法,就是从RazorEngine本身着手。
RazorEngine接收到模板内容的时候,会调用编辑器将其编译成一个程序集,加载到内存中,同时返回编译好的对应的模板的类型对象(Type对象)。然后调用对象的构造函数,生成对象实例,最后“执行”模板。BUT!为什么是加载到内存呢,如果是将程序集保存在磁盘上,那么下次再进行读取的话,这个性能绝对不是同一个档次的。所以,考虑之后,决定用以下的思路解决问题:
获取到模板之后(字符串),会计算其MD5值,并作为生成的模板类型的Class(有坑),同时将其作为程序集的名称。每当客户机代码请求编译模板的时候,就去指定的目录查找是否有这么一个程序集,如果有,直接加载这个程序集,并返回类型信息(因为程序集中只有一个类型,所以非常方便)。RazorEngine本身采用了可扩展的设计。扩展口就在Razor.SetTemplateService()。只需要实现CompilerServiceBase抽象类,就能自定义模板引擎的逻辑。只是...代码编译部分处于调用的较底层,如果全部采用全新的实现的话...工作量不少,而且容易存在BUG。SO...原样应用RazorEngine.dll并进行扩展这么完美的事情暂时还办不到。我采用的做法是,修改RazorEngine的源码,但是不修改已经定义的类型,只在相应的名称空间下面提供新的类型来满足自己的需求(因为其中不少需要的类型是internal的)。
参考TemplateService的源码,实现了一个ReloadableTemplateService,重新实现了CreateTemplateType方法:
[Pure]
public virtual Type CreateTemplateType(string razorTemplate, Type modelType)
{
//重要:类名不能以数字开头
var key = GetTemplateMd5(razorTemplate);
string className = "C" + key;
var assemblyPath = AssemblyDirecotry.TrimEnd(new[] { '/', '\\' }) + "\\" + key + ".dll"; if (File.Exists(assemblyPath))
{
try
{
//var assembly = Assembly.Load(File.ReadAllBytes(assemblyPath));
var assembly = Assembly.LoadFile(assemblyPath);
return assembly.GetTypes()[];
}
catch (Exception ex)
{
Debug.WriteLine("an error ocured and assembly has been deleted");
File.Delete(assemblyPath);
}
} var context = new TypeContext
{
ModelType = (modelType == null) ? typeof(object) : modelType,
TemplateContent = razorTemplate,
TemplateType = (_config.BaseTemplateType) ?? typeof(TemplateBase<>),
}; foreach (string ns in _config.Namespaces)
context.Namespaces.Add(ns); //csharp only
var service = new CSharpFileDirectCompilerService(); service.Debug = _config.Debug;
service.CodeInspectors = _config.CodeInspectors ?? Enumerable.Empty<ICodeInspector>(); var result = service.CompileType(context, className, assemblyPath);
_assemblies.Add(result.Item2); return result.Item1;
}
需要注意是,C#的类型名称不能以数字开头...这个问题我查了一下午,主要是压根没有想到这一点。生成程序集的方法,被放置在CSharpFileDirectCompilerService中,由于这个类型的局限性很强(我只是想快速解决问题),所以没有实现基类要求的方法(我把它废了,虽然这样很傻逼):
[Pure]
private Tuple<CompilerResults, string> Compile(TypeContext context, string className, string assemblyPath)
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name); var compileUnit = GetCodeCompileUnit(className, context.TemplateContent, context.Namespaces,
context.TemplateType, context.ModelType); var @params = new CompilerParameters
{
GenerateInMemory = false,
OutputAssembly = assemblyPath,
GenerateExecutable = false,
IncludeDebugInformation = false,
CompilerOptions = "/target:library /optimize /define:RAZORENGINE"
}; var assemblies = CompilerServicesUtility
.GetLoadedAssemblies()
.Where(a => !a.IsDynamic && File.Exists(a.Location))
.GroupBy(a => a.GetName().Name)
.Select(grp => grp.First(y => y.GetName().Version == grp.Max(x => x.GetName().Version)))
// only select distinct assemblies based on FullName to avoid loading duplicate assemblies
.Select(a => a.Location); var includeAssemblies = (IncludeAssemblies() ?? Enumerable.Empty<string>());
assemblies = assemblies.Concat(includeAssemblies)
.Where(a => !string.IsNullOrWhiteSpace(a))
.Distinct(StringComparer.InvariantCultureIgnoreCase); @params.ReferencedAssemblies.AddRange(assemblies.ToArray()); string sourceCode = null;
if (Debug)
{
var builder = new StringBuilder();
using (var writer = new StringWriter(builder, CultureInfo.InvariantCulture))
{
_codeDomProvider.GenerateCodeFromCompileUnit(compileUnit, writer, new CodeGeneratorOptions());
sourceCode = builder.ToString();
}
} return Tuple.Create(_codeDomProvider.CompileAssemblyFromDom(@params, compileUnit), sourceCode);
} /// <summary>
/// Compiles the type defined in the specified type context.
/// </summary>
/// <param name="context">The type context which defines the type to compile.</param>
/// <returns>The compiled type.</returns>
[Pure, SecurityCritical,Obsolete("该方法无法兼容其父类,功能已经在其重载中提供")]
public override Tuple<Type, Assembly> CompileType(TypeContext context)
{
throw new NotImplementedException("该方法无法兼容其父类,功能已经在其重载中提供");
} /// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <param name="className"></param>
/// <param name="assemblyDirectory"></param>
/// <returns></returns>
/// <exception cref="NullReferenceException"></exception>
/// <exception cref="TemplateCompilationException"></exception>
public Tuple<Type, Assembly> CompileType(TypeContext context, string className, string assemblyDirectory)
{
if (context == null)
throw new NullReferenceException("context");
var result = Compile(context, className, assemblyDirectory);
var compileResult = result.Item1; if (compileResult.Errors != null && compileResult.Errors.HasErrors)
throw new TemplateCompilationException(compileResult.Errors, result.Item2, context.TemplateContent); return Tuple.Create(
compileResult.CompiledAssembly.GetType("CompiledRazorTemplates.Dynamic." + className),
compileResult.CompiledAssembly);
}
然后就成了,调用方式是:
Razor.SetTemplateService(new ReloadableTemplateService()
{
AssemblyDirecotry = "d:\\temple"
});
稍微测了下性能,这里定性描述下:编译模板的时候,耗时会根据模板的大小和复杂度,一两秒或者更多。而加载一个程序集的话,尤其是像这种一个类型的小程序集,总是毫秒级的。
真是个愉快的周末。
DIY RazorEngine 的程序集生成方式的更多相关文章
- Razor - 模板引擎 / 代码生成 - RazorEngine
目录 Brief Authors Official Website RazorEngine 的原理 - 官方解释 安装记录 Supported Syntax (默认实现支持的语法) 测试记录 - ca ...
- 从invoke简单理解反射
前言 程序集 : 程序集是.NET应用程序的基本单位,包含了程序的资源.类型元数据和MSIL代码.根据程序集生成方式的不同,可分为静态程序集和动态程序集.程序集又可分为单文件程序集和多文件程序集, ...
- C#开发问题汇总
问题1:HTTP 错误 500.21 - Internal Server Error处理程序“NickLeeCallbackHandler”在其模块列表中有一个错误模块“ManagedPipeline ...
- “RazorEngine.Templating.TemplateCompilationException”类型的异常在 RazorEngine.NET4.0.dll 中发生,但未在用户代码中进行处理
错误信息: "RazorEngine.Templating.TemplateCompilationException"类型的异常在 RazorEngine.NET4.0.dll 中 ...
- 第四篇 基于.net搭建热插拔式web框架(RazorEngine实现)
在开头也是先给大家道个歉,由于最近准备婚事导致这篇文章耽误了许久,同时也谢谢老婆大人对我的支持. 回顾上篇文章,我们重造了一个controller,这个controller中用到了视图引擎,我们的视图 ...
- 解决T4模板的程序集引用的五种方案
在众多.NET应用下的代码生成方案中,比如CodeDOM,BuildProvider, 我觉得T4是最好的一种.关于T4的基本概念和模板结果,可以参考我的文章<基于T4的代码生成方式>.如 ...
- Razor模板引擎 (RazorEngine)
Razor模板引擎不仅在ASP.NET MVC中内置了Razor模板引擎,还有一个开源的RazorEngine, 这样以来我们可以在非ASP.NET MVC项目中使用Razor引擎,甚至在控制台,Wi ...
- MVC的验证(模型注解和非侵入式脚本的结合使用) .Net中初探Redis .net通过代码发送邮件 Log4net (Log for .net) 使用GDI技术创建ASP.NET验证码 Razor模板引擎 (RazorEngine) .Net程序员应该掌握的正则表达式
MVC的验证(模型注解和非侵入式脚本的结合使用) @HtmlHrlper方式创建的标签,会自动生成一些属性,其中一些属性就是关于验证 如图示例: 模型注解 通过模型注解后,MVC的验证,包括前台客 ...
- [转]从数据到代码——基于T4的代码生成方式
本文转自:http://www.cnblogs.com/artech/archive/2010/10/23/1859529.html 在之前写一篇文章<从数据到代码>(上篇.下篇)中,我通 ...
随机推荐
- 命令行界面下用户和组管理之userdel的使用
userdel - delete a user account and related files 在Linux中,userdel命令的功能是删除系统中的用户及相关的文件 语法 userdel ...
- mapping 详解2(field datatypes)
基本类型 1. 字符串 字符串类型被分为两种情况:full-text 和 keywords. full-text 表示字段内容会被分析,而 keywords 表示字段值只能作为一个精确值查询. 参数: ...
- C#如何加载程序运行目录外的程序集
我们的应用程序部署的时候,目录结构一般不会只有运行程序的目录这一个,我们可能在运行目录下建子目录,也可能使用System32目录,也可能使用其它第三方的程序集..Net程序集 首先会在GAC中搜索相应 ...
- Matlab plotyy函数的使用及问题总结
MATLAB函数,用来绘制双纵坐标图. 调用格式: 1.plotyy(X1,Y1,X2,Y2):以左.右不同纵轴绘制X1-Y1.X2-Y2两条曲线. 2.plotyy(X1,Y1,X2,Y2,FUN1 ...
- fail-fast机制
在JDK的Collection中我们时常会看到类似于这样的话: 例如,ArrayList: 注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证.快速失 ...
- N_F1_APPROVE
package nc.bs.pub.action; import java.util.ArrayList; import java.util.Hashtable; import java.util.L ...
- ORCAL
select name from v$database;--查询当前数据库名: select instance_name from v$instance;--查询当前数据库实例名 select def ...
- Spring AOP (Spring 3.x 企业应用开发实战读书笔记第六章)
从面相对象编程到面相切面编程,是一种代码组织方式的进化. 每一代的代码组织方式,其实是为了解决当时面对的问题.比如写编译器和写操作系统的时候的年代当然要pop,比如写界面的时候当然要oop,因为界面这 ...
- VBA实现随意输入组合码,查询唯一标识码
记录背景: 需要在excel中查询出组合码,对应的唯一标识码. 举例 组合码:4+5+6+9+1*2 标识码:A1 界面随意输入组合码:1*2+4+5+6+9 输出标识码:A1 VBA实现: P ...
- C--指针函数,static
(*p)是固定写法,代表指针的变量P将来是指向函数 void (*p)(); p=test;//指针变量P指向了test函数 函数名test代表函数地址 //同等调用test()函数 (*p)(); ...