引言

如果您是Web开发人员并且正在为浏览器开发,那么您一定对JS熟悉,它可以在浏览器中运行。 有一种观点认为JS不太适合复杂的计算和算法。 而且,尽管近年来JS在性能和使用范围方面取得了长足的飞跃,但许多程序员仍然梦想着在浏览器内部启动系统语言。 在不久的将来,游戏可能会由于WebAssembly而改变。
微软并没有停滞不前,而是积极尝试将.NET移植到WebAssembly。 作为结果之一,我们获得了一个用于客户开发的新框架-Blazor。 由于WebAssembly,尚不清楚Blazor是否可以比React,Angular,Vue等现代JS框架更快。 但是它绝对具有很大的优势-用C#开发,并且.NET Core的整个世界都可以在应用程序内部使用。
在Blazor中编译和执行C#
编译和执行诸如C#这样的复杂语言的过程是一项复杂且耗时的任务。 #?
-它取决于技术(或核心)的功能。 但是,事实证明,微软已经为我们准备了一切。
首先,创建一个Blazor应用程序。

之后,您需要安装Nuget-一个用于分析和编译C#的软件包。
Install-Package Microsoft.CodeAnalysis.CSharp
准备起始页。
@page "/" @inject CompileService service <h1>Compile and Run C# in Browser</h1> <div> <div class="form-group"> <label for="exampleFormControlTextarea1">C# Code</label> <textarea class="form-control" id="exampleFormControlTextarea1" rows="10" bind="@CsCode"></textarea> </div> <button type="button" class="btn btn-primary" onclick="@Run">Run</button> <div class="card"> <div class="card-body"> <pre>@ResultText</pre> </div> </div> <div class="card"> <div class="card-body"> <pre>@CompileText</pre> </div> </div> </div> @functions { string CsCode { get; set; } string ResultText { get; set; } string CompileText { get; set; } public async Task Run() { ResultText = await service.CompileAndRun(CsCode); CompileText = string.Join("\r\n", service.CompileLog); this.StateHasChanged(); } }
首先,您需要将字符串解析为抽象语法树。 由于在下一步中,我们将编译Blazor组件-我们需要该LanguageVersion.Latest
的最新版本( LanguageVersion.Latest
)。 Roslyn为此提供了一种用于C#的方法:
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code, new CSharpParseOptions(LanguageVersion.Latest));
在此阶段,您已经可以通过阅读解析器诊断程序来检测总体编译错误。
foreach (var diagnostic in syntaxTree.GetDiagnostics()) { CompileLog.Add(diagnostic.ToString()); }
接下来,我们将Assembly
编译为二进制流。
CSharpCompilation compilation = CSharpCompilation.Create("CompileBlazorInBlazor.Demo", new[] {syntaxTree}, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (MemoryStream stream = new MemoryStream()) { EmitResult result = compilation.Emit(stream); }
应该注意的是,您需要获取references
-已连接库的元数据列表。 但是我无法沿着Assembly.Location
路径读取这些文件,因为浏览器中没有文件系统。 解决这个问题的方法可能更有效,但是本文的目的是一个概念上的机会,因此我们将再次通过Http下载这些库,并且仅在编译开始时进行。
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { references.Add( MetadataReference.CreateFromStream( await this._http.GetStreamAsync("/_framework/_bin/" + assembly.Location))); }
从EmitResult
您可以找到编译是否成功以及诊断错误。
现在,您需要将Assembly
加载到当前的AppDomain
并执行编译的代码。 不幸的是,无法在浏览器中创建多个AppDomain
,因此无法安全地加载和卸载Assembly
。
Assembly assemby = AppDomain.CurrentDomain.Load(stream.ToArray()); var type = assemby.GetExportedTypes().FirstOrDefault(); var methodInfo = type.GetMethod("Run"); var instance = Activator.CreateInstance(type); return (string) methodInfo.Invoke(instance, new object[] {"my UserName", 12});

在这一阶段,我们直接在浏览器中编译并执行C#代码。 一个程序可以包含几个文件,并可以使用其他.NET库。 那不是很好吗? 现在继续前进。
在浏览器中编译并运行Blazor组件。
Blazor组件是Razor
修改的模板。 因此,要编译Blazor组件,您需要部署整个环境以编译Razor模板并配置Blazor的扩展。 您需要从nuget安装Microsoft.AspNetCore.Blazor.Build
软件包。 但是,将其添加到Blazor项目将失败,因为链接器将无法编译该项目。 因此,您需要下载它,然后手动添加3个库。
microsoft.aspnetcore.blazor.build\0.7.0\tools\Microsoft.AspNetCore.Blazor.Razor.Extensions.dll microsoft.aspnetcore.blazor.build\0.7.0\tools\Microsoft.AspNetCore.Razor.Language.dll microsoft.aspnetcore.blazor.build\0.7.0\tools\Microsoft.CodeAnalysis.Razor.dll
让我们创建一个用于编译Razor
的内核,并为Blazor对其进行修改,因为默认情况下,该内核将为页面生成Razor代码。
var engine = RazorProjectEngine.Create(BlazorExtensionInitializer.DefaultConfiguration, fileSystem, b => { BlazorExtensionInitializer.Register(b); });
为了执行,仅fileSystem
这是对文件系统的抽象。 我们实现了一个空文件系统,但是,如果要编译支持_ViewImports.cshtml
复杂项目,则需要在内存中实现更复杂的结构。
现在,我们将从C#代码的Blazor组件生成代码。
var file = new MemoryRazorProjectItem(code); var doc = engine.Process(file).GetCSharpDocument(); var csCode = doc.GeneratedCode;
您还可以从doc
接收有关Blazor组件生成C#代码的结果的诊断消息。
现在我们得到了C#组件的代码。 您需要SyntaxTree
,然后编译Assembly,将其加载到当前的AppDomain中并找到组件的类型。 与前面的示例相同。
仍然需要将此组件加载到当前应用程序中。 有几种方法可以做到这一点,例如,通过创建自己的RenderFragment
。
@inject CompileService service <div class="card"> <div class="card-body"> @Result </div> </div> @functions { RenderFragment Result = null; string Code { get; set; } public async Task Run() { var type = await service.CompileBlazor(Code); if (type != null) { Result = builder => { builder.OpenComponent(0, type); builder.CloseComponent(); }; } else { Result = null; } } }

结论
我们在Blazor浏览器中编译并启动了该组件。 显然,在浏览器内部完全编译动态C#代码可以打动任何程序员。
但是在这里您应该考虑这样的“陷阱”:
- 需要其他
bind
扩展和库来支持bind
双向绑定。 - 为了支持
async, await
,类似地,我们连接其他的。 图书馆 - 编译Blazor相关组件需要两步编译。
所有这些问题已经解决,这是另一篇文章的主题。
吉特
演示版