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