Razor 是一种用于 ASP.NET MVC 视图的模板引擎,它设计用于将模型应用到模板中,生成 HTML 页面。本文将探讨如何将 Razor 模板引擎应用于 ASP.NET 之外的环境,并详细介绍其工作原理。
在 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 使用 Razor 语法来动态生成 HTML。它从模型中读取数据并将其显示在页面上。
@model IndexModel
@Model.Name
@foreach (Novel novel in Model.Novels)
{
-
@novel.Year, @novel.Name
}
当用户访问 Index 视图时,他们将看到以下 HTML 输出,其中包含了模型中的数据。
Harry Harrison
-
1960, Deathworld
-
1970, Spaceship Medic
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());