Compilando e executando C # e Blazor dentro de um navegador

1. Introdução



Se você é um desenvolvedor da Web e está desenvolvendo um navegador, definitivamente está familiarizado com o JS, que pode ser executado dentro do navegador. Há uma opinião de que o JS não é muito adequado para cálculos e algoritmos complexos. E, embora nos últimos anos a JS tenha dado um grande salto em desempenho e amplitude de uso, muitos programadores continuam sonhando em lançar a linguagem do sistema dentro do navegador. Num futuro próximo, o jogo pode mudar devido ao WebAssembly.


A Microsoft não está parada e está ativamente tentando portar o .NET para o WebAssembly. Como um dos resultados, obtivemos uma nova estrutura para o desenvolvimento do cliente - Blazor. Ainda não está claro se o Blazor pode ser mais rápido que as estruturas JS modernas, como React, Angular, Vue devido ao WebAssembly. Mas definitivamente tem uma grande vantagem - o desenvolvimento em C #, assim como todo o mundo do .NET Core, pode ser usado dentro do aplicativo.


Compilando e executando C # no Blazor


O processo de compilação e execução de uma linguagem complexa como C # é uma tarefa complexa e demorada. #? - Depende das capacidades da tecnologia (ou melhor, do núcleo). No entanto, a Microsoft, como se viu, já havia preparado tudo para nós.


Primeiro, crie um aplicativo Blazor.



Depois disso, você precisa instalar o Nuget - um pacote para analisar e compilar C #.


 Install-Package Microsoft.CodeAnalysis.CSharp 

Prepare a página inicial.


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

Primeiro, você precisa analisar a string em uma árvore de sintaxe abstrata. Como na próxima etapa iremos compilar os componentes do Blazor - precisamos da versão mais recente ( LanguageVersion.Latest ) da linguagem. Existe um método para o Roslyn para C # para isso:


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

Já neste estágio, você pode detectar erros brutos de compilação lendo os diagnósticos do analisador.


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

Em seguida, compilamos o Assembly em um fluxo binário.


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

Note-se que você precisa obter references - uma lista de metadados das bibliotecas conectadas. Mas não consegui ler esses arquivos no caminho Assembly.Location , pois não há sistema de arquivos no navegador. Pode haver uma maneira mais eficiente de resolver esse problema, mas o objetivo deste artigo é uma oportunidade conceitual, portanto, faremos o download dessas bibliotecas novamente via Http e faremos isso apenas no primeiro início da compilação.


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

No EmitResult você pode descobrir se a compilação foi bem-sucedida e também obter erros de diagnóstico.
Agora você precisa carregar o Assembly no AppDomain atual e executar o código compilado. Infelizmente, não há como criar vários AppDomain dentro do navegador; portanto, não funcionará para carregar e descarregar o Assembly com segurança.


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


Nesta fase, compilamos e executamos o código C # diretamente no navegador. Um programa pode consistir em vários arquivos e usar outras bibliotecas .NET. Isso não é ótimo? Agora vamos seguir em frente.


Compile e execute o componente Blazor em um navegador.


Os componentes Blazor são modelos modificados pelo Razor . Portanto, para compilar o componente Blazor, é necessário implantar um ambiente inteiro para compilar modelos do Razor e configurar extensões para o Blazor. Você precisa instalar o pacote Microsoft.AspNetCore.Blazor.Build partir do nuget. No entanto, adicioná-lo ao nosso projeto Blazor falhará, porque o vinculador não poderá compilar o projeto. Portanto, você precisa fazer o download e adicionar 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 

Vamos criar um kernel para compilar o Razor e modificá-lo para o Blazor, pois, por padrão, o kernel irá gerar o código do Razor para as páginas.


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

Para execução, apenas o sistema de fileSystem está fileSystem - isso é uma abstração do sistema de arquivos. Implementamos um sistema de arquivos vazio, no entanto, se você deseja compilar projetos complexos com suporte para _ViewImports.cshtml , é necessário implementar uma estrutura mais complexa na memória.
Agora vamos gerar o código do componente Blazor do código C #.


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

Você também pode receber mensagens de diagnóstico do doc sobre os resultados da geração de código C # a partir do componente Blazor.
Agora temos o código do componente C #. Você precisa SyntaxTree , compilar o Assembly, carregá-lo no AppDomain atual e encontrar o tipo de componente. O mesmo que no exemplo anterior.


Resta carregar este componente no aplicativo atual. Existem várias maneiras de fazer isso, por exemplo, criando seu próprio 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; } } } 


Conclusão


Compilamos e lançamos o componente no navegador Blazor. Obviamente, uma compilação completa de código C # dinâmico dentro do navegador pode impressionar qualquer programador.


Mas aqui você deve considerar essas "armadilhas":


  • Extensões de bind e bibliotecas adicionais são necessárias para suportar a ligação bidirecional de ligação.
  • Para suportar async, await , da mesma forma nos conectamos adicionais. bibliotecas
  • A compilação de componentes relacionados ao Blazor requer uma compilação em duas etapas.

Todos esses problemas já foram resolvidos e este é um tópico para um artigo separado.


Git


Demo

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


All Articles