Compiler et exécuter C # et Blazor dans un navigateur

Présentation



Si vous êtes un développeur Web et que vous développez pour un navigateur, vous connaissez certainement JS, qui peut fonctionner à l'intérieur du navigateur. Il y a une opinion que JS n'est pas très approprié pour des calculs et des algorithmes complexes. Et bien que ces dernières années JS ait fait un grand bond en performances et en largeur d'utilisation, de nombreux programmeurs continuent de rêver de lancer le langage système à l'intérieur du navigateur. Dans un avenir proche, le jeu peut changer en raison de WebAssembly.


Microsoft n'est pas immobile et essaie activement de porter .NET vers WebAssembly. Comme l'un des résultats, nous avons obtenu un nouveau cadre pour le développement client - Blazor. Il n'est pas encore clair si Blazor peut être plus rapide que les frameworks JS modernes comme React, Angular, Vue grâce à WebAssembly. Mais il a certainement un gros avantage - le développement en C #, ainsi que le monde entier de .NET Core peuvent être utilisés à l'intérieur de l'application.


Compiler et exécuter C # dans Blazor


Le processus de compilation et d'exécution d'un langage aussi complexe que C # est une tâche complexe et longue. #? - Cela dépend des capacités de la technologie (ou plutôt du cœur). Cependant, il s'est avéré que Microsoft avait déjà tout préparé pour nous.


Créez d'abord une application Blazor.



Après cela, vous devez installer Nuget - un package pour analyser et compiler C #.


 Install-Package Microsoft.CodeAnalysis.CSharp 

Préparez la page de démarrage.


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

Vous devez d'abord analyser la chaîne dans un arbre de syntaxe abstraite. Comme à l'étape suivante, nous compilerons les composants Blazor - nous avons besoin de la dernière version ( LanguageVersion.Latest ) de la langue. Il existe une méthode pour Roslyn pour C # pour cela:


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

Déjà à ce stade, vous pouvez détecter les erreurs de compilation grossières en lisant les diagnostics de l'analyseur.


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

Ensuite, nous compilons Assembly dans un flux binaire.


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

Il convient de noter que vous devez obtenir des references - une liste de métadonnées des bibliothèques connectées. Mais je n'ai pas pu lire ces fichiers le long du chemin Assembly.Location , car il n'y a pas de système de fichiers dans le navigateur. Il peut y avoir un moyen plus efficace de résoudre ce problème, mais le but de cet article est une opportunité conceptuelle, nous allons donc télécharger à nouveau ces bibliothèques via Http et le faire uniquement au premier démarrage de la compilation.


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

Depuis EmitResult vous pouvez savoir si la compilation a réussi et obtenir des erreurs de diagnostic.
Vous devez maintenant charger Assembly dans le AppDomain actuel et exécuter le code compilé. Malheureusement, il n'y a aucun moyen de créer plusieurs AppDomain à l'intérieur du navigateur, donc cela ne fonctionnera pas pour charger et décharger Assembly toute sécurité.


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


À ce stade, nous avons compilé et exécuté le code C # directement dans le navigateur. Un programme peut être composé de plusieurs fichiers et utiliser d'autres bibliothèques .NET. N'est-ce pas génial? Passons maintenant.


Compilez et exécutez le composant Blazor dans un navigateur.


Les composants Blazor sont des modèles modifiés par Razor . Par conséquent, pour compiler le composant Blazor, vous devez déployer un environnement complet pour compiler les modèles Razor et configurer les extensions pour Blazor. Vous devez installer le package Microsoft.AspNetCore.Blazor.Build partir de nuget. Cependant, l'ajouter à notre projet Blazor échouera, car alors l'éditeur de liens ne pourra pas compiler le projet. Par conséquent, vous devez le télécharger, puis ajouter manuellement 3 bibliothèques.


 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 

Nous allons créer un noyau pour compiler Razor et le modifier pour Blazor, car par défaut le noyau générera du code Razor pour les pages.


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

Ce qui manque, c'est juste fileSystem - c'est une abstraction sur le système de fichiers. Nous avons implémenté un système de fichiers vide, cependant, si vous souhaitez compiler des projets complexes avec prise en charge de _ViewImports.cshtml , vous devez implémenter une structure plus complexe en mémoire.
Nous allons maintenant générer le code à partir du composant Blazor du code C #.


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

Vous pouvez également recevoir des messages de diagnostic du doc sur les résultats de la génération de code C # à partir du composant Blazor.
Nous avons maintenant le code du composant C #. Vous devez SyntaxTree , puis compiler Assembly, le charger dans le AppDomain actuel et trouver le type de composant. Identique à l'exemple précédent.


Reste à charger ce composant dans l'application courante. Il existe plusieurs façons de le faire, par exemple, en créant votre propre 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; } } } 


Conclusion


Nous avons compilé et lancé le composant dans le navigateur Blazor. De toute évidence, une compilation complète de code C # dynamique directement dans le navigateur peut impressionner n'importe quel programmeur.


Mais ici, vous devriez considérer ces "pièges":


  • Des extensions et bibliothèques de bind supplémentaires sont nécessaires pour prendre en charge la bind bidirectionnelle de liaison.
  • Pour prendre en charge async, await , de même, nous nous connectons supplémentaires. bibliothèques
  • La compilation des composants liés à Blazor nécessite une compilation en deux étapes.

Tous ces problèmes ont déjà été résolus et c'est un sujet pour un article séparé.


Git


Démo

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


All Articles