zoukankan      html  css  js  c++  java
  • DIY RazorEngine 的程序集生成方式

    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()[0];
                    }
                    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"
                });

    稍微测了下性能,这里定性描述下:编译模板的时候,耗时会根据模板的大小和复杂度,一两秒或者更多。而加载一个程序集的话,尤其是像这种一个类型的小程序集,总是毫秒级的。
    真是个愉快的周末。

     
     
    分类: C#
  • 相关阅读:
    面试总结进程、线程与多线程
    精妙算法收集一道有趣的腾讯笔试加分题
    反汇编分析寄存器状态
    远程桌面快捷键
    Js中 关于top、clientTop、scrollTop、offsetTop的用法
    JavaScript获取CSS属性
    oracle开启日志归档 (成功)
    eclipse插件开发帮助文档地址
    alter system修改oracle参数
    oracle分析统计表
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3676699.html
Copyright © 2011-2022 走看看