下面是一个示例代码,仅依赖于 Razor(用于解析和 C# 代码生成)和 Roslyn(用于 C# 代码编译,但您也可以使用旧的 CodeDom)。
该代码段中没有 MVC,因此,没有 View,没有 .cshtml 文件,没有 Controller,只有 Razor 源解析和编译运行时执行。但仍然存在模型的概念。
您只需要添加以下 nuget 包:Microsoft.AspNetCore.Razor.Language
(使用 v5.0.5 测试),Microsoft.AspNetCore.Razor.Runtime
(使用 v2.2.0 测试)和Microsoft.CodeAnalysis.CSharp
(使用 v3.9.0 测试)掘金。
此 C# 源代码与 .NET 5、NETCore 3.1(对于旧版本,请检查此答案的历史记录)、NETStandard 2 和 .NET Framework 兼容。要测试它,只需创建一个 .NET 框架或 .NET Core 控制台应用程序,粘贴它,添加 nuget,然后手动创建 hello.txt 文件(它必须位于可执行文件旁边)。
using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Hosting;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions; // needed or not depends on .NET version
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace RazorTemplate
{
class Program
{
static void Main(string[] args)
{
// points to the local path
var fs = RazorProjectFileSystem.Create(".");
// customize the default engine a little bit
var engine = RazorProjectEngine.Create(RazorConfiguration.Default, fs, (builder) =>
{
// InheritsDirective.Register(builder); // in .NET core 3.1, compatibility has been broken (again), and this is not needed anymore...
builder.SetNamespace("MyNamespace"); // define a namespace for the Template class
});
// get a razor-templated file. My "hello.txt" template file is defined like this:
//
// @inherits RazorTemplate.MyTemplate
// Hello @Model.Name, welcome to Razor World!
//
var item = fs.GetItem("hello.txt", null);
// parse and generate C# code
var codeDocument = engine.Process(item);
var cs = codeDocument.GetCSharpDocument();
// outputs it on the console
//Console.WriteLine(cs.GeneratedCode);
// now, use roslyn, parse the C# code
var tree = CSharpSyntaxTree.ParseText(cs.GeneratedCode);
// define the dll
const string dllName = "hello";
var compilation = CSharpCompilation.Create(dllName, new[] { tree },
new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location), // include corlib
MetadataReference.CreateFromFile(typeof(RazorCompiledItemAttribute).Assembly.Location), // include Microsoft.AspNetCore.Razor.Runtime
MetadataReference.CreateFromFile(Assembly.GetExecutingAssembly().Location), // this file (that contains the MyTemplate base class)
// for some reason on .NET core, I need to add this... this is not needed with .NET framework
MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Runtime.dll")),
// as found out by @Isantipov, for some other reason on .NET Core for Mac and Linux, we need to add this... this is not needed with .NET framework
MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll"))
},
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); // we want a dll
// compile the dll
string path = Path.Combine(Path.GetFullPath("."), dllName + ".dll");
var result = compilation.Emit(path);
if (!result.Success)
{
Console.WriteLine(string.Join(Environment.NewLine, result.Diagnostics));
return;
}
// load the built dll
Console.WriteLine(path);
var asm = Assembly.LoadFile(path);
// the generated type is defined in our custom namespace, as we asked. "Template" is the type name that razor uses by default.
var template = (MyTemplate)Activator.CreateInstance(asm.GetType("MyNamespace.Template"));
// run the code.
// should display "Hello Killroy, welcome to Razor World!"
template.ExecuteAsync().Wait();
}
}
// the model class. this is 100% specific to your context
public class MyModel
{
// this will map to @Model.Name
public string Name => "Killroy";
}
// the sample base template class. It's not mandatory but I think it's much easier.
public abstract class MyTemplate
{
// this will map to @Model (property name)
public MyModel Model => new MyModel();
public void WriteLiteral(string literal)
{
// replace that by a text writer for example
Console.Write(literal);
}
public void Write(object obj)
{
// replace that by a text writer for example
Console.Write(obj);
}
public async virtual Task ExecuteAsync()
{
await Task.Yield(); // whatever, we just need something that compiles...
}
}
}