ブラウザー内でC#とBlazorをコンパイルして実行する

はじめに









あなたがWeb開発者であり、ブラウザ用に開発している場合、ブラウザ内で実行できるJSに間違いなく精通しています。 JSは複雑な計算やアルゴリズムにはあまり適していないという意見があります。 そして近年、JSはパフォーマンスと使用の幅で大きな飛躍を遂げましたが、多くのプログラマーはブラウザー内でシステム言語を起動することを夢見続けています。 近い将来、WebAssemblyによりゲームが変わる可能性があります。







Microsoftはまだ立ち止まっておらず、.NETをWebAssemblyに積極的に移植しようとしています。 結果の1つとして、クライアント開発用の新しいフレームワーク、Blazorを入手しました。 WebAssemblyにより、BlazorがReact、Angular、Vueなどの最新のJSフレームワークよりも高速であるかどうかはまだ明確ではありません。 しかし、確かに大きな利点があります。C#での開発と.NET Coreの全世界をアプリケーション内で使用できます。







BlazorでのC#のコンパイルと実行



C#のような複雑な言語をコンパイルして実行するプロセスは、複雑で時間のかかるタスクです。 #?



-それは、テクノロジー(またはコア)の機能に依存します。 しかし、Microsoftは、結局のところ、すべてを準備していたのです。







最初に、Blazorアプリケーションを作成します。













その後、C#を分析およびコンパイルするためのパッケージであるNugetをインストールする必要があります。







 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



)バージョンが必要です。 このために、C#のRoslynのメソッドがあります。







 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コードを生成するため、Razorをコンパイルするためのカーネルを作成し、Blazor用に変更します。







 var engine = RazorProjectEngine.Create(BlazorExtensionInitializer.DefaultConfiguration, fileSystem, b => { BlazorExtensionInitializer.Register(b); });
      
      





実行するには、 fileSystem



のみが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



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#コードの完全なコンパイルは、プログラマーを感動させることができます。







しかし、ここではそのような「落とし穴」を考慮する必要があります。









これらの問題はすべて解決済みであり、これは別の記事のトピックです。







Git







デモ








All Articles