Интерактивность Blazor в .NET 9 и .NET 10: полное руководство
Если вы создавали веб-приложения с помощью Blazor в течение последних нескольких лет, вы знаете, что эта платформа прошла долгий путь. То, что началось с выбора между Blazor Server и Blazor WebAssembly, превратилось в унифицированную гибкую модель рендеринга, которая позволяет вам выбрать правильную стратегию интерактивности для каждого компонента вашего приложения.
В .NET 8 мы получили фундаментальный сдвиг — введение режимов рендеринга и статического рендеринга на стороне сервера (SSR) по умолчанию. Теперь .NET 9 и .NET 10 построены на этой основе с усовершенствованиями, которые делают работу разработчиков более удобной, а работу конечных пользователей — более быстрой.
В этом посте я хочу познакомить вас с полной картиной интерактивности Blazor в ее нынешнем виде: как работают режимы рендеринга, что дает потоковая SSR, как улучшенная навигация и обработка форм меняют игру и что нового в последних выпусках. Если вы планируете новый проект Blazor или думаете об обновлении, мне хотелось бы иметь это руководство, когда начал во всем этом разбираться.
Быстрый взгляд назад: как мы сюда попали
До .NET 8 вам приходилось использовать модель хостинга на уровне проекта. Blazor Server означал, что все работало на сервере через соединение SignalR. Blazor WebAssembly означал, что все работает в браузере. У каждого были свои компромиссы, и смешивать их было болезненно.
.NET 8 изменил правила игры, представив единый шаблон проекта — веб-приложение Blazor — который объединяет обе модели. Ключевой концепцией являются режимы рендеринга: вы решаете для каждого компонента, как он должен отображаться и где происходит интерактивность. По умолчанию выбран статический SSR, что означает, что компоненты визуализируются на сервере и отправляют в браузер обычный HTML — без SignalR, без WebAssembly, только быстрый HTML.
В .NET 9 эти концепции были усовершенствованы, улучшены условия для разработчиков и оптимизирована производительность. .NET 10 идет дальше благодаря улучшенной обработке повторного подключения, сохранению состояния компонента в разных режимах рендеринга и улучшениям доставки самого скрипта Blazor.
Давайте разберем все это.
Режимы рендеринга в .NET 9
В основе современного Blazor лежит концепция режимов рендеринга. Есть четыре режима, о которых вам следует знать:
1. Статический SSR (по умолчанию)
Когда вы создаете новое веб-приложение Blazor, компоненты по умолчанию отображаются статически на сервере. Сервер обрабатывает компонент Razor, генерирует HTML и отправляет его в браузер. Нет постоянного соединения, нет среды выполнения WebAssembly — только традиционный запрос/ответ.
Это идеально подходит для страниц с большим количеством контента, информационных панелей, которые в основном отображают данные, или любой страницы, где вам не требуется взаимодействие в реальном времени.
@page "/products"
<h1>Our Products</h1>
@foreach (var product in products)
{
<div class="product-card">
<h3>@product.Name</h3>
<p>@product.Description</p>
<span class="price">@product.Price.ToString("C")</span>
</div>
}
@code {
private List<Product> products = new();
protected override async Task OnInitializedAsync()
{
products = await ProductService.GetAllAsync();
}
}
Этот компонент отображает на сервере, создает HTML и все. Нет постоянной связи. Быстрая начальная загрузка, отлично подходит для SEO, минимальное использование ресурсов сервера.
2. Интерактивный серверЕсли вам нужна интерактивность в реальном времени — обработка нажатий кнопок, обработка ввода пользователя, динамическое обновление пользовательского интерфейса — вы можете выбрать режим интерактивного сервера. При этом устанавливается соединение SignalR между браузером и сервером, и обновления пользовательского интерфейса происходят через это соединение.
[[[ТОК_1]]]
Директива @rendermode InteractiveServer — это все, что нужно. Компонент сначала отображается на сервере, а затем устанавливается соединение SignalR для обработки последующих взаимодействий. Обработчики событий C# выполняются на сервере, а различия пользовательского интерфейса отправляются в браузер.
Когда его использовать: Когда вам нужна интерактивность, вашим компонентам необходим доступ к ресурсам на стороне сервера (базам данных, API, файловым системам), и вам нужна быстрая начальная загрузка, не дожидаясь загрузки WebAssembly.
3. Интерактивная веб-сборка
Если вам нужна интерактивность без поддержания соединения с сервером, Interactive WebAssembly запускает логику вашего компонента непосредственно в браузере, используя среду выполнения .NET WebAssembly.
[[[ТОК_3]]]
Компромисс: существует первоначальная стоимость загрузки среды выполнения .NET и ваших сборок. Но после загрузки компонент полностью запускается в браузере без каких-либо обращений к серверу для взаимодействия с пользовательским интерфейсом.
Когда его использовать: для высокоинтерактивных компонентов, где важна задержка (например, редакторы форматированного текста, инструменты рисования, фильтрация в реальном времени), когда вы хотите снизить нагрузку на сервер или когда вы создаете прогрессивное веб-приложение (PWA).
4. Интерактивный авто
Это прагматичная золотая середина и одна из моих любимых особенностей. Interactive Auto начинается с рендеринга на стороне сервера через SignalR для первой загрузки, затем автоматически загружает среду выполнения WebAssembly в фоновом режиме. При последующих посещениях компонент запускается на WebAssembly.
[[[ТОК_4]]]
Это дает вам лучшее из обоих миров: быстрый первый рендеринг (без ожидания загрузки WASM) и, в конечном итоге, компонент запускается на стороне клиента. Пользователь не замечает перехода.
Когда его использовать: Когда вам нужна как быстрая начальная загрузка, так и возможное выполнение на стороне клиента. Это отличный выбор по умолчанию для многих интерактивных компонентов.
Потоковое SSR: лучшее из обоих миров
Потоковая SSR — одна из тех функций, которая звучит просто, но существенно влияет на воспринимаемую производительность. Вот идея: вместо того, чтобы ждать загрузки всех ваших данных перед отправкой любого HTML, сервер немедленно отправляет оболочку страницы, а затем транслирует обновления контента по мере того, как данные становятся доступными.
@page "/reports"
@attribute [StreamRendering]
<h1>Monthly Reports</h1>
@if (reports is null)
{
<div class="loading-spinner">
<p>Loading reports...</p>
</div>
}
else
{
<table class="table">
<thead>
<tr>
<th>Month</th>
<th>Revenue</th>
<th>Growth</th>
</tr>
</thead>
<tbody>
@foreach (var report in reports)
{
<tr>
<td>@report.Month</td>
<td>@report.Revenue.ToString("C")</td>
<td class="@(report.Growth >= 0 ? "text-success" : "text-danger")">
@report.Growth.ToString("P1")
</td>
</tr>
}
</tbody>
</table>
}
@code {
private List<MonthlyReport>? reports;
protected override async Task OnInitializedAsync()
{
// This might take a couple of seconds
reports = await ReportService.GetMonthlyReportsAsync();
}
}
С атрибутом [StreamRendering] происходит следующее:
- Сервер немедленно отправляет HTML с помощью счетчика загрузки.
OnInitializedAsyncзапускается и извлекает данные.- Когда данные поступают, сервер передает обновленный HTML (таблицу) в браузер.
- Браузер исправляет DOM без полной перезагрузки страницы.
Пользователь видит страницу мгновенно с индикатором загрузки, затем содержимое заполняется. Никакой инфраструктуры JavaScript не требуется. Нет подключения к WebSocket. Просто умное использование потоковой передачи HTTP.Когда использовать: Любая статическая страница SSR, которая извлекает данные во время рендеринга. Страницы со списком продуктов, информационные панели, отчеты — везде первоначальная загрузка данных может занять более нескольких сотен миллисекунд.
Когда НЕ использовать: Если данные загружаются очень быстро (менее 100 мс), затраты на потоковую передачу того не стоят. Кроме того, потоковая SSR не обеспечивает постоянной интерактивности — для этого вам все равно нужен интерактивный режим рендеринга.
Улучшенная навигация и обработка форм
Одним из тонких, но мощных улучшений современного Blazor является улучшенная навигация. По умолчанию Blazor перехватывает клики по внутренним ссылкам и отправку форм, получая новое содержимое страницы через fetch и исправляя DOM вместо полной навигации по браузеру.
Это означает, что навигация между статическими страницами SSR ощущается как SPA — полная страница не мигает, положение прокрутки может быть сохранено, а работа очень плавная.
Как это работает
При загрузке скрипта Blazor (blazor.web.js) он автоматически перехватывает клики по внутренним ссылкам. Вместо традиционной навигации в браузере он:
- Делает запрос
fetchк целевому URL. - Получает ответ в формате HTML.
- Объединяет новый контент с существующим DOM.
- Обновляет URL-адрес и историю браузера.
Вам не нужно ничего делать, чтобы включить эту функцию — она включена по умолчанию. Но вы можете это контролировать:
[[[ТОК_11]]]
Улучшенная обработка форм
Формы обрабатываются одинаково. Когда вы используете EditForm или стандартный элемент <form> с обработкой формы Blazor, отправки перехватываются и обрабатываются через fetch:
@page "/contact"
<EditForm Model="contactModel" OnValidSubmit="HandleSubmit" FormName="contact" Enhance>
<DataAnnotationsValidator />
<div class="mb-3">
<label for="name">Name</label>
<InputText id="name" @bind-Value="contactModel.Name" class="form-control" />
<ValidationMessage For="() => contactModel.Name" />
</div>
<div class="mb-3">
<label for="email">Email</label>
<InputText id="email" @bind-Value="contactModel.Email" class="form-control" />
<ValidationMessage For="() => contactModel.Email" />
</div>
<div class="mb-3">
<label for="message">Message</label>
<InputTextArea id="message" @bind-Value="contactModel.Message" class="form-control" />
<ValidationMessage For="() => contactModel.Message" />
</div>
<button type="submit" class="btn btn-primary">Send</button>
</EditForm>
@code {
[SupplyParameterFromForm]
private ContactModel contactModel { get; set; } = new();
private async Task HandleSubmit()
{
await ContactService.SubmitAsync(contactModel);
contactModel = new ContactModel();
}
}
Атрибут Enhance в EditForm указывает Blazor перехватить отправку формы. Форма отправляется на сервер, сервер обрабатывает ее и повторно отображает страницу, а обновленный HTML-код передается обратно — и все это без полноценной навигации по странице. Он кажется интерактивным, но полностью визуализируется на сервере.
Обратите внимание на атрибут [SupplyParameterFromForm] — именно так Blazor привязывает опубликованные данные формы к вашей модели в статических сценариях SSR. Это мост между традиционными формами и компонентной моделью Blazor.
Постраничная и покомпонентная интерактивность
Одним из наиболее мощных аспектов новой модели является то, что вы можете смешивать режимы рендеринга в одном приложении. Ваша страница со списком продуктов может быть статической SSR, ваша корзина покупок может быть Interactive Server, а конфигуратор вашего продукта может быть Interactive WebAssembly — и все это в одном приложении.
Настройка режимов рендеринга на уровне компонента
Вы можете установить режим рендеринга непосредственно на компоненте с помощью директивы @rendermode:
@* This component is interactive via Server *@
@rendermode InteractiveServer
<h3>Live Chat</h3>
<!-- chat UI here -->
Или вы можете установить его при использовании компонента из родителя:
@page "/product/{Id:int}"
<h1>@product?.Name</h1>
<p>@product?.Description</p>
<!-- This child component gets its own interactive render mode -->
<ProductConfigurator Product="product" @rendermode="InteractiveWebAssembly" />
<!-- This stays static -->
<ProductReviews ProductId="Id" />
@code {
[Parameter] public int Id { get; set; }
private Product? product;
protected override async Task OnInitializedAsync()
{
product = await ProductService.GetByIdAsync(Id);
}
}
В этом примере сама страница является статической SSR. Компонент ProductConfigurator работает на WebAssembly для обеспечения богатой интерактивности на стороне клиента. Компонент ProductReviews остается статическим, поскольку он просто отображает данные.
Глобальная настройка режимов рендеринга
Если вы хотите, чтобы все страницы были интерактивными по умолчанию, вы можете установить режим рендеринга на корневом уровне в App.razor:
<Routes @rendermode="InteractiveServer" />
```Это делает каждую страницу интерактивной посредством рендеринга на сервере. Отдельные компоненты все равно могут переопределить это, если это необходимо.
### Важные правила, которые следует запомнить
При смешивании режимов рендеринга следует учитывать несколько ограничений:
- **Дочерний компонент не может иметь более интерактивный режим рендеринга, чем его родительский компонент.** Если родительский компонент является статическим, дочерние элементы могут быть статическими или интерактивными. Но если родительским элементом является Interactive Server, дочерним элементом не может быть Interactive WebAssembly (он должен быть Server или Auto).
- **Интерактивные компоненты не могут напрямую использовать службы с ограниченной областью действия из статических родительских элементов.** Если компонент работает на WebAssembly, он не может напрямую обращаться к серверным экземплярам `DbContext` — вам понадобится уровень API.
— **Состояние не переключается автоматически между режимами рендеринга.** Если компонент выполняет предварительную отрисовку на сервере, а затем переключается на WebAssembly, вам необходимо явно обрабатывать сохранение состояния — подробнее об этом в разделе .NET 10.
## Что нового в .NET 10
.NET 10 продолжает подход постепенного улучшения, уделяя особое внимание надежности и опыту разработчиков. Вот основные моменты интерактивности Blazor:
### Улучшенный процесс повторного подключения
Если вы использовали режим интерактивного сервера в рабочей среде, вы, вероятно, сталкивались с наложением повторного подключения — моментом, когда соединение SignalR разрывается и пользователь видит сообщение «Повторное подключение...». В .NET 10 эта возможность становится значительно лучше.
Логика повторного подключения теперь более разумна в отношении повторных попыток. Вместо фиксированного интервала повторных попыток он использует стратегию отсрочки, которая адаптируется к ситуации. Пользовательский интерфейс во время повторного подключения также стал более совершенным, и у вас есть лучшие возможности для настройки:
```razor
<!-- In your App.razor or layout -->
<div id="components-reconnect-modal">
<div class="reconnect-visible">
<p>Connection lost. Attempting to reconnect...</p>
<div class="spinner"></div>
</div>
<div class="reconnect-failed">
<p>Could not reconnect to the server.</p>
<button onclick="location.reload()">Reload</button>
</div>
<div class="reconnect-rejected">
<p>Your session has expired.</p>
<a href="/">Return to Home</a>
</div>
</div>
Платформа теперь также более агрессивно пытается переподключиться при временных сбоях и может более надежно восстанавливать состояние схемы. Это означает меньшее количество моментов «пожалуйста, перезагрузите страницу» для ваших пользователей.
Постоянное состояние компонента в разных режимах рендеринга
Это большой вопрос. В .NET 9, когда компонент выполняет предварительную отрисовку на сервере, а затем переходит в WebAssembly (или переключается между режимами отрисовки), состояние компонента теряется. Компонент эффективно повторно инициализируется, что может привести к дублированию выборок данных и мерцанию пользовательского интерфейса.
В .NET 10 улучшена служба PersistentComponentState для более плавной работы при переходах между режимами рендеринга:
@page "/weather"
@rendermode InteractiveWebAssembly
@inject PersistentComponentState ApplicationState
<h1>Weather Forecast</h1>
@if (forecasts is null)
{
<p>Loading...</p>
}
else
{
@foreach (var forecast in forecasts)
{
<div class="forecast-card">
<h4>@forecast.Date.ToShortDateString()</h4>
<p>@forecast.Summary — @forecast.TemperatureC°C</p>
</div>
}
}
@code {
private List<WeatherForecast>? forecasts;
private PersistingComponentStateSubscription persistingSubscription;
protected override async Task OnInitializedAsync()
{
persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
if (!ApplicationState.TryTakeFromJson<List<WeatherForecast>>(
"weather-forecasts", out var restored))
{
// Data wasn't persisted from prerendering — fetch it
forecasts = await Http.GetFromJsonAsync<List<WeatherForecast>>("api/weather");
}
else
{
forecasts = restored;
}
}
private Task PersistData()
{
ApplicationState.PersistAsJson("weather-forecasts", forecasts);
return Task.CompletedTask;
}
public void Dispose()
{
persistingSubscription.Dispose();
}
}
Благодаря улучшениям в .NET 10 этот шаблон работает более надежно. Состояние, сериализованное во время предварительного рендеринга, правильно доступно, когда компонент инициализируется в интерактивном режиме, что позволяет избежать двойной выборки данных и кратковременного мерцания, которое могут видеть пользователи.
Blazor Script как статический веб-ресурс
В предыдущих версиях файл JavaScript Blazor (blazor.web.js) обслуживался из внутренней конечной точки платформы. В .NET 10 он поставляется как статический веб-ресурс. Это может показаться небольшим изменением, но оно имеет практические преимущества:– Улучшенное кеширование. Статические веб-ресурсы получают правильные заголовки кэша и URL-адреса с отпечатками пальцев, поэтому браузеры кэшируют их более эффективно.
- Удобство для CDN: поскольку это обычный статический файл, CDN могут кэшировать и обслуживать его из периферийных расположений. – Сжатие. Статическое сжатие веб-ресурсов применяется автоматически, уменьшая размер передаваемого сценария.
Вам не нужно менять способ ссылки на него — платформа автоматически обрабатывает обновленный путь.
Лучшие практики: выбор правильного режима рендеринга
После работы со всеми этими режимами в нескольких проектах, вот моя схема принятия решений:
Начните со статического SSR
Сделайте статический SSR по умолчанию. Большинство страниц в большинстве приложений предназначены в первую очередь для отображения данных. Страницы продуктов, сообщения в блогах, профили пользователей, страницы настроек — им не нужна интерактивность в реальном времени. Статический SSR обеспечивает наилучшую производительность, минимальное использование ресурсов и простейшую мысленную модель.
Добавляйте интерактивность только там, где это необходимо
Определите конкретные компоненты, которые должны реагировать на взаимодействия с пользователем в режиме реального времени. Кнопка «Мне нравится», виджет чата, интерфейс с возможностью перетаскивания — все это требует интерактивности. Но страница вокруг них, вероятно, этого не делает.
Используйте интерактивный автоматический режим в качестве интерактивного режима
Если вам действительно нужна интерактивность, Interactive Auto часто является лучшим выбором по умолчанию. Он обеспечивает быструю начальную загрузку (рендеринг на сервере) с последующим выполнением на стороне клиента (WebAssembly). Пользователь получает лучшее из обоих миров, а вы пишете свой код один раз.
Резервный интерактивный сервер для особых случаев
Используйте Interactive Server, когда:
- Вашему компоненту необходим прямой доступ к ресурсам сервера (базам данных, файловой системе, внутренним API).
- Размер загрузки WebAssembly вызывает беспокойство, и вы не можете использовать Auto.
- Вам необходимо, чтобы компонент всегда запускался на сервере из соображений безопасности (например, для обработки конфиденциальных данных).
Щедро используйте потоковую SSR
Если ваши статические страницы SSR извлекают какие-либо данные, добавьте [StreamRendering]. Затраты минимальны, а ощутимое улучшение производительности значительно. Пользователи видят, что контент появляется постепенно, а не смотрит на пустую страницу.
Осторожно обращайтесь с переходами состояний
Если вы используете Interactive Auto или совмещаете предварительную визуализацию с WebAssembly, всегда используйте PersistentComponentState во избежание дублирования выборки данных. Ваши пользователи скажут вам спасибо за отсутствие мерцающего контента.
Помните о дереве компонентов
Помните правила иерархии режимов рендеринга. Спланируйте дерево компонентов так, чтобы интерактивные границы имели смысл. Распространенным шаблоном является наличие статического макета с интерактивными «островками», встроенными там, где это необходимо:
@* Layout: Static SSR *@
<header>
<NavMenu />
<UserMenu @rendermode="InteractiveServer" /> @* Needs real-time auth state *@
</header>
<main>
@Body
</main>
<footer>
<ChatWidget @rendermode="InteractiveAuto" /> @* Rich interactivity *@
</footer>
Заключение
Модель интерактивности Blazor в .NET 9 и .NET 10 представляет собой зрелый, хорошо продуманный подход к созданию веб-приложений. Возможность выбирать режимы рендеринга для каждого компонента, улучшенная плавная навигация, потоковая SSR, а также постоянные улучшения в переподключении и управлении состоянием делают его привлекательным выбором для широкого спектра приложений.Ключевой вывод заключается в том, что интерактивность — это спектр, а не бинарный выбор. Большая часть вашего приложения может быть статической. Некоторым частям требуется интерактивность, управляемая сервером. Некоторым может быть полезно работать в браузере. Blazor теперь позволяет вам сделать этот выбор с максимальной степенью детализации — отдельного компонента — без нарушения структуры.
Если вы начинаете новый проект, мой совет прост: создайте веб-приложение Blazor, начните со статического SSR, добавьте [StreamRendering] на страницы с большим объемом данных и переводите отдельные компоненты в интерактивные режимы только тогда, когда они вам нужны. В итоге вы получите быстрое, эффективное и хорошо масштабируемое приложение.
Удачного программирования. Как всегда, если у вас возникнут вопросы, обращайтесь!