手动构建String Razor模板引擎(.NET Core)

2020-12-25  乐帮网

netcore

在本文中,将会介绍ASP.NET Core Razor如何将模板转换为程序集并运行。我们还会介绍的基于Razor的字符串模板引擎在ASP.NET外部使用的步骤。

主机内容:
了解ASP.NET Core Razor如何将模板转换为程序集并运行它们
逐步完成使我们的基于Razor的字符串模板引擎的步骤,以在ASP.NET之外使用。
Razor解析器的工作原理在此就不做介绍了。

在这之前你必须知道以下内容:Razor是ASP.NET MVC视图的模板引擎。它旨在将模型应用于模板以生成HTML页面。

HomeController.cs

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

        return this.View(model);
    }
}

Index.cshtml

@model IndexModel

<h1>@Model.Name</h1>

<ul>
    @foreach (Novel novel in Model.Novels)
    {
        <li>@novel.Year, @novel.Name</li>
    }
</ul>

最终生成的静态html代码如下

<h1>Harry Harrison</h1>
<ul>
    <li>1960, Deathworld</li>
    <li>1970, Spaceship Medic</li>
</ul>

这个过程是怎么进行的呢?

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<RazorSourceDocument>(),
        new List<TagHelperDescriptor>());

    RazorCSharpDocument razorCSharpDocument = codeDocument.GetCSharpDocument();

    return razorCSharpDocument.GeneratedCode;
} 

调用GenerateCodeFromTemplate将得到实际的类源代码。

GenerateCodeFromTemplate("Hello @Model.Name")
namespace MyNamespace
{
    public class Template
    {
        public async override global::System.Threading.Tasks.Task ExecuteAsync()
        {
            WriteLiteral("Hello ");
            Write(Model.Name);
        }
    }

在我定义的Template ,类名将始终位于您选择的名称空间MyNamespace 下。
由于未定义WriteLiteral 和Write 函数,因此这些代码将无法编译,我们需要Template继承一些东西才能使其正常工作。

StringBuilder builder = new StringBuilder();

builder.AppendLine("@inherits ConsoleApp9.MyTemplateBase");
builder.Append(@"Hello @Model.Name");

Console.WriteLine(GenerateCodeFromTemplate(builder.ToString()));

现在我们有:
让我们定义MyTemplateBase准备编译模板。

它具有三个重要成员:

public dynamic Model { get; set; }; //–我们将在模板中使用该模型来引用数据
public abstract Task ExecuteAsync(); //–开始执行的模板入口点
public string Result();// –将来会取得成果的东西
public abstract class MyTemplateBase
{
    private readonly StringBuilder stringBuilder = new StringBuilder();
    public dynamic Model { get; set; }

    public abstract Task ExecuteAsync();

    public void WriteLiteral(string literal)
    {
        this.stringBuilder.Append(literal);
    }

    public void Write(object obj)
    {
        this.stringBuilder.Append(obj);
    }

    public string Result()
    {
        return this.stringBuilder.ToString();
    }
}

2.编译

我们将使用Roslyn来编译此代码。包:Microsoft.CodeAnalysis.CSharp
为了使用Roslyn构建对象,您需要构建SyndexTree和引用程序集(就像在常规控制台应用程序中那样)。

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());

这将导致错误,因为无法立即访问匿名对象的属性。

'object' does not contain a definition for Name
为了克服这个问题,我们将基于使用包装器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);

        // nested objects and array handling goes here
        // full code: https://github.com/adoconnection/RazorEngineCore/blob/master/
        // RazorEngineCore/AnonymousTypeWrapper.cs

        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());

源代码和Nuget包

https://github.com/adoconnection/RazorEngineCore
Nuget: Install-Package RazorEngineCore

原文地址:这里

公众号二维码

关注我的微信公众号
在公众号里留言交流
投稿邮箱:1052839972@qq.com

庭院深深深几许?杨柳堆烟,帘幕无重数。
玉勒雕鞍游冶处,楼高不见章台路。
雨横风狂三月暮。门掩黄昏,无计留春住。
泪眼问花花不语,乱红飞过秋千去。

欧阳修

付款二维码

如果感觉对您有帮助
欢迎向作者提供捐赠
这将是创作的最大动力