Razor模板引擎的自定义实现

Razor 是一种用于 ASP.NET MVC 视图的模板引擎,它设计用于将模型应用到模板中,生成 HTML 页面。本文将探讨如何将 Razor 模板引擎应用于 ASP.NET 之外的环境,并详细介绍其工作原理。

HomeController.cs 示例

在 ASP.NET MVC 中,控制器负责处理用户请求并返回视图。以下是一个简单的 HomeController 示例,它创建了一个模型并将其传递给视图。

public class HomeController : Controller { public IActionResult Index() { IndexModel model = new IndexModel { Name = "Harry Harrison", Novels = new List() { new Novel { Name = "Deathworld", Year = 1960 }, new Novel { Name = "Spaceship Medic", Year = 1970 }, } }; return this.View(model); } }

Index.cshtml 示例

视图文件 Index.cshtml 使用 Razor 语法来动态生成 HTML。它从模型中读取数据并将其显示在页面上。

@model IndexModel

@Model.Name

    @foreach (Novel novel in Model.Novels) {
  • @novel.Year, @novel.Name
  • }

输出的 HTML

当用户访问 Index 视图时,他们将看到以下 HTML 输出,其中包含了模型中的数据。

Harry Harrison

  • 1960, Deathworld
  • 1970, Spaceship Medic

Razor模板引擎的工作原理

1. 模板解析:Razor 模板需要被编译,首先 Razor 需要将字符串模板转换成 C# 代码。将使用最新的 ASP.NET Core Razor 包:Microsoft.AspNetCore.Razor.Language。

string GenerateCodeFromTemplate(string template) { RazorProjectEngine engine = RazorProjectEngine.Create( RazorConfiguration.Default, RazorProjectFileSystem.Create( "." ), (builder) => { builder.SetNamespace("MyNamespace"); }); string fileName = Path.GetRandomFileName(); RazorSourceDocument document = RazorSourceDocument.Create(template, fileName); RazorCodeDocument codeDocument = engine.Process( document, null, new List(), new List()); RazorCSharpDocument razorCSharpDocument = codeDocument.GetCSharpDocument(); return razorCSharpDocument.GeneratedCode; }

调用 GenerateCodeFromTemplate 方法将生成实际的类源代码。

GenerateCodeFromTemplate("Hello @Model.Name")

2. 编译:将使用 Roslyn 编译代码。需要的包是 Microsoft.CodeAnalysis.CSharp。

static MemoryStream Compile(string assemblyName, string code) { SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code); CSharpCompilation compilation = CSharpCompilation.Create( assemblyName, new[] { syntaxTree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(MyTemplateBase).Assembly.Location), MetadataReference.CreateFromFile(typeof(DynamicObject).Assembly.Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("Microsoft.CSharp")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("netstandard")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Runtime")).Location), }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); MemoryStream memoryStream = new MemoryStream(); EmitResult emitResult = compilation.Emit(memoryStream); if (!emitResult.Success) return null; memoryStream.Position = 0; return memoryStream; }

现在得到了内存流中的程序集字节码!

3. 运行:让加载字节码。

Assembly assembly = Assembly.Load(memoryStream.ToArray()); Type templateType = assembly.GetType("MyNamespace.Template");

现在可以创建实例并尝试运行它。

MyTemplateBase instance = (MyTemplateBase)Activator.CreateInstance(templateType); instance.Model = new { Name = "Harry Harrison" }; instance.ExecuteAsync().Wait(); Console.WriteLine(instance.Result());

这将导致错误,因为匿名对象的属性不能立即访问。为了克服这个问题,将使用基于 DynamicObject 的包装器。

public class AnonymousTypeWrapper : DynamicObject { private readonly object model; public AnonymousTypeWrapper(object model) { this.model = model; } public override bool TryGetMember(GetMemberBinder binder, out object result) { PropertyInfo propertyInfo = this.model.GetType().GetProperty(binder.Name); if (propertyInfo == null) { result = null; return false; } result = propertyInfo.GetValue(this.model, null); return true; } }

最后,应用最后一部分拼图:

MyTemplateBase instance = (MyTemplateBase)Activator.CreateInstance(templateType); var model = new { Name = "Harry Harrison" }; instance.Model = new AnonymousTypeWrapper(model); instance.ExecuteAsync().Wait(); Console.WriteLine(instance.Result());
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485