Поиск по сайту:

Как создавать клиентские веб-приложения C# с помощью Microsoft Blazor Web Framework


Blazor — это новая веб-платформа, позволяющая создавать полностью интерактивные веб-приложения с помощью C#. Используя магию среды выполнения .NET, скомпилированной для WebAssembly, вы даже можете полностью запустить Blazor на клиенте — вам больше не нужно использовать фреймворки JavaScript для создания приложений.

Что такое Блазор?

Blazor — это новейшая функция ASP.NET Core, веб-фреймворка Microsoft, которому уже 20 лет. Несмотря на свою старость, Microsoft по-прежнему поддерживает его, и ASP.NET постоянно совершенствуется вместе с C# и средой выполнения .NET в целом.

Страницы Razor и старая модель MVC ASP.NET позволяют создавать управляемые данными HTML-страницы с помощью C#. Это было всегда, но чего всегда не хватало, так это интерактивности. Довольно сложно создать веб-приложение, когда вам нужно перезагрузить страницу, чтобы просмотреть изменения.

Итак, Blazor решает эту проблему, напрямую связывая сервер и клиент с библиотекой, которая может изменять DOM во время выполнения, и привносит многие методы жизненного цикла компонентов и обработчики обновлений, которые вы ожидаете от фреймворка, предназначенного для конкуренции с React.

А благодаря минималистичному синтаксису страницы Razor кодирование в Blazor упрощается:

У Blazor есть два основных режима работы, самым крутым из которых является Blazor Client, который сам упаковывает .NET в структуру, которую можно полностью запустить на WebAssembly. WASM — это, по сути, MSIL для Интернета; это облегченный двоичный формат инструкций, который может быть скомпилирован на любом языке, даже на «настольных» языках, таких как C++ и Rust.

Так как производительность с этим? Ну, в настоящее время он использует интерпретатор, так как над собственной компиляцией AOT для .NET все еще ведется работа. Но со временем это улучшится; Основным недостатком Blazor Client является более длительное время загрузки — библиотеки DLL могут быть большими файлами по веб-стандартам, и все они должны быть загружены, чтобы приложение заработало. Однако у крупных фреймворков JS также есть эта проблема, и это основная причина популярности рендеринга на стороне сервера.

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

Если вы хотите использовать более традиционную модель хостинга, аналогичную рендерингу на стороне сервера React (SSR), этот вариант также доступен для вас. Это более производительно и позволяет запускать код на доверенном сервере.

Для этого Blazor Server может подключаться к клиенту с помощью подключения SignalR, которое представляет собой всего лишь причудливую оболочку над WebSockets, которая также может при необходимости вернуться к HTTP. Это создает большую нагрузку на сервер ASP.NET, чем традиционный веб-хостинг, поскольку ему необходимо повторно отображать и повторно отправлять каждое отдельное изменение HTML всем подключенным клиентам. Если у вас подключены тысячи людей, это может стать проблемой при масштабировании.

Хотя Blazor в настоящее время является веб-платформой, Microsoft также создает Blazor Desktop, который во многом похож на Electron, упаковывая веб-интерфейсы в настольные приложения, а также Blazor Native, который сможет отображать собственные пользовательские интерфейсы в мобильных операционных системах. Microsoft, кажется, рассматривает это как свою следующую модель программирования приложений для создания интерактивных интерфейсов на любой платформе.

Гибридный режим

Blazor также можно использовать в «гибридном режиме» с помощью предварительного рендеринга. Blazor WASM может отображать необработанный HTML-код во время загрузки платформы, что обычно используется для полосы загрузки или вращающегося колеса. Но если вы размещаете приложение из ASP.NET, вы можете отображать страницы Razor с сервера еще до того, как отправите приложение клиенту.

Это работает отлично, с той загвоздкой, что страница не станет интерактивной, пока Blazor не загрузится. Обычно это занимает всего секунду, так что на самом деле это не проблема, за исключением некоторых странностей с двойной перезагрузкой данных.

С этой настройкой вы получаете лучшее из обоих миров — ваше приложение загружается быстро, без загрузочного экрана, и молниеносно перемещаться по нему. Эта функция все еще находится в разработке, и у нее есть много особенностей, но начиная с .NET 6, предварительная версия 6, она работает, и вы можете прочитать больше в руководствах Джона Хилтона, части 1 и 2.

Одна из особенностей заключается в том, что приложения WASM, использующие API, должны будут иметь две реализации каждой службы, на клиенте и на сервере, чтобы сервер мог разговаривать сам с собой для получения информации при предварительном рендеринге. Это приводит к некоторому миганию, когда WASM загружается и выполняет свою собственную выборку данных, но это можно исправить в .NET 6 — в этом руководстве показано, как сохранить состояние компонента, чтобы ваше приложение плавно переходило от фальшивой «страницы загрузки» к реальной приложение.

Настройка Blazor

К счастью, Visual Studio предоставляет генераторы, и вам не нужно настраивать все это самостоятельно. Откройте Visual Studio и создайте новый проект. Найдите «Blazor» и выберите либо Blazor WebAssembly, либо Blazor Server.

Здесь мы воспользуемся Blazor WebAssembly, так как Blazor Server проще и просто включает в себя и клиент, и сервер в одном проекте.

Дайте ему имя и выберите свой фреймворк. Следует отметить, что Blazor WebAssembly технически вообще не нуждается в сервере; вы можете просто разместить его как статический веб-сайт на NGINX или даже в корзине AWS S3. Но если вы подключаетесь к API для общения с базой данных, вы также можете сделать этот API связанным с клиентом, так как вы можете обмениваться кодом между ними и, конечно же, использовать один и тот же язык.

Решение будет состоять из трех проектов, клиентского приложения, API-интерфейса сервера ASP.NET и веб-узла для клиента, а также общей библиотеки между ними.

Если вы нажмете «Пуск», вы увидите, что ASP.NET загружается и обслуживает веб-страницу. Если вы используете Blazor Server, он будет подключаться к ASP.NET через SignalR для обработки взаимодействий.

Разумеется, Blazor WebAssembly будет отправлять запросы на сервер только в том случае, если приложение делает это явно.

Экскурсия по Blazor Environment

Давайте пройдемся по приложению. Начиная с клиента, основной точкой входа является Program.cs, который создает WebAssemblyHost, добавляет корневой компонент App.razor , а затем создает и запускает приложение.

Технически точкой входа является wwwroot/index.html, который загружает файл JavaScript Blazor, показывает страницу загрузки во время инициализации Blazor и показывает сообщение об ошибке, если это не так.

App.razor обрабатывает маршрутизацию страниц с помощью компонента Router . Это берет сборку и загружает все страницы, отмеченные атрибутом @page name . Вы можете узнать больше о маршрутизации на страницах Razor здесь.

Маршрутизатор загружает компонент MainLayout.razor, который расширяет LayoutComponentBase. Это загружает компонент NavMenu.razor рядом с телом и отображает ваше приложение.

Большой! Что касается сервера, то это стандартное приложение ASP.NET. Он создает построитель узлов и добавляет службы, настроенные в Startup.cs. В частности, он настраивает его для отладки WASM, обслуживает файлы Blazor Framework и настраивает собственные страницы и контроллеры Razor для обслуживания содержимого JSON.

Если бы это было просто приложение Blazor Server, ему не понадобился бы этот отдельный API, и оно могло бы просто получать данные из внутренней службы. Но поскольку это не так, необходимо предоставить эту службу по сети в форме ASP.NET ApiController, который определяет методы обработчика для различных действий HTTP.

Модели для этого совместно используются клиентом и сервером в общем проекте.

Исправление ужасного выбора цвета Microsoft

Прежде чем сесть за код, нам нужно кое-что исправить. По какой-то причине Microsoft выбрал цвет по умолчанию для директив C# HTML на страницах Razor, используя, пожалуй, наихудшую цветовую комбинацию, которую только можно себе представить, — светло-фиолетовый текст на светло-коричневом фоне.

К счастью, вы можете изменить его в меню «Инструменты» > «Параметры»:

В разделе «Среда» > «Шрифты и цвета» выберите «Директива Razor» и измените цвет фона на что-то более разумное. Вы, вероятно, все еще хотите, чтобы он выделялся, поэтому я просто выбрал чистый черный, который будет заметным, но ненавязчивым.

Вам также потребуется изменить «HTML Server-Side Script» (технически это устаревшее название, учитывая существование Blazor WebAssembly).

И с помощью этого простого исправления вы можете избавить свои глазные яблоки от боли.

Обслуживание динамического контента

Последний файл, который вы хотите проверить, находится на клиенте, FetchData.razor. Именно здесь клиент фактически использует серверный API со следующей управляемой данными страницей:

Это довольно сложная страница Razor, так что это хороший пример для нас.

Для начала он указывает ручное переопределение маршрута страницы с помощью @page \/fetchdata\. Затем он импортирует общие модели из общего проекта, а также использует внедрение зависимостей, чтобы предоставить ему HttpClient, настроенный в Program.cs.

Мы перейдем к самому низу, чтобы сначала объяснить это — блок @code содержит фактический код C# для этой страницы. В нем есть закрытая переменная с именем forecasts. Изначально это значение null, но когда страница инициализируется, она отправляет веб-запрос на получение данных из API и их десериализацию.

Когда эта переменная обновляется, Blazor обнаруживает изменение и повторно отображает страницу. Сама страница представляет собой нечто среднее между ними, которое начинается с заголовка и описания, а затем содержит оператор @if . Это условный HTML-код C#, и в данном случае он используется для отображения загружаемого текста, когда Blazor отправляет запрос к API. Затем, после завершения и повторного рендеринга, прогнозы не будут нулевыми, и он отобразит таблицу, которая будет выполнять элемент @foreach в списке, и отобразит строка таблицы.

Используя это, вы можете визуализировать динамический контент из ваших моделей. Для поддержки более сложных операций CRUD и более динамичных пользовательских интерфейсов вам потребуется использовать кнопки и вводы. Вы можете использовать теги <button> с атрибутом @onclick , который принимает ссылку на метод.

Вы можете использовать EventCallbacks для передачи события родительским компонентам. Вы также можете использовать лямбда-выражения для встроенной обработки событий, если метод короткий:

<button @onclick="@(e => heading = "New heading!!!")">
    Update heading
</button>

Существует несколько дополнительных аргументов событий, которые вы можете передать этим функциям-обработчикам, включая местоположение мыши, события клавиатуры и события касания.

Если вы хотите реализовать окно поиска, у Blazor есть привязки для этого. Однако, если вы хотите, чтобы он выполнял поиск автоматически при нажатии клавиши ввода, а не при каждом нажатии клавиши, вам необходимо привязать событие @onkeydown к функции, которая принимает объект KeyboardEventArgs, и вручную проверять, был ли нажат Enter.

Однако вы не можете получить входное значение из KeyboardEventArgs, поэтому это приводит к беспорядку в поле ввода, где вы должны связать onkeydown и oninput, установить строковое значение, а затем использовать эту строку в onkeydown.

<input type="text" @onkeydown="@Enter" @oninput="@(ui => { searchValue = (string) ui.Value;})" />

@code {
    public void Enter(KeyboardEventArgs e)
    {
        if (e.Code == "Enter" || e.Code == "NumpadEnter")
        { 
             //do stuff
        }
    } 
}

Как работает стайлинг?

Чрезвычайно просто. Существует главный файл site.css , но для стилей на уровне компонентов просто создайте файл .razor.css для каждой страницы Razor:

index.razor
index.razor.css

Blazor автоматически создаст файл CSS с ограниченной областью действия, который применяет настройки с каждой страницы Razor только к этой странице Razor:

<link href="BlazorWebAssemblyTest.Client.styles.css" rel="stylesheet" />

/* /Pages/Counter.razor.rz.scp.css */
h1[b-3xxtam6d07] {
    color: brown;
}

Заставить препроцессоры, такие как SASS, работать немного сложнее, но вполне выполнимо.

Жизненный цикл компонента Blazor

Как и React, Blazor также имеет привязки жизненного цикла компонентов. Согласно этой диаграмме из Blazor University, фантастического ресурса для изучения Blazor, среда выполнения Blazor вызывает их в следующем порядке:

SetParametersAsync и OnParametersSet вызываются всякий раз, когда выполняется рендеринг родительского компонента. Здесь также вызывается OnInitializedAsync, если это происходит в первый раз. Затем для каждого взаимодействия с пользователем событие может вызывать StateHasChanged, которое проверяет, должно ли оно отображаться, и запускает этот процесс обновления снова и снова.

Здесь слишком много подробностей, поэтому вам следует прочитать руководство от Blazor University, которое объясняет их все, а также странные взаимодействия с методами async/await. Чтобы отображать как можно больше содержимого как можно быстрее, Blazor может запускать StateHasChanged и выполнять визуализацию дважды: один раз сразу после возврата объекта ожидания задачи и один раз по завершении.

Как Blazor работает с JavaScript?

Независимо от того, используете ли вы Blazor Server или Blazor Desktop, у вас есть полная совместимость с JavaScript, например вызов функций JS из управляемого кода:

private async Task ConvertArray()
{
    text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}

И вызов .NET из JS:

DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

На самом деле вы можете использовать все пакеты NPM с Blazor, хотя в большинстве случаев вам следует предпочесть пакет NuGet, если это возможно.

Навигация пользователя вокруг

Одной из наиболее важных проблем с веб-приложениями, особенно с одностраничными веб-приложениями (SPWA), является маршрутизация. Каждая страница Razor имеет маршрут по умолчанию в соответствии с ее именем, но вы можете переопределить его, а также добавить несколько маршрутов страниц на каждую страницу Razor. Маршрутизатор будет сопоставлять каждый из них отдельно и по-прежнему будет обслуживать один и тот же файл.

Однако, если вы хотите обслуживать различный контент в зависимости от того, по какому URL вы находитесь, все становится немного сложнее. Самым простым решением было бы просто определить маршрут @page для каждой строки, которую вы хотите добавить. Это работает в первый раз, но из-за того, как Blazor обрабатывает обновления, он не будет обновлять страницу при переходе по новому URL-адресу, поскольку параметры не изменились. Вы по-прежнему можете использовать это для определения нескольких вариантов написания одной и той же страницы, если эти страницы не ссылаются друг на друга.

Решение проблемы SPWA заключается в использовании маршрутов на основе параметров всякий раз, когда вы хотите перехватить несколько вариантов и связать их между собой. Параметры могут быть определены в скобках:

Эта конкретная настройка предназначена для корневой страницы. Если вы обеспокоены тем, что он будет соответствовать другим страницам, таким как /error, поскольку он настроен на соответствие чему-либо после root, не беспокойтесь, Blazor будет соответствовать другим страницам, которые строго определены, прежде чем вернуться к использованию маршруты на основе параметров. Однако у вас не может быть нескольких маршрутов параметров на одном и том же корневом маршруте без получения неоднозначной ошибки маршрута.

В моем случае я хотел направить пользователя на новую страницу с аргументом. Вы можете сделать это, обратившись к NavigationManager и вызвав NavigateTo:

NavManager.NavigateTo("/search/" + searchValue);

Если вам нужна более продвинутая маршрутизация, вам нужно заглянуть в Query Parameters.

Ручное обновление страницы при изменении URL

Одна из особенностей Blazor заключается в том, что он не запускает обновление при изменении вашего URL-адреса, даже если у вас есть зависящий от него код с отслеживанием состояния, поэтому вам придется обновлять его вручную. К счастью, у NavigationManager есть делегат на случай изменения местоположения, и вы можете привязать к нему функцию.

NavigationManager.LocationChanged += HandleLocationChanged;

Вы захотите использовать InvokeAsync с функцией, которая обновляет любой объект состояния, от которого зависит ваше приложение. Затем вам нужно будет вызвать StateHasChanged, чтобы запустить обновление.

Вы также можете вызвать OnParametersSetAsync отсюда, если ваш код также зависит от других параметров URL и вы не хотите копировать/вставлять.

Библиотеки Blazor

Одним из преимуществ Blazor является то, что это не что-то совершенно новое — он построен на основе 20-летней основы ASP.NET, поэтому он уже поставляется с разнообразной экосистемой библиотек. Мы не будем вдаваться в их список здесь, потому что на GitHub уже есть список awesome-blazor, который охватывает все.

Тем не менее, одна вещь, которую вы обязательно должны использовать, — это готовая библиотека компонентов. Есть пара бесплатных, но RadZen — один из самых популярных, и он полностью с открытым исходным кодом. Blazorise — еще один отличный инструмент, хотя он бесплатен только для некоммерческих продуктов.

В любом случае использование готовых компоновок компонентов и последующая их ручная стилизация позволят быстро создавать прототипы и значительно сократить время разработки. Мы настоятельно рекомендуем их.

Будущее Блейзора

Blazor — это действительно уникальная платформа среди океана клонов React на основе JavaScript, и благодаря поддержке Microsoft у нее большое будущее. Ранее мы упоминали, что у Microsoft были планы на Blazor Desktop, который появится в конце 2021 года.

Blazor Desktop будет работать так же, как и Electron, где он запускает ваше приложение в контейнере Chromium с некоторыми привязками для функций рабочего стола. За исключением того, что вместо использования Chromium Blazor Desktop будет использовать собственные веб-представления и запускать код .NET непосредственно на компьютере.

В то же время вы действительно можете получить Blazor на рабочем столе с помощью Electron.NET, и это работает на удивление хорошо. Все, что вам нужно сделать, это установить его и добавить Electron в качестве службы ASP.NET. Вы также можете вызывать нативные функции Electron из C#.