Kompilieren und Ausführen von C # und Blazor in einem Browser

Einführung



Wenn Sie ein Webentwickler sind und für einen Browser entwickeln, sind Sie definitiv mit JS vertraut, das im Browser ausgeführt werden kann. Es gibt eine Meinung, dass JS für komplexe Berechnungen und Algorithmen nicht sehr geeignet ist. Und obwohl JS in den letzten Jahren einen großen Sprung in Leistung und Anwendungsbreite gemacht hat, träumen viele Programmierer weiterhin davon, die Systemsprache im Browser zu starten. In naher Zukunft kann sich das Spiel aufgrund von WebAssembly ändern.


Microsoft steht nicht still und versucht aktiv, .NET auf WebAssembly zu portieren. Als eines der Ergebnisse haben wir ein neues Framework für die Kundenentwicklung erhalten - Blazor. Es ist noch nicht klar, ob Blazor aufgrund von WebAssembly schneller sein kann als moderne JS-Frameworks wie React, Angular, Vue. Aber es hat definitiv einen großen Vorteil - die Entwicklung in C # sowie die gesamte Welt von .NET Core können innerhalb der Anwendung verwendet werden.


Kompilieren und Ausführen von C # in Blazor


Das Kompilieren und Ausführen einer so komplexen Sprache wie C # ist eine komplexe und zeitaufwändige Aufgabe. #? - Es hängt von den Fähigkeiten der Technologie (oder vielmehr vom Kern) ab. Wie sich herausstellte, hatte Microsoft jedoch bereits alles für uns vorbereitet.


Erstellen Sie zunächst eine Blazor-Anwendung.



Danach müssen Sie Nuget installieren - ein Paket zum Analysieren und Kompilieren von C #.


 Install-Package Microsoft.CodeAnalysis.CSharp 

Bereiten Sie die Startseite vor.


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

Zuerst müssen Sie die Zeichenfolge in einen abstrakten Syntaxbaum analysieren. Da wir im nächsten Schritt Blazor-Komponenten kompilieren werden, benötigen wir die neueste ( LanguageVersion.Latest ) Version der Sprache. Hierfür gibt es eine Methode für Roslyn für C #:


 SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code, new CSharpParseOptions(LanguageVersion.Latest)); 

Bereits zu diesem Zeitpunkt können Sie grobe Kompilierungsfehler erkennen, indem Sie die Parser-Diagnose lesen.


  foreach (var diagnostic in syntaxTree.GetDiagnostics()) { CompileLog.Add(diagnostic.ToString()); } 

Als nächstes kompilieren wir Assembly zu einem binären Stream.


 CSharpCompilation compilation = CSharpCompilation.Create("CompileBlazorInBlazor.Demo", new[] {syntaxTree}, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (MemoryStream stream = new MemoryStream()) { EmitResult result = compilation.Emit(stream); } 

Es ist zu beachten, dass Sie references benötigen - eine Liste von Metadaten verbundener Bibliotheken. Ich konnte diese Dateien jedoch nicht im Assembly.Location Pfad lesen, da der Browser kein Dateisystem enthält. Es gibt möglicherweise einen effizienteren Weg, um dieses Problem zu lösen, aber der Zweck dieses Artikels ist eine konzeptionelle Möglichkeit. Daher werden wir diese Bibliotheken erneut über HTTP herunterladen und dies erst beim ersten Start der Kompilierung tun.


 foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { references.Add( MetadataReference.CreateFromStream( await this._http.GetStreamAsync("/_framework/_bin/" + assembly.Location))); } 

In EmitResult Sie herausfinden, ob die Kompilierung erfolgreich war, und Diagnosefehler erhalten.
Jetzt müssen Sie Assembly in die aktuelle AppDomain und den kompilierten Code ausführen. Leider gibt es keine Möglichkeit, mehrere AppDomain im Browser zu erstellen, sodass das sichere Laden und Entladen von Assembly nicht funktioniert.


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


Zu diesem Zeitpunkt haben wir C # -Code direkt im Browser kompiliert und ausgeführt. Ein Programm kann aus mehreren Dateien bestehen und andere .NET-Bibliotheken verwenden. Ist das nicht toll? Nun gehen wir weiter.


Kompilieren Sie die Blazor-Komponente und führen Sie sie in einem Browser aus.


Blazor-Komponenten sind Razor Vorlagen. Um die Blazor-Komponente zu kompilieren, müssen Sie daher eine gesamte Umgebung bereitstellen, um Razor-Vorlagen zu kompilieren und Erweiterungen für Blazor zu konfigurieren. Sie müssen das Microsoft.AspNetCore.Blazor.Build Paket von Nuget installieren. Das Hinzufügen zu unserem Blazor-Projekt schlägt jedoch fehl, da der Linker das Projekt dann nicht kompilieren kann. Daher müssen Sie es herunterladen und dann manuell 3 Bibliotheken hinzufügen.


 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 

Erstellen wir einen Kernel zum Kompilieren von Razor und ändern ihn für Blazor, da der Kernel standardmäßig Razor-Code für die Seiten generiert.


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

Zur Ausführung fileSystem nur das fileSystem - dies ist eine Abstraktion über das Dateisystem. Wir haben ein leeres Dateisystem implementiert. Wenn Sie jedoch komplexe Projekte mit Unterstützung für _ViewImports.cshtml kompilieren _ViewImports.cshtml , müssen Sie eine komplexere Struktur im Speicher implementieren.
Jetzt generieren wir den Code aus der Blazor-Komponente des C # -Codes.


  var file = new MemoryRazorProjectItem(code); var doc = engine.Process(file).GetCSharpDocument(); var csCode = doc.GeneratedCode; 

Sie können vom doc auch Diagnosemeldungen über die Ergebnisse der Generierung von C # -Code aus der Blazor-Komponente erhalten.
Jetzt haben wir den Code der C # -Komponente. Sie müssen SyntaxTree , dann Assembly kompilieren, in die aktuelle AppDomain laden und den Komponententyp ermitteln. Gleich wie im vorherigen Beispiel.


Diese Komponente muss noch in die aktuelle Anwendung geladen werden. Es gibt verschiedene Möglichkeiten, dies zu tun, indem Sie beispielsweise Ihr eigenes 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; } } } 


Fazit


Wir haben die Komponente im Blazor-Browser kompiliert und gestartet. Offensichtlich kann eine vollständige Zusammenstellung von dynamischem C # -Code direkt im Browser jeden Programmierer beeindrucken.


Aber hier sollten Sie solche "Fallstricke" berücksichtigen:


  • Zusätzliche bind und Bibliotheken sind erforderlich, um bind bidirektionale Bindungsbindung zu unterstützen.
  • async, await zu unterstützen async, await und verbinden Sie sich zusätzlich. Bibliotheken
  • Das Kompilieren von Blazor-bezogenen Komponenten erfordert eine zweistufige Kompilierung.

Alle diese Probleme wurden bereits behoben und dies ist ein Thema für einen separaten Artikel.


Git


Demo

Source: https://habr.com/ru/post/de433818/


All Articles