Compilar y ejecutar C # y Blazor dentro de un navegador

Introduccion



Si usted es un desarrollador web y está desarrollando un navegador, definitivamente está familiarizado con JS, que puede ejecutarse dentro del navegador. Existe la opinión de que JS no es muy adecuado para cálculos y algoritmos complejos. Y aunque en los últimos años JS ha dado un gran salto en rendimiento y amplitud de uso, muchos programadores continúan soñando con lanzar el lenguaje del sistema dentro del navegador. En un futuro cercano, el juego puede cambiar debido a WebAssembly.


Microsoft no se detiene y está intentando activamente portar .NET a WebAssembly. Como uno de los resultados, obtuvimos un nuevo marco para el desarrollo de clientes: Blazor. Todavía no está claro si Blazor puede ser más rápido que los marcos JS modernos como React, Angular, Vue debido a WebAssembly. Pero definitivamente tiene una gran ventaja: el desarrollo en C #, así como todo el mundo de .NET Core se puede usar dentro de la aplicación.


Compilar y ejecutar C # en Blazor


El proceso de compilación y ejecución de un lenguaje tan complejo como C # es una tarea compleja y que requiere mucho tiempo. #? - Depende de las capacidades de la tecnología (o más bien, el núcleo). Sin embargo, Microsoft resultó que ya había preparado todo para nosotros.


Primero, cree una aplicación Blazor.



Después de eso, debe instalar Nuget, un paquete para analizar y compilar C #.


 Install-Package Microsoft.CodeAnalysis.CSharp 

Prepara la página de inicio.


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

Primero debe analizar la cadena en un árbol de sintaxis abstracta. Como en el siguiente paso compilaremos los componentes de Blazor, necesitamos la última versión ( LanguageVersion.Latest ) del idioma. Hay un método para Roslyn para C # para esto:


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

Ya en esta etapa, puede detectar errores graves de compilación leyendo los diagnósticos del analizador.


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

A continuación, compilamos Assembly en una secuencia binaria.


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

Cabe señalar que necesita obtener references : una lista de metadatos de bibliotecas conectadas. Pero no pude leer estos archivos a lo largo de la ruta de acceso de la Assembly.Location , ya que no hay un sistema de archivos en el navegador. Puede haber una forma más eficiente de resolver este problema, pero el propósito de este artículo es una oportunidad conceptual, por lo que volveremos a descargar estas bibliotecas a través de Http y lo haremos solo al comienzo de la compilación.


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

Desde EmitResult puede averiguar si la compilación fue exitosa, así como obtener errores de diagnóstico.
Ahora necesita cargar Assembly en el AppDomain actual y ejecutar el código compilado. Desafortunadamente, no hay forma de crear múltiples AppDomain dentro del navegador, por lo que no funcionará cargar y descargar el Assembly forma segura.


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


En esta etapa, compilamos y ejecutamos código C # directamente en el navegador. Un programa puede constar de varios archivos y usar otras bibliotecas .NET. ¿No es genial? Ahora sigamos adelante.


Compile y ejecute el componente Blazor en un navegador.


Los componentes de Blazor son plantillas modificadas por Razor . Por lo tanto, para compilar el componente Blazor, debe implementar un entorno completo para compilar plantillas Razor y configurar extensiones para Blazor. Debe instalar el paquete Microsoft.AspNetCore.Blazor.Build desde nuget. Sin embargo, agregarlo a nuestro proyecto Blazor fallará, porque entonces el enlazador no podrá compilar el proyecto. Por lo tanto, debe descargarlo y luego agregar manualmente 3 bibliotecas.


 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 

Creemos un núcleo para compilar Razor y modifíquelo para Blazor, ya que de forma predeterminada el núcleo generará el código Razor para las páginas.


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

Para la ejecución, solo fileSystem : esta es una abstracción sobre el sistema de archivos. Implementamos un sistema de archivos vacío, sin embargo, si desea compilar proyectos complejos con soporte para _ViewImports.cshtml , entonces necesita implementar una estructura más compleja en la memoria.
Ahora generaremos el código del componente Blazor del código C #.


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

También puede recibir mensajes de diagnóstico del doc sobre los resultados de generar código C # desde el componente Blazor.
Ahora tenemos el código del componente C #. SyntaxTree , luego compilar Assembly, cargarlo en el AppDomain actual y encontrar el tipo de componente. Igual que en el ejemplo anterior.


Queda por cargar este componente en la aplicación actual. Hay varias formas de hacer esto, por ejemplo, creando su propio 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; } } } 


Conclusión


Compilamos y lanzamos el componente en el navegador Blazor. Obviamente, una compilación completa de código dinámico de C # dentro del navegador puede impresionar a cualquier programador.


Pero aquí debe considerar tales "trampas":


  • Se necesitan extensiones y bibliotecas de bind adicionales para admitir el bind bidireccional de enlace.
  • Para soportar async, await , de manera similar conectamos adicional. bibliotecas
  • Compilar componentes relacionados con Blazor requiere una compilación de dos pasos.

Todos estos problemas ya se han resuelto y este es un tema para un artículo separado.


Git


Demo

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


All Articles