106  
csharp
Поиск  
Always will be ready notify the world about expectations as easy as possible: job change page
30 ноября 2020 г.

Компиляция и запуск C# и Blazor внутри браузера

Источник:
Просмотров:
2148

Если вы Web-разработчик и ведете разработку для браузера, то вы точно знакомы с JS, который может исполняться внутри браузера. Существует мнение, что JS не сильно подходит для сложных вычислений и алгоритмов. И хотя в последние годы JS cделал большой рывок в производительности и широте использования, многие программисты продолжают мечтать запустить системный язык внутри браузера. В ближайшее время игра может поменяться благодаря WebAssembly.

Microsoft не стоит на месте и активно пытается портировать .NET в WebAssembly. Как один из результатов мы получили новый фреймворк для клиентской разработки — Blazor. Пока не совсем очевидно, сможет ли Blazor за счет WebAssembly быть быстрее современных JS — фреймворков типа React, Angular, Vue. Но он точно имеет большое преимущество — разработка на C#, а так же весь мир .NET Core может быть использован внутри приложения.

Компиляция и выполнение C# в Blazor

Процесс компиляции и выполнения такого сложного языка как C# — это сложная и трудоемкая задача. А можно ли внутри браузера скомпилировать и выполнить С#? — Это зависит от возможностей технологии (а точнее, ядра). Однако в Microsoft, как оказалось, уже все подготовили для нас.

Для начала создадим Blazor приложение.

После этого нужно установить Nuget — пакет для анализа и компиляции C#.

Install-Package Microsoft.CodeAnalysis.CSharp

Подготовим стартовую страницу.

@page "/"
@inject CompileService service

Compile and Run C# in Browser

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

Для начала надо распарсить строку в абстрактное синтаксическое дерево. Так как в следующем этапе мы будем компилировать Blazor компоненты — нам нужна самая последняя (LanguageVersion.Latest) версия языка. Для этого в Roslyn для C# есть метод:

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

Уже на этом этапе можно обнаружить грубые ошибки компиляции, вычитав диагностику парсера.

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

Далее выполняем компиляцию Assembly в бинарный поток.

CSharpCompilation compilation = CSharpCompilation.Create("CompileBlazorInBlazor.Demo", new[] {syntaxTree}, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

using (MemoryStream stream = new MemoryStream())
{
    EmitResult result = compilation.Emit(stream);
}

Следует учесть, что нужно получить references — список метаданных подключенных библиотек. Но прочитать эти файлы по пути Assembly.Location не получилось, так как в браузере файловой системы нет. Возможно, есть более эффективный способ решения этой проблемы, но цель данной статьи — концептуальная возможность, поэтому скачаем эти библиотки снова по Http и сделаем это только при первом запуске компиляции.

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

Из EmitResult можно узнать была ли успешной компиляция, а так же получить диагностические ошибки.
Теперь нужно загрузить Assembly в текущий AppDomain и выполнить скомпилированный код. К сожалению, внутри браузера нет возможности создавать несколько AppDomain, поэтому безопасно загрузить и выгрузить Assembly не получится.

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

На данном этапе мы скомпилировали и выполнили C# код прямо в браузере. Программа может состоять из нескольких файлов и использовать другие .NET библиотеки. Разве это не здорово? Теперь идем дальше.

Компиляция и запуск Blazor компонента в браузере.

Компоненты Blazor — это модифицированные Razor шаблоны. Поэтому чтобы скомпилировать Blazor комопнент, нужно развернуть целую среду для компиляции Razor шаблонов и настроить расширения для Blazor. Нужно установить пакет Microsoft.AspNetCore.Blazor.Build из nuget. Однако, добавить его в наш проект Blazor не получится, так как потом линкер не сможет скомпилировать проект. Поэтому нужно его скачать, а потом вручную добавить 3 библиотеки.

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

Создадим ядро для компиляции Razor и модифицируем его для Blazor, так как по умолчанию ядро будет генерировать код Razor страниц.

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

Для выполнения не хватает только fileSystem — это абстракция над файловой системой. Мы реализовали пустую файловую систему, однако, если вы хотите компилировать сложные проекты с поддержкой _ViewImports.cshtml — то нужно реализовать более сложную структуру в памяти.
Теперь сгенерируем код из Blazor компонента C# код.

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

Из doc можно также получить диагностические сообщения о результатах генерации C# код из Blazor компонента.
Теперь мы получили код C# компонента. Нужно распарсить SyntaxTree, потом скомпилировать Assembly, загрузить её в текущий AppDomain и найти тип компонента. Так же как в предыдущем примере.

Осталось загрузить этот компонент в текущее приложение. Есть несколько способов, как это сделать, например, создав свой 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;
        }
    }
}

Заключение

Мы скомпилировали и запустили в браузере Blazor компонент. Очевидно, что полноценная компиляция динамического кода C# прямо внутри браузера может впечатлить любого программиста.

Но тут следует учитывать такие "подводные камни":

  • Для поддержки двунаправленного биндинга bind нужны дополнительные расширения и библиотеки.
  • Для поддержки async, await, аналогично подключаем доп. библиотеки
  • Для компиляции связанных Blazor компонентов потребуется двухэтапная компиляция.

Все эти проблемы уже решены и это тема для отдельной статьи.

Похожее
Nov 27, 2023
Author: Juldhais Hengkyawan
Use the Bogus library to generate and insert 1 million dummy product data into the SQL Server database We need to create 1 million dummy product data into the SQL Server database, which can be used for development or performance...
Jun 5, 2023
Author: Juan Alberto España Garcia
In this section, we’ll explore the world of unit testing in C# and .NET, learn what unit testing is, why it’s important, and the landscape of testing frameworks and tools available to developers. What is Unit Testing? Unit testing is...
Sep 23
Author: Tiago Martins
This is a behavioral pattern where the main goal is to split a complex job into multiple steps, each with a specific functionality. It’s really good for several types of architectures. For a system composed of various microservices with numerous...
Oct 26, 2023
Author: Genny Allcroft
Key strategic and tactical considerations to take when building a new product with the domain-driven design concepts in mind I have just finished reading Learning Domain-Driven Design by Vlad Khononov. It’s quite a short book (c. 300 pages) aiming to...
Написать сообщение
Тип
Почта
Имя
*Сообщение
RSS
Если вам понравился этот сайт и вы хотите меня поддержать, вы можете
10 историй, как «валят» айтишников на технических интервью
Функции и хранимые процедуры в PostgreSQL: зачем нужны и как применять в реальных примерах
Soft skills: 18 самых важных навыков, которыми должен владеть каждый работник
Семь итераций наивности или как я полтора года свою дебютную игру писал
Вопросы с собеседований, которые означают не то, что вы думаете
Путеводитель по репликации баз данных
5 приемов увеличения продуктивности разработчика
Топ 8 лучших ресурсов для практики программирования в 2018
Использование SQLite в .NET приложениях
GraphQL решает кучу проблем — рассказываем, за что мы его любим
LinkedIn: Sergey Drozdov
Boosty
Donate to support the project
GitHub account
GitHub profile