Razor - 模板引擎 / 代码生成 - RazorEngine

Posted TCSJ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Razor - 模板引擎 / 代码生成 - RazorEngine相关的知识,希望对你有一定的参考价值。

Brief

Open source templating engine based on Microsoft\'s Razor parsing engine.

Authors

  • Matthew Abbott
  • Ben Dornis
  • Matthias Dittrich

Official Website

GitHub

CodePlex - obsolete

NuGet

Install-Package RazorEngine
Install-Package RazorEngine -Version 3.6.4
https://www.nuget.org/packages/RazorEngine/ - latest: RazorEngine 3.6.4, Friday, March 27 2015

Stack Overflow

RazorEngine 的原理 - 官方解释

There is often a confusion about where Razor sits in this set of technologies. Essentially Razor is the parsing framework that does the work to take your text template and convert it into a compilable class. In terms of MVC and WebPages, they both utilise this parsing engine to convert text templates (view/page files) into executable classes (views/pages). Often we are asked questions such as "Where is @html, @Url", etc. These are not features provided by Razor itself, but implementation details of the MVC and WebPages frameworks.

RazorEngine is another consumer framework of the Razor parser. We wrap up the instantiation of the Razor parser and provide a common framework for using runtime template processing.

-- Razor vs. MVC vs. WebPages vs. RazorEngine

安装记录

Installed into Net451Console via NuGet at 2016-07-31:

  1. ------- 正在安装...RazorEngine 3.9.0 -------
  2. 正在尝试解析依赖项“Microsoft.AspNet.Razor (≥ 3.0.0)”。
  3. 正在安装“Microsoft.AspNet.Razor 3.0.0”。
  4. 已将文件“System.Web.Razor.dll”添加到文件夹“Microsoft.AspNet.Razor.3.0.0\\lib\\net45”。
  5. 已将文件“System.Web.Razor.xml”添加到文件夹“Microsoft.AspNet.Razor.3.0.0\\lib\\net45”。
  6. 已将文件“Microsoft.AspNet.Razor.3.0.0.nuspec”添加到文件夹“Microsoft.AspNet.Razor.3.0.0”。
  7. 已将文件“Microsoft.AspNet.Razor.3.0.0.nupkg”添加到文件夹“Microsoft.AspNet.Razor.3.0.0”。
  8. 已成功安装“Microsoft.AspNet.Razor 3.0.0”。
  9. 正在安装“RazorEngine 3.9.0”。
  10. 已将文件“RazorEngine.dll”添加到文件夹“RazorEngine.3.9.0\\lib\\net40”。
  11. 已将文件“RazorEngine.xml”添加到文件夹“RazorEngine.3.9.0\\lib\\net40”。
  12. 已将文件“RazorEngine.dll”添加到文件夹“RazorEngine.3.9.0\\lib\\net45”。
  13. 已将文件“RazorEngine.xml”添加到文件夹“RazorEngine.3.9.0\\lib\\net45”。
  14. 已将文件“LICENSE.md”添加到文件夹“RazorEngine.3.9.0”。
  15. 已将文件“RazorEngine.3.9.0.nuspec”添加到文件夹“RazorEngine.3.9.0”。
  16. 已将文件“RazorEngine.3.9.0.nupkg”添加到文件夹“RazorEngine.3.9.0”。
  17. 已成功安装“RazorEngine 3.9.0”。
  18. 正在将“Microsoft.AspNet.Razor 3.0.0”添加到 Console
  19. 已将引用“System.Web.Razor”添加到项目“Console
  20. 已添加文件“packages.config”。
  21. 已将文件“packages.config”添加到项目“Console
  22. 已成功将“Microsoft.AspNet.Razor 3.0.0”添加到 Console
  23. 正在将“RazorEngine 3.9.0”添加到 Console
  24. 已将引用“RazorEngine”添加到项目“Console
  25. 已添加文件“packages.config”。
  26. 已成功将“RazorEngine 3.9.0”添加到 Console
  27. ==============================

总结: 共安装了两个 DLL: System.Web.Razor.dllRazorEngine.dll

Supported Syntax (默认实现支持的语法)

You can access several things when you use the default TemplateBase<> implementation:
@using Custom.Namespace (see also the quick intro and assembly resolvers for custom references)
@model ModelType
@inherits HtmlSupportTemplateBase<ModelType> (see below)
* Set a layout (and @RenderBody() within the layout template): @{ Layout = "layout.cshtml"; }
@Include("templateName", model = null, modelType = null) to include another template.
* Accessing the ViewBag: <h1>@ViewBag.Title</h1>
* Sections (@DefineSection@RenderSection and @IsSectionDefined)

-- http://antaris.github.io/RazorEngine/TemplateBasics.html#Supported-syntax

测试记录 - can\'t cleanup temp files

2015-10-19 Tony 在 .NET4.51 Console Application 中单步执行以下代码时弹出命令行窗口显示以下信息:

代码:

  1. using RazorEngine;
  2. using RazorEngine.Templating;
  3. namespace Net451ConsoleApplication
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. // using RazorEngine.Templating; // Dont forget to include this.
  10. string template = "Hello @Model.Name, welcome to RazorEngine!";
  11. var result = Engine.Razor.RunCompile(template, "templateKey", null, new { Name = "World" });
  12. }
  13. }
  14. }

信息:

  1. RazorEngine: We can\'t cleanup temp files if you use RazorEngine on the default Appdomain.
  2. Create a new AppDomain and use RazorEngine from there.
  3. Read the quickstart or https://github.com/Antaris/RazorEngine/issues/244 for details!
  4. You can ignore this and all following \'Please clean ... manually\' messages if you are using DisableTempFileLocking, which is not recommended.
  5. Please clean \'C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_********.***\' manually!

每次单步执行 RazorEngine.Engine.Razor.RunCompile() 后,都会生成 1 个目录及该目录中的 6 个文件,该目录及其中的全部文件的名称都是随机的,例如:

  1. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_pv3sxecv.dyv\\CompiledRazorTemplates.Dynamic.RazorEngine_290440367cc04e8c9d06a99a7a805d08.dll"
  2. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_pv3sxecv.dyv\\filbbops.0.cs"
  3. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_pv3sxecv.dyv\\filbbops.cmdline"
  4. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_pv3sxecv.dyv\\filbbops.err"
  5. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_pv3sxecv.dyv\\filbbops.out"
  6. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_pv3sxecv.dyv\\filbbops.tmp"

单步执行完毕后 (Tony 理解为应用程序域已终止并释放),其它 5 个文件已自动移除,但 .dll 文件及该目录会残留。以下为 4 次单步执行后的残留:

  1. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_bmze52rx.ovb\\CompiledRazorTemplates.Dynamic.RazorEngine_a291215db8fd497cae3e4a2bfcabe7b2.dll"
  2. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_tkidehx4.qay\\CompiledRazorTemplates.Dynamic.RazorEngine_bd299256d8b94f64ba943c5a09014ed6.dll"
  3. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_a1anyws2.vvo\\CompiledRazorTemplates.Dynamic.RazorEngine_f890b330f6104de19631e25ea528a6e8.dll"
  4. "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_pv3sxecv.dyv\\CompiledRazorTemplates.Dynamic.RazorEngine_290440367cc04e8c9d06a99a7a805d08.dll"

官网提供了上述问题的解决方案,2017-03-15 编写以下代码对该方案进行了测试,结论: 该方案在控制台程序中有效:

  1. //Program.cs
  2. using RazorEngine;
  3. using RazorEngine.Templating;
  4. namespace Net451Console
  5. {
  6. class Program
  7. {
  8. //static void Main(string[] args)
  9. static int Main(string[] args)
  10. {
  11. bool isDefaultAppDomain = NewDomain.Switch();
  12. if(isDefaultAppDomain) {
  13. return 0;
  14. }
  15. #region test code:
  16. string template = "Hello @Model.Name, welcome to RazorEngine!";
  17. var result = Engine.Razor.RunCompile(template, "templateKey", null, new { Name = "World" });
  18. //执行上一行后创建了 "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_tbr5ujmj.fju" 目录及其中的 6 个文件。
  19. var isTrue = result == "Hello World, welcome to RazorEngine!";
  20. #endregion test code.
  21. return 123;
  22. }
  23. //退出 Main() 后 "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_tbr5ujmj.fju" 目录被删除。
  24. }
  25. }
  1. //NewDomain.cs
  2. using System;
  3. using System.Reflection;
  4. using System.Security;
  5. using System.Security.Permissions;
  6. using System.Security.Policy;
  7. namespace Net451Console
  8. {
  9. class NewDomain
  10. {
  11. /// <summary>
  12. /// 如果 应用程序域 非 进程的默认应用程序域,则 不作任何处理、直接返回 false;
  13. /// 如果 应用程序域 是 进程的默认应用程序域,则 创建新的应用程序域、并在该域中执行当前程序集、接着卸载该域、并返回 true。
  14. /// </summary>
  15. public static bool Switch()
  16. {
  17. bool isDefaultAppDomain = AppDomain.CurrentDomain.IsDefaultAppDomain();
  18. if(!isDefaultAppDomain) {
  19. return false;
  20. }
  21. // RazorEngine cannot clean up from the default appdomain...
  22. Console.WriteLine("Switching to second AppDomain, for RazorEngine...");
  23. //2017-03-15 已测试证明以下两行代码无用:
  24. //AppDomainSetup adSetup = new AppDomainSetup();
  25. //adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
  26. //测试记录: 至此 adSetup.ApplicationBase 的值为 "D:\\Test\\Net451Console\\branches\\RazorEngine\\Net451Console\\bin\\Debug\\"。
  27. // You only need to add strongnames when your appdomain is not a full trust environment.
  28. AppDomain domain = AppDomain.CreateDomain(
  29. "MyMainDomain",
  30. null,
  31. AppDomain.CurrentDomain.SetupInformation,
  32. new PermissionSet(PermissionState.Unrestricted),
  33. new StrongName[0]
  34. );
  35. string location = Assembly.GetExecutingAssembly().Location;
  36. //测试记录: 至此 location 的值为 "D:\\Test\\Net451Console\\branches\\RazorEngine\\Net451Console\\bin\\Debug\\Net451Console.exe"。
  37. int exitCode = domain.ExecuteAssembly(location);
  38. //执行上一行的 domain.ExecuteAssembly() 导致重新执行 Main(),届时 isDefaultAppDomain 为 false,将跳过本方法主体转而执行 Mian() 中的剩余代码,Main() 的返回值将成为上一行的返回值并赋给 exitCode,接着执行下 2 行代码,最后退出本方法。
  39. // Note that you need to Unload the domain to trigger cleanup.
  40. // RazorEngine will cleanup.
  41. AppDomain.Unload(domain);
  42. //执行上一行后 "C:\\Users\\User0\\AppData\\Local\\Temp\\RazorEngine_tbr5ujmj.fju" 目录中的 6 个文件仅剩下 1 个 .dll 文件。
  43. return true;
  44. }
  45. }
  46. }

测试记录 - 以上方案仅在控制台程序中有效、在 ASP.NET MVC 中无效

  1. 在 ASP.NET MVC 中 AppDomain.CurrentDomain.IsDefaultAppDomain() 总是返回 false
  2. 执行 Assembly.GetExecutingAssembly().Location 返回 C:\\Users\\User0\\AppData\\Local\\Temp\\Temporary ASP.NET Files\\root\\b54c3416\\14425600\\assembly\\dl3\\57a31749\\d172b5e9_d29fd201\\Net451MvcNoAuth.dll,接着执行下一行 domain.ExecuteAssembly(location) 报错 异常详细信息: System.MissingMethodException: 未在程序集“Net451MvcNoAuth, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”中找到入口点。

测试记录 - Quick Start

以下测试代码整理自 http://antaris.github.io/RazorEngine/index.html#Quickstart:

  1. using RazorEngine;
  2. using RazorEngine.Templating;
  3. namespace Net451Console
  4. {
  5. class Program
  6. {
  7. //static void Main(string[] args)
  8. static int Main(string[] args)
  9. {
  10. bool isDefaultAppDomain = NewDomain.Switch();
  11. if(isDefaultAppDomain) {
  12. return 0;
  13. }
  14. #region test code:
  15. //All you need to do is use the static Engine class (the Engine.Razor instance) in the \'RazorEngine\' namespace:
  16. string template = "Hello @Model.Name, welcome to RazorEngine!";
  17. string key = "templateKey";
  18. var result0 = Engine.Razor.RunCompile(template, key, null, new { Name = "World" });
  19. //The "templateKey" must be unique and after running the above example you can re-run the cached template with this key.
  20. var result1 = Engine.Razor.Run(key, null, new { Name = "Max" });
  21. //The null parameter is the modelType and null in this case means we use dynamic as the type of the model. You can use a static model as well by providing a type object.
  22. var result2 = Engine.Razor.RunCompile(key, typeof(Person), new Person { Name = "Max" });
  23. //Note that we now re-compile the model with a different type. When you do not run the same template a lot of times (like several 1000 times), compiling uses the most time. So the benefit you get from a static type will most likely not compensate the additional compile time. Therefore you should either stick to one type for a template (best of both worlds) or just use (the slower) dynamic (null). You can specify the modelType of a template with the @model directive. When you do this the modelType parameter is ignored, but you should use the same type instance (or null) on every call to prevent unnecessary re-compilations because of type mismatches in the caching layer.
  24. #endregion test code.
  25. return 123;
  26. }
  27. }
  28. public class Person
  29. {
  30. public string Name { get; set; }
  31. }
  32. }

测试记录 - Configuration

以下测试代码整理自 http://antaris.github.io/RazorEngine/index.html#Configuration:

  1. using RazorEngine;
  2. using RazorEngine.Configuration;
  3. using RazorEngine.Templating;
  4. using RazorEngine.Text;
  5. namespace Net451Console
  6. {
  7. class Program
  8. {
  9. //static void Main(string[] args)
  10. static int Main(string[] args)
  11. {
  12. bool isDefaultAppDomain = NewDomain.Switch();
  13. if(isDefaultAppDomain) {
  14. return 0;
  15. }
  16. #region test code:
  17. //You can configure RazorEngine with the TemplateServiceConfiguration class.
  18. var config = new TemplateServiceConfiguration();
  19. // ... configure your instance
  20. //General Configuration
  21. //By default RazorEngine is configured to encode using Html. This supports the majority of users but with some configuration changes you can also set it to encode using Raw format which is better suited for templates that generate things like javascript, php, C# and others.
  22. config.Language = Language.CSharp; // C# as template language.
  23. config.EncodedStringFactory = new RawStringFactory(); // Raw string encoding.
  24. //config.EncodedStringFactory = new HtmlEncodedStringFactory(); // Html encoding.
  25. //One thing you might want to enable is the debugging feature:
  26. config.Debug = true;
  27. IRazorEngineService service = RazorEngineService.Create(config);
  28. //If you want to use the static Engine class with this new configuration:
  29. Engine.Razor = service;
  30. //When Debug is true you can straight up debug into the generated code. RazorEngine also supports debugging directly into the template files (normally .cshtml files). As as you might see in the above code there is no file to debug into. To provide RazorEngine with the necessary information you need to tell where the file can be found:
  31. string template = "Hello @Model.Name, welcome to RazorEngine!";
  32. string templateFile = "d:/mytemplate.cshtml";
  33. // Provide a non-null file to improve debugging
  34. var loadedTemplateSource = new LoadedTemplateSource(template, templateFile);
  35. var result = Engine.Razor.RunCompile(loadedTemplateSource, "templateKey", null, new { Name = "World" });
  36. //This time when debugging the template you will jump right into the template file.
  37. #endregion test code.
  38. return 123;
  39. }
  40. }
  41. }

上述代码中的 d:/mytemplate.cshtml 的测试记录:

  • 该文件中的断点有效。
  • 该文件的内容与 template 的值无关。
  • 该文件的内容可为任意字符 (包括空文件)。
  • 该文件不能放在 Windows10 的 C 分区,否则会因为访问权限而报错。

测试记录 - 对比 3 种 Type 的 model 的语法

以下测试代码整理自 http://antaris.github.io/RazorEngine/TemplateBasics.html:

  1. using RazorEngine;
  2. using RazorEngine.Templating;
  3. using System.Dynamic;
  4. namespace Net451Console
  5. {
  6. class Program
  7. {
  8. //static void Main(string[] args)
  9. static int Main(string[] args)
  10. {
  11. bool isDefaultAppDomain = NewDomain.Switch();
  12. if(isDefaultAppDomain) {
  13. return 0;
  14. }
  15. #region test code:
  16. string template = "<div>Hello @Model.Name</div>";
  17. var model0 = new Person { Name = "Matt" }; //statically type
  18. var model1 = new { Name = "Matt" }; //anonymous type<

    以上是关于Razor - 模板引擎 / 代码生成 - RazorEngine的主要内容,如果未能解决你的问题,请参考以下文章

    使用Razor视图引擎来生成邮件内容

    Razor语法大全

    ASP.Net MVC开发基础学习笔记:三Razor视图引擎控制器与路由机制学习

    Razor语法大全

    使用razor/asp.net mvc3生成静态html页面?

    ASP.NET MVC Route详解