Начнем работу с фреймворком Blazor с создания приложения, которое использует унифицированную модель Blazor и собственно это рекомендуемая Microsoftом модель.
Если мы работаем через .NET CLI, то для создания проекта Blazor можно использовать шаблон blazor:
Например, определим для проекта каталог HelloBlazorApp, перейдем в консоли в этот каталог и создадим новый проект с помощью команды dotnet new blazor:
C:\Users\eugen>cd C:\dotnet\blazor\HelloBlazorApp C:\dotnet\blazor\HelloBlazorApp>dotnet new blazor Шаблон "Веб-приложение Blazor" успешно создан. Этот шаблон содержит технологии сторонних производителей, кроме Майкрософт. Дополнительные сведения см. в разделе https://aka.ms/aspnetcore/8.0-third-party-notices. Идет обработка действий после создания... Восстановление C:\dotnet\blazor\HelloBlazorApp\HelloBlazorApp.csproj: Определение проектов для восстановления... Восстановлен C:\dotnet\blazor\HelloBlazorApp\HelloBlazorApp.csproj (за 1,37 sec). Восстановление выполнено. C:\dotnet\blazor\HelloBlazorApp>
При создании проекта ему можно передать дополнительные параметры, которые характерны именно для Blazor. Но в данном случае пока рассмотрим проект, который создается по умолчанию.
Если мы работаем в среде Visual Studio, то здесь для создания проекта Blazor можно использовать шаблон проекта Blazor Web App:
Далее установим для проекта имя (например, в моем случае проект будет называться HelloBlazorApp) и установим каталог проекта:
Затем нам надо настроить ряд опций для проекта:
Здесь мы можем указать версию фреймворка .NET, которую будет использовать проект, и еще ряд опций:
Authentication Type: тип аутентификации, который будет применяться в проекте. Может принимать два значения: None (аутентификация по умолчанию не применяется) и Individual Accounts (в проект добавляется функционал системы аутентификации ASP.NET Identity)
Configure for HTTPS: стандартная опция, которая указывает, будет ли при запуске проекта применяться https
Interactive render mode: устанавливает режим рендеринга и может принимать следующие значения:
Server
: применяет рендеринг на стороне сервера
WebAssembly
: применяет рендеринг на стороне клиента
Auto
: при отправке содержимого клиенту применяется рендеринг на стороне сервера. После того, как клиент получит содержимое. и на стороне клиента активируется среда выполнения WebAssembly,
применяется рендеринг на стороне клиента
None
: рендеринг не применяется
Interactivity location: устанавливает, как применяется интерактивность. Может принимать два значения:
Per page/component
: интерактивность применяется к отдельным страницам/компонентам
Global
: интерактивность применяется глобально - для всего проекта
Include sample pages: позволяет включить в проект при создании некоторый базовый набор страниц Razor, которые позволяют протестировать функциональность
Do not use top-level statements: стандартная опция, которая указывает, будут ли применяться инструкции верхнего уровня
Оставим все опции по умолчанию и нажнем на кнопку создания проекта.
Вне зависимости, как мы создаем проект - через Visual Studio или .NET CLI, при использовании настроек по умолчанию после создания он будет иметь следующую структуру:
Можно отметить, что структура проекта Blazor похожа на проекты ASP.NET Core. По сути мы имеем дело с проектом приложения ASP.NET Core, в рамках которого разворачивается функциональнось фреймворка Blazor.
Основные элементы проекта:
Папка Properties хранит файл launchSettings.json с конфигурацией, применяемой при разработке, в частности, запуске проекта
Папка wwwroot хранит статические файлы. По умолчанию в ней находятся используемые файлы css, в частности, файлы фреймворка bootstrap.
Папка Components хранит компоненты - основные строительные блоки приложения. В ней по умолчанию имеется два каталога:
Папка Layout хранит компоненты Razor, которые определяют структуру приложения и ее отдельные части
MainLayout.razor хранит код компонента MainLayout, который определяет общий макет приложения.
NavMenu.razor хранит код компонента NavMenu, который определяет элементы навигации
Папка Pages содержит компоненты Razor, которые определяют визуальную часть приложения и его логику.
Counter.razor хранит код компонента Counter, суть которого в определение счетчика, значение которого увеличивается при нажатии на кнопку.
Error.razor хранит код компонента Error, который применяется для вывода сообщения об ошибке.
Weather.razor хранит код компонента Weather, который для теста выводит некоторый набор данных
Home.razor хранит код компонента Home (условно главный компонент).
_Imports.razor содержит подключения пространств имен с помощью директивы using, которые будут подключаться в компоненты Razor (файлы с расширением .razor).
App.razor содержит определение корневого компонента приложения, который представляет веб-страницу приложения.
Routes.razor определяет позволяет маршрутизацию между вложенными компонентами с помощью другого встроенного компонента Router.
Файл appsettings.json хранит конфигурацию приложения.
Файл Program.cs содержит класс Program, который представляет точку входа в приложение. В данном случае это стандартный для приложения ASP.NET Core класс Program, который запускает и конфигурирует хост, в рамках которого разворачивается приложение с Blazor.
Таким образом, проект Blazor уже содержит некоторую базовую типовую функциональность, который позволяе нам запустить проект и оценить работу фреймворка. Итак, запустим проект. Вначале мы увидим код компонента Home:
С помощью меню в левой части страницы мы можем перейти к другим компонентам. Например, перейдем к компоненту Counter:
Или к компоненту Weather, который выведет некоторые данные на страницу:
Теперь разберем, как вообще работает стандартный проект Blazor Web с типовым содержанием.
Прежде всего, чтобы задействовать функциональность Blazor, надо в файле Program.cs добавить необходимые сервисы и настроить обработку запросов. Например, возьмем типовой файл Program.cs из проекта по умолчанию:
using HelloBlazorApp.Components; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.Run();
Отметим прежде всего те моменты, которые характерны именно для Blazor, потому что в целом это стандартный файл приложения ASP.NET Core.
Для работы с компонентами Blazor прежде всего надо добавить сервисы, которые отвечают за рендеринг компонентов не сервере:
builder.Services.AddRazorComponents();
Кроме того, также добавляются сервисы, которые отвечают за интерактивный рендеринг компонентов не сервере:
builder.Services.AddInteractiveServerComponents();
Далее чтобы определить корневой компонент приложения, вызывается метод MapRazorComponents() (по умолчанию это компонент App из файла App.razor):
app.MapRazorComponents<App>() .AddInteractiveServerRenderMode();
Затем метод AddInteractiveServerRenderMode() настраивает режим интерактивного рендеринга на стороне сервера.
Благодаря этому при обращении к приложению используется компонент App, а при взаимодействии с компонентами обеспечивается интерактивность - мы можем взаимодействовать с компонентами.
Файл App.razor определяет компонент App, который по умолчанию является корневым компонентом всего приложения. Когда к любому компоненту приходит запрос, то именно этот компонент определяет ответ сервера. А отдельные компоненты располагаются внутри этого компонента.
Если мы посмотрим на файл App.razor, то увидим, что он представляет типичную страницу html с вкраплениями компонентов Razor:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link rel="stylesheet" href="bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="HelloBlazorApp.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> </body> </html>
В данном случае мы видим, что компонент App определяет каркас веб-страницы, которую увидит пользователь в своем браузере. С помощью компонента <HeadOutlet />
устанавливается заголовок веб-страницы. Компонент <Routes />
устанавливает конкретное содержимое веб-страницы на основе маршрутизации.
Но прежде всего следует обратить внимание на подключение внизу страницы скрипта _framework/blazor.web.js. Это автоматически подключаемый скрипт, который устанавливает подключение между браузером и сервером посредством SignalR и обеспечивает интерактивность.
Когда к приложению приходит запрос, то после обработки запроса приложение в ответ клиенту посылает код HTML-страницы, который скомпилирован на основе этого компонента Razor. Благодаря чему приложения Blazor могут использовать предварительный рендеринг на стороне сервера. Также отправляются подключаемые на странице статические файлы - файлы изображений, стилей CSS и скриптов JavaScript.
После того как браузер получил начальные файлы, создается DOM (Document Object Model) и выполняется файл blazor.web.js. Код из этого файла устанавливает с помощью SignalR устанавливает соединение с сервером. После этого приложение считается полностью загруженным и готово к взаимодействию с пользователем.
Для установки содержимого компонент App использует другой компонент - Routes из файла Routes.razor:
<Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> </Router>
Компонент Routes использует встроенный компонент Router, который добавляет возможность маршрутизации по вложенным компонентам. Его атрибут AppAssembly указывает на сборку, в которой следует искать запрошенные вложенные компоненты.
При запросе компонентов может быть две ситуации: запрошенный ресурс (компонент) найден и ресурс не найден. Если с запрошенным путем сопоставлен определенный компонент, то рендерится вложенный компонент Found. Если компоненты для определенного пути не найдены, то приложение просто отправляет ошибку 404.
Компонент Found содержит другой компонент - RouteView. Через атрибут RouteData он получает контекст маршрутизации, который будут использоваться при обработке запроса. А другой атрибут - DefaultLayout устанавливает компонент, который будет определять компоновку (layout) содержимого - в данном случае это компонент MainLayout из папки Layout.
В обоих случаях в компоненте App, как было описано выше будет использоваться компонент MainLayout, который определен в файле MainLayout.razor в папке Shared:
@inherits LayoutComponentBase <div class="page"> <div class="sidebar"> <NavMenu /> </div> <main> <div class="top-row px-4"> <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a> </div> <article class="content px-4"> @Body </article> </main> </div> <div id="blazor-error-ui"> An unhandled error has occurred. <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div>
Здесь мы видим, что этот компонент наследуется от класса LayoutComponentBase, который определяет некоторую базовую функцональность для подобных компонентов.
С помощью элемента <NavMenu />
добавляется компонент NavMenu из файла Shared/NavMenu.razor, который создает систему навигации. Благодаря чему
при загрузке приложения в левой части станицы мы можем переходить внутри приложения по набору ссылок.
Другой отличительной особенностью компонента MainLayout является использование свойства Body, унаследованного от класса LayoutComponentBase:
<div class="content px-4"> @Body </div>
Посредством свойства Body в определенном месте разметки будет рендерится выбранный для обработки запроса компонент. То есть именно за место вызова @Body
будет добавляться контент компонентов Home, Counter и Weather из папки Pages.
Основные комопненты, которые представляют отдельные ресурсы и к которым пользователь может осуществлять запросы, располагаются в папке Pages - это компоненты Home, Counter, Weather.
Возьмем самый простой компонент - Home:
@page "/" <PageTitle>Home</PageTitle> <h1>Hello, world!</h1> Welcome to your new app.
Директива @page "/"
указывает, что этот компонент будет сопоставляться с запросами к корню приложения, например, https://localhost:44304/
. То есть по сути
этот компонент можно рассматривать как главную страницу приложения.
Или другой комопнент - Counter:
@page "/counter" @rendermode InteractiveServer <PageTitle>Counter</PageTitle> <h1>Counter</h1> <p role="status">Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount++; } }
Данный компонент будет сопоставляться с запросами по пути "/counter", например, https://localhost:44304/counter
.
Далее идет важная директива @rendermode
@rendermode InteractiveServer
Эта директива устанавливает режим рендеринга для компонентов. Так, для компонента Counter установлен режим рендеринга InteractiveServer
. Это значит, что
при взаимодействии с компонентом обработка этого взаимодействия будет происходить на стороне сервера - благодаря соединению SignalR сервер будет получать данные о взаимодействии,
обработатывать их и отправлять ответ, на основе которого будет обновляться компонент на веб-странице.
Компонент counter также определяет некоторую логику на C#. В частности, он определяет переменную currentCount и метод IncrementCount, который увеличивает значение переменной:
private int currentCount = 0; private void IncrementCount() { currentCount++; }
В коде html мы можем установить привязку к переменным и методам компонента:
<p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
Метод привязывается к событию кнопки onclick, благодаря чему при нажатии на кнопку будет срабатывает метод IncrementCount, и пользователь увидит новое значение currentCount.
При этом следует отметить, что несмотря на то, что пользователь сможет увидеть в своем браузере и значение переменной currentCount и также сможет нажать на кнопку, которая сгенерирует событие нажатия, но вся логика C# срабатывает на сервере. В частности, когда на странице происходит событие (например, нажатие на кнопку), Blazor (blazor.web.js) перехватывает это событие и через соединение SignalR отправляет информацию о событии на сервер.
Сервер обрабатывает полученную информацию. И после обработки Blazor вычисляет минимально необходимое количество изменений, чтобы привести интерфейс веб-страницы в соответствии с изменившимся состоянием компонента, и отправляет информацию об этих изменениях обратно клиенту через SignalR-соединение. В итоге на клиенте будут изменяться только те части страницы, которые и должны быть изменены, перезагрузки страницы не будет.
Третий компонент - Weather демонстрирует пример потокового рендеринга, при котором после некоторой задержки компонент выводит некоторый набор данных в код html.