欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

利用Razor在ASP.NET MVC中的实现,自定义视图引擎框架(2)

程序员文章站 2022-06-10 13:12:53
...

ASP.NET MVC3开始使用Razor作为其视图引擎,取代了原来ASP.NET Web Form引擎。笔者最近研究了一下MVC3对Razor的实现,从中找到一个切入点,能够让我们自定义基于Razor语法的视图解析引擎。在项目里面可以用于诸如邮件模板定制等方面。目前,只是一个demo版本,还在进一步完善中。CodePlex : http://codeof.codeplex.com/SourceControl/list/changesets 其中的RazorEx

目前支持的功能:

1.支持Razor语法(基本的@语法)的模板文件解析
2.支持Layout / Renderbody语法
3.支持类似asp.net 动态编译机制,在程序运行期间,如果模板文件变了,无需重新编译
4.支持名字空间引用配置
5.支持复杂的程序集引用关系

利用Razor在ASP.NET MVC中的实现,自定义视图引擎框架(1)中,介绍了如何利用微软实现的System.Web.Razor来解析基于Razor语法的模板,最后得到一个编译单元或者源码。本文介绍如何在代码中对编译单元或者源码进行动态编译,并执行。

应该没有比动态编译更灵活的了,它允许我们动态的创建程序代码并编译执行。尽管它灵活,但是实现复杂,并且效率不高,不到万不得已不要考虑。而在这个case中却不得不用这种方式,因为模板是用户创建的,我们永远不可能预知:

利用Razor在ASP.NET MVC中的实现,自定义视图引擎框架(2)

在.net中,System.CodeDom.Compiler.CodeDomProvider提供了将一个或多个源程序或编译单元编译成程序集的方法。在.net4.0中Microsoft.CSharp.CSharpCodeProvider继承了上面这个类,并给予了实现。有了CSharpCodeProvider,编译一个动态的程序集十分容易:

CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters c_options = new CompilerParameters();
            c_options.IncludeDebugInformation = false;
            c_options.GenerateExecutable = false;
            c_options.GenerateInMemory = true;
            c_options.ReferencedAssemblies.Add( "System.dll" );
            CompilerResults results = provider.CompileAssemblyFromSource(c_options, code);

上面的代码段,实例化了一个CSharpCodeProvider以及一个CompilerParameters。前者用于编译,后者用于指定编译时的一些选项,如上面的代码的设置。最后调用CompileAssemblyFromSource,传入编译选项和源代码文本即可。另外CSharpCodeProviderCompileAssemblyFromDom重载,可以接受编译选项对象和

CodeCompileUnit对象作为参数。在上一篇中,我们知道RazorTemplateEngine.GenerateCode方法返回的刚好是包含了CodeCompileUnit的对象,所以我们将使用CompileAssemblyFromDom方法

在返回值CompilerResults.CompiledAssembly中,我们可以访问到编译结果的Assembly对象,再结合反射即可执行编译代码。另外,在编译过程中,如果编译失败将抛出异常。

在引擎的开发过程中,除了上一篇和上述需要知道的基本内容外,分别有以下问题需要解决:

1、动态编译时需要知道引用哪些dll,否则将无法编译成功。比如在模板里面我引用了一个复杂对象,这个对象定义显然不在引擎的程序集中,可能是用户自己的程序集,或是用户程序集引用的程序集。这就带来了一个问题,在编译时我们如何知道要引用哪些程序集?我用了一个比较笨的方案:从GetCallingAssembly开始,把相互依赖的程序集遍历一遍,并且全部在编译时引用。代码中的AssemblyReferenceResolver就实现了这个功能;

2、如上面的问题,编译的时候,源码需要有正确的命名空间的引用,否则即使引用的程序集,还是不能编译成功。为此,模仿mvc的实现,设计了一个configuration,添加下面这样的配置文件即可:

<configuration>
  <configSections>
    <section name="RazorTemplateEngineImportNamespace" 
             type="RazorTemplateEngine.ImportNamespaceResolver,RazorTemplateEngine"/>
  </configSections>
  <RazorTemplateEngineImportNamespace>
    <add namespace="ModelTest"/>
    <add namespace="System.Collections.Generic"/>
  </RazorTemplateEngineImportNamespace>
</configuration>

3、一个模板对应一个类,也就对应一个程序集,如果反复解析模板会反复编译,这样会很大程度上影响效率。解决方案是使用缓存:将编译过的dll和模板文件存成字典,如果已经编译过了并且模板文件的最后更新时间不晚于dll的创建时间,则直接返回之前编译的程序集;否则就进行编译。代码中的DynamicAssemblyCache类就实现这个功能;

4、如何实现模板嵌套。在基类__TemplatePage中加入下面属性和方法:

        private string _layout;

        public virtual string Layout {
            get { return _layout; }
            set {
                _layout = value;
            }
        }

        public string ChildBody { get; set; }

        public virtual string RenderBody()
        {
            if(ChildBody != null)
                return ChildBody;
            return string.Empty;
        }

这样类似Layout=””  @RenderBody的语法就可以通过编译。配合递归的Execute即可实现。

 

 

项目现已实现基本的功能,我打算过一段试用期过后Release一个版本。源码在上面的CodePlex上,代码不多,还有待重构,有兴趣的同仁可以和我讨论,希望能实现一个健壮的引擎。

转载于:https://www.cnblogs.com/P_Chou/archive/2011/08/30/customize-razor-template-2.html