在浏览器中编译并运行C#和Blazor

引言



如果您是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相关组件需要两步编译。

所有这些问题已经解决,这是另一篇文章的主题。


吉特


演示版

Source: https://habr.com/ru/post/zh-CN433818/


All Articles