Поделиться через


Руководство по ASP.NET Core BlazorSignalR

Примечание.

Это не последняя версия этой статьи. В текущем выпуске смотрите версию .NET 9 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске смотрите статью версии .NET 9.

Внимание

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

Для текущего выпуска см. версию .NET 9 этой статьи.

В этой статье описывается процесс настройки подключений SignalR в программах Blazor и управления ими.

Общие рекомендации по настройке ASP.NET Core SignalR смотрите в разделах области документации Обзор ASP.NET Core, особенно настройка ASP.NET Core.

Серверные приложения используют ASP.NET Core SignalR для взаимодействия с браузером. SignalRУсловия размещения и масштабирования распространяются на серверные приложения.

Blazor лучше всего работает с использованием WebSockets в качестве транспорта SignalR благодаря низкой задержке, надежности и безопасности. Долгосрочное опрашивание используется SignalR, если WebSockets недоступны или если приложение явно настроено на использование долгосрочного опрашивания.

Служба Azure SignalR с сохранением состояния при повторном подключении

Служба Azure SignalR с пакетом SDK версии 1.26.1 или более поздней версии поддерживает SignalR повторное подключение с отслеживанием состояния (WithStatefulReconnect).

Сжатие WebSocket для компонентов интерактивного сервера

По умолчанию компоненты интерактивного сервера:

  • Включите сжатие для подключений WebSocket. DisableWebSocketCompression (по умолчанию: false) управляет сжатием WebSocket.

  • frame-ancestors Политика безопасности содержания (CSP), заданная как 'self', которая позволяет внедрять приложение только в <iframe> источник, из которого оно обслуживается, когда включено сжатие или предусмотрена конфигурация для контекста WebSocket. ContentSecurityFrameAncestorPolicy управляет frame-ancestors CSP.

frame-ancestors CSP можно удалить вручную, задав значение ContentSecurityFrameAncestorsPolicynull , так как может потребоваться настроить CSP централизованно. frame-ancestors Если CSP управляется централизованно, необходимо принять меры по применению политики всякий раз при отображении первого документа. Мы не рекомендуем полностью удалить политику, так как это может сделать приложение уязвимым для атаки.

Используйте ConfigureWebSocketAcceptContext для настройки WebSocketAcceptContext WebSocket-подключений, используемых компонентами сервера. По умолчанию применяется политика, которая включает сжатие и задает CSP для предков кадров, определенных в ContentSecurityFrameAncestorsPolicy.

Примеры использования:

Отключите сжатие, установив DisableWebSocketCompression в true, что снижает уязвимость приложения к атакам, но может привести к снижению производительности:

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.DisableWebSocketCompression = true)

Если сжатие включено, настройте более строгий CSP со значением (необходимые одиночные кавычки), позволяющий сжатие WebSocket, но не допускающий браузерам внедрять приложение в любой .

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")

Если сжатие включено, удалите frame-ancestors CSP, установив ContentSecurityFrameAncestorsPolicy на null. Этот сценарий рекомендуется использовать только для приложений, которые задают CSP централизованно.

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = null)

Внимание

Браузеры применяют директивы CSP из нескольких заголовков CSP с использованием самого строгого значения директивы политики. Поэтому разработчик не может добавить более слабую frame-ancestors политику, чем 'self' по назначению или по ошибке.

Для строкового значения, передаваемого в ContentSecurityFrameAncestorsPolicy, требуются одиночные кавычки:

Неподдерживаемые значения:noneself

Поддерживаемые значения:'none', 'self'

Дополнительные параметры включают указание одного или нескольких источников узлов и источников схемы.

Для информации о последствиях для безопасности, см. Руководство по смягчению угроз для интерактивного серверного рендеринга в ASP.NET Core. Дополнительные сведения о директиве frame-ancestors см. в разделе CSP: frame-ancestors (документация по MDN).

Отключение сжатия ответов для Горячей перезагрузки

При использовании Горячей перезагрузки отключите промежуточное ПО сжатия ответов в среде Development. Независимо от того, используется ли код по умолчанию из шаблона проекта, UseResponseCompression всегда вызывается первым в конвейере обработки запросов.

В файле Program:

if (!app.Environment.IsDevelopment())
{
    app.UseResponseCompression();
}

Согласование между источниками на стороне SignalR клиента для проверки подлинности

В этом разделе объясняется, как настроить SignalRбазовый клиент для отправки учетных данных, таких как файлы cookie или заголовки проверки подлинности HTTP.

используйте SetBrowserRequestCredentials, чтобы задать Include в междоменных запросах fetch.

IncludeRequestCredentialsMessageHandler.cs:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Http;

public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
        return base.SendAsync(request, cancellationToken);
    }
}

Если подключение концентратора установлено, назначьте HttpMessageHandler параметру HttpMessageHandlerFactory:

private HubConnectionBuilder? hubConnection;

...

hubConnection = new HubConnectionBuilder()
    .WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
    {
        options.HttpMessageHandlerFactory = innerHandler => 
            new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
    }).Build();

В предыдущем примере URL-адрес подключения концентратора настраивается на абсолютный URI по адресу /chathub. Код URI также может быть установлен через строку, например https://signalr.example.com, или через конфигурацию. Navigation является внедренным NavigationManager.

Дополнительные сведения см. в статье Конфигурация ASP.NET Core SignalR.

Отрисовка на стороне клиента

Если настроен предварительный рендеринг, он выполняется до установки подключения клиента к серверу. Дополнительные сведения см. в разделе "Отрисовка компонентов ASP.NET Core".

Если предварительный рендеринг настроен, он производится до установления подключения клиента к серверу. Дополнительные сведения см. в следующих статьях:

Предварительно обработанный размер состояния и SignalR ограничение на размер сообщения

Размер большого предварительно отрисованного состояния может превышать лимит на размер сообщения цепи BlazorSignalR, что приводит к следующему:

  • Не удается инициализировать SignalR цепь с ошибкой на клиенте: Circuit host not initialized.
  • Пользовательский интерфейс повторного подключения на клиенте отображается при сбое канала. Восстановление невозможно.

Чтобы устранить проблему, используйте любой из следующих подходов:

  • Уменьшите объем данных, которые вы помещаете в предварительно созданное состояние.
  • SignalR Увеличьте размер сообщения. ПРЕДУПРЕЖДЕНИЕ. Увеличение ограничения может увеличить риск атак типа "отказ в обслуживании" (DoS).

Дополнительные клиентские ресурсы

Использование сопоставления сеансов (липкие сеансы) для размещения веб-фарм на стороне сервера

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

Следующая ошибка выдаётся приложением, которое не включило привязку сессии в веб-ферме.

Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.

Дополнительные сведения о привязке сеансов с размещением службы приложений Azure см. в статье Развертывание и размещение серверных приложений ASP.NET Core.

Служба Azure SignalR

Необязательная служба Azure работает в связке с центром приложения для масштабирования серверного приложения до большого количества одновременных подключений. Кроме того, глобальное присутствие службы и её высокопроизводительные центры обработки данных значительно помогают в снижении задержки, связанной с географией.

Служба не требуется для Blazor приложений, размещенных в службе приложение Azure или приложениях контейнеров Azure, но может быть полезно в других средах размещения:

  • Чтобы упростить горизонтальное масштабирование подключения.
  • Управление глобальным распределением.

Дополнительные сведения см. в разделе «Размещение и развертывание серверных приложений ASP.NET Core»Blazor.

Параметры обработчика канала на стороне сервера

Настройте схему с CircuitOptions. Просмотрите значения по умолчанию в справочном источнике.

Примечание.

В документации ссылки на справочные материалы по .NET обычно ведут к ветви по умолчанию в репозитории .NET, которая отражает текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для конкретного выпуска, используйте раскрывающийся список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Задайте или прочитайте параметры в файле Program с помощью делегата параметров AddInteractiveServerComponents. Заполнитель {OPTION} представляет опцию, а заполнитель {VALUE} — это значение.

В файле Program:

builder.Services.AddRazorComponents().AddInteractiveServerComponents(options =>
{
    options.{OPTION} = {VALUE};
});

Чтение или установка параметров в файле Program с делегатом параметров AddServerSideBlazor. Заполнитель {OPTION} представляет опцию, а заполнитель {VALUE} — это значение.

В файле Program:

builder.Services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

Чтение или установка параметров в Startup.ConfigureServices с помощью делегата параметров AddServerSideBlazor. Заполнитель {OPTION} представляет опцию, а заполнитель {VALUE} — значение.

В Startup.ConfigureServices из Startup.cs:

services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

Чтобы настроить HubConnectionContext, используйте HubConnectionContextOptions с AddHubOptions. Просмотрите параметры контекста подключения концентратора по умолчанию в эталонном источнике. Для описания параметров в документации см. раздел конфигурация ASP.NET Core. Заполнитель {OPTION} представляет опцию, а заполнитель {VALUE} — значение.

Примечание.

По ссылкам в документации на справочные материалы по .NET обычно загружается ветвь репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

В файле Program:

builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

В файле Program:

builder.Services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

В Startup.ConfigureServices из Startup.cs:

services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Предупреждение

Значение MaximumReceiveMessageSize по умолчанию — 32 КБ. Увеличение значения может увеличить риск атак типа "отказ в обслуживании" (DoS).

Blazor MaximumParallelInvocationsPerClient использует значение 1, которое является значением по умолчанию. Дополнительные сведения см. в разделе MaximumParallelInvocationsPerClient > 1 разорвает отправку файла в Blazor Server режиме (dotnet/aspnetcore #53951).

Для получения информации об управлении памятью см. в разделе Хостинг и развертывание серверных приложений ASP.NET CoreBlazor.

Blazor Параметры хаба

Настройте MapBlazorHub параметры для управления HttpConnectionDispatcherOptionsBlazor центром. Просмотрите параметры по умолчанию для диспетчера подключения концентратора в разделе эталонный источник. Заполнитель {OPTION} представляет опцию, а {VALUE} — это значение.

Примечание.

По ссылкам в документации на справочные материалы по .NET обычно загружается ветвь репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список «Switch branches or tags» (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Поместите вызов app.MapBlazorHub после вызова app.MapRazorComponents в файл приложения Program :

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

Настройка концентратора, используемого AddInteractiveServerRenderMode, терпит неудачу при MapBlazorHub с AmbiguousMatchException.

Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.

Чтобы решить проблему для приложений, предназначенных для .NET 8, предоставьте настраиваемому центру Blazor более высокий приоритет, используя метод WithOrder:

app.MapBlazorHub(options =>
{
    options.CloseOnAuthenticationExpiration = true;
}).WithOrder(-1);

Дополнительные сведения см. на следующих ресурсах:

Укажите параметры app.MapBlazorHub в файле приложения Program :

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

Укажите параметры app.MapBlazorHub в конфигурации маршрутизации конечных точек:

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub(options =>
    {
        options.{OPTION} = {VALUE};
    });
    ...
});

Максимальный размер сообщения получения

Этот раздел применяется только к проектам, реализующим SignalR.

Максимальный размер входящих SignalR сообщений для методов концентратора ограничен HubOptions.MaximumReceiveMessageSize (по умолчанию: 32 КБ). SignalR сообщения больше MaximumReceiveMessageSize вызывают ошибку. Платформа не накладывает ограничение на размер сообщений SignalR от концентратора клиенту.

Если для SignalR логирования не установлен уровень Отладка или Трассировка, ошибка размера сообщения отображается только в консоли средств разработчика браузера.

Ошибка: подключение отключено с ошибкой "Ошибка: сервер вернул ошибку при закрытии: соединение закрыто с ошибкой."

Если для ведения журнала на стороне сервера SignalR установлен уровень Отладка или Трассировка, ведение журнала на стороне сервера выводит InvalidDataException для ошибки размера сообщения.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      ...
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Ошибка.

System.IO.InvalidDataException: превышен максимальный размер сообщения 32768B. Размер сообщения можно настроить в AddHubOptions.

Один из подходов включает увеличение предела, задав MaximumReceiveMessageSize в Program файле. В следующем примере максимальный размер сообщения получения составляет 64 КБ:

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR Увеличение предельного размера входящих сообщений зависит от затрат на дополнительные ресурсы сервера и повышает риск атак типа "отказ в обслуживании" (DoS). Кроме того, считывание большого объема содержимого в память в виде строк или массивов байтов может привести к выделениям памяти, которые плохо взаимодействуют со сборщиком мусора, что приводит к дополнительным потерям производительности.

Лучшим вариантом чтения больших полезных данных является отправка содержимого в небольших блоках и обработка полезных данных в виде Stream. Это можно использовать при чтении больших JSON-нагрузок взаимодействия JavaScript (JS) или при наличии данных взаимодействия как необработанных байтов (JS). Пример, демонстрирующий отправку больших двоичных данных в серверных приложениях, использующих методы, аналогичные InputFile компоненту, смотрите в примере приложения Binary Submit и в BlazorInputLargeTextArea примере компонента.

Примечание.

В документации ссылки на исходные справочные материалы по .NET обычно загружают ветвь по умолчанию репозитория, которая представляет процесс текущей разработки для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Формы, обрабатывающие большие полезные данные через SignalR, также могут напрямую использовать взаимодействие потоковой передачи для интероперабельности JS. Дополнительные сведения см. в статье Вызов методов .NET из функций JavaScript в ASP.NET Core Blazor. Пример форм, который передает <textarea> данные на сервер, см. в статье «Устранение неполадок форм в ASP.NET Core Blazor».

Один из подходов включает увеличение предела, задав MaximumReceiveMessageSize в Program файле. В следующем примере максимальный размер сообщения получения составляет 64 КБ:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR Увеличение предельного размера входящих сообщений зависит от затрат на дополнительные ресурсы сервера и повышает риск атак типа "отказ в обслуживании" (DoS). Кроме того, считывание большого объема содержимого в память в виде строк или массивов байтов может привести к выделениям памяти, которые используются неэффективно сборщиком мусора, что приводит к дополнительным потерям производительности.

Лучшим вариантом чтения больших полезных данных является отправка содержимого в небольших блоках и обработка полезных данных в виде Stream. Это можно использовать при чтении больших полезных данных JSON для взаимодействия с JavaScript (JS) или если данные для взаимодействия (JS) доступны как сырые байты. Пример, демонстрирующий отправку больших двоичных полезных данных в Blazor Server, который использует методы, аналогичные компоненту InputFile, см. в примере приложения Binary Submit и в примере компонента BlazorInputLargeTextArea.

Примечание.

По ссылкам в документации на справочные материалы по .NET обычно загружается ветвь репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Формы, которые обрабатывают большие полезные данные через SignalR, также могут использовать потоковое взаимодействие напрямую JS. Дополнительные сведения см. в статье Вызов методов .NET из функций JavaScript в ASP.NET Core Blazor. В приложении, в котором передаются данные <textarea>, см. раздел Blazor Server"Устранение неполадок форм ASP.NET Core Blazor".

Увеличьте ограничение, настроив MaximumReceiveMessageSize в Startup.ConfigureServices:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR Увеличение предельного размера входящих сообщений зависит от затрат на дополнительные ресурсы сервера и повышает риск атак типа "отказ в обслуживании" (DoS). Кроме того, считывание большого объема содержимого в память в виде строк или массивов байтов может привести к неудачным выделениям памяти, которые плохо обрабатываются сборщиком мусора, что дополнительно снизит производительность.

При разработке кода, который передает большой объем данных, следует учитывать следующее руководство.

  • Используйте встроенную поддержку потокового JS взаимодействия для передачи данных, превышающих SignalR ограничение размера входящих сообщений:
  • Общие советы:
    • Не выделяйте большие объекты в JS и коде C#.
    • Освободите занятую память при завершении или отмене процесса.
    • Примените следующие дополнительные требования для обеспечения безопасности:
      • Объявите максимальный размер файла или данных, который может быть передан.
      • Объявите минимальную скорость передачи от клиента к серверу.
    • После получения данных сервером данные могут быть следующими:
      • Временно сохранены в буфере памяти до тех пор, пока не будут собраны все сегменты.
      • Употреблены немедленно. Например, данные могут храниться сразу в базе данных или записываться на диск по мере получения каждого сегмента.
  • Разделите данные на небольшие части и отправляйте сегменты данных последовательно, пока все данные не будут получены сервером.
  • Не выделяйте большие объекты в JS и коде C#.
  • Не блокируйте основной поток пользовательского интерфейса на длительные периоды при отправке или получении данных.
  • Освободите занятую память при завершении или отмене процесса.
  • Примените следующие дополнительные требования для обеспечения безопасности:
    • Объявите максимальный размер файла или данных, который может быть передан.
    • Объявите минимальную скорость передачи от клиента к серверу.
  • После получения данных сервером данные могут быть следующими:
    • Временно сохранены в буфере памяти до тех пор, пока не будут собраны все сегменты.
    • Употребляйте немедленно. Например, данные могут храниться сразу в базе данных или записываться на диск по мере получения каждого сегмента.

Blazor Настройка маршрута конечной точки узла на стороне сервера

В файле Program вызовите MapBlazorHub, чтобы сопоставить BlazorHub с путем по умолчанию приложения. Скрипт Blazor (blazor.*.js) автоматически указывает на конечную точку, созданную MapBlazorHub.

Отражение состояния подключения на стороне сервера в пользовательском интерфейсе

Если клиент обнаруживает потерянное соединение с сервером, пользовательский интерфейс по умолчанию отображается пользователю во время попытки повторного подключения клиента:

Пользовательский интерфейс повторного подключения по умолчанию.

Пользовательский интерфейс повторного подключения по умолчанию.

Если повторное подключение завершается ошибкой, пользователю будет показано повторить попытку или перезагрузить страницу:

Пользовательский интерфейс повтора по умолчанию.

Пользовательский интерфейс повтора по умолчанию.

При успешном повторном подключении состояние пользователя часто теряется. Пользовательский код можно добавить в любой компонент для сохранения и перезагрузки пользовательского состояния во время сбоев подключения. Дополнительные сведения. см. в статье Управление состоянием ASP.NET Core Blazor.

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

  • Набор классов CSS components-reconnect-* (столбце класса Css), которые задаются или не задаются Blazor в элементе с idcomponents-reconnect-modal.
  • Событие components-reconnect-state-changed (в столбце события), указывающее на изменение статуса повторного подключения.
Класс CSS Событие Означает...
components-reconnect-show show Потеря соединения. Клиент пытается повторно подключиться. Отображается модал повторного подключения.
components-reconnect-hide hide Активное подключение к серверу восстановлено. Модель повторного подключения закрыта.
components-reconnect-retrying retrying Клиент пытается повторно подключиться.
components-reconnect-failed failed Сбой повторного подключения, возможно, из-за сбоя сети.
components-reconnect-rejected rejected Повторное подключение отклонено.

При изменении состояния повторного подключения в components-reconnect-state-changed на failedвызовите Blazor.reconnect() в JavaScript для попытки повторного подключения.

Когда изменение состояния повторного подключения rejected, сервер был достигнут, но отказался от подключения, а состояние пользователя на сервере потеряно. Чтобы перезагрузить приложение, вызовите метод location.reload() в JavaScript. Это состояние соединения может появиться в следующих случаях:

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

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

const reconnectModal = document.getElementById("components-reconnect-modal");
reconnectModal.addEventListener("components-reconnect-state-changed", 
  handleReconnectStateChanged);

function handleReconnectStateChanged(event) {
  if (event.detail.state === "show") {
    reconnectModal.showModal();
  } else if (event.detail.state === "hide") {
    reconnectModal.close();
  } else if (event.detail.state === "failed") {
    Blazor.reconnect();
  } else if (event.detail.state === "rejected") {
    location.reload();
  }
}

Элемент с idcomponents-reconnect-max-retries отображает максимальное количество попыток повторного подключения.

<span id="components-reconnect-max-retries"></span>

Элемент с idcomponents-reconnect-current-attempt отображает текущую попытку повторного подключения:

<span id="components-reconnect-current-attempt"></span>

Элемент с idcomponents-seconds-to-next-attempt отображает количество секунд до следующей попытки повторного подключения.

<span id="components-seconds-to-next-attempt"></span>

Шаблон проекта Blazor Web App включает компонент ReconnectModal (Components/Layout/ReconnectModal.razor) с пакетной таблицей стилей и файлами JavaScript (ReconnectModal.razor.css, ReconnectModal.razor.js), которые можно настроить по мере необходимости. Эти файлы можно проверить в источнике ссылок ASP.NET Core или проверить приложение, созданное из шаблона проекта Blazor Web App. Компонент добавляется в проект при создании проекта в Visual Studio с установленным режимом интерактивной отрисовки на Server или Auto, или создается с помощью интерфейса командной строки .NET с параметром --interactivity server (по умолчанию) или --interactivity auto.

Примечание.

Ссылки в документации на справочные материалы по .NET обычно загружают ветвь репозитория, которая по умолчанию представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте выпадающий список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Чтобы настроить пользовательский интерфейс, определите один элемент с id и components-reconnect-modal, находящимися в содержимом элемента <body>. В следующем примере элемент помещается в App компонент.

App.razor:

Чтобы настроить пользовательский интерфейс, определите один элемент с содержимым элемента <body>, содержащим id и components-reconnect-modal. В следующем примере элемент размещается на главной странице.

Pages/_Host.cshtml:

Чтобы настроить пользовательский интерфейс, определите один элемент с id и components-reconnect-modal в содержимом элемента <body>. В следующем примере элемент помещается на страницу макета.

Pages/_Layout.cshtml:

Чтобы настроить пользовательский интерфейс, определите один элемент с id и components-reconnect-modal в содержимом элемента <body>. В следующем примере элемент помещается на основную страницу.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    Connection lost.<br>Attempting to reconnect...
</div>

Примечание.

Если приложение отрисовывает несколько элементов с idcomponents-reconnect-modal, то только первый отрисованный элемент получает изменения класса CSS для отображения или скрытия элемента.

Добавьте следующие стили CSS в таблицу стилей сайта.

wwwroot/app.css:

wwwroot/css/site.css:

#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show, 
#components-reconnect-modal.components-reconnect-failed, 
#components-reconnect-modal.components-reconnect-rejected {
    display: block;
    background-color: white;
    padding: 2rem;
    border-radius: 0.5rem;
    text-align: center;
    box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
    margin: 50px 50px;
    position: fixed;
    top: 0;
    z-index: 10001;
}

В следующей таблице описаны классы CSS, применяемые к элементу components-reconnect-modal платформы Blazor.

Класс CSS Означает...
components-reconnect-show Потеря соединения. Клиент пытается повторно подключиться. Отобразить модальное окно.
components-reconnect-hide Активное подключение к серверу восстановлено. Скрыть модальное окно.
components-reconnect-failed Сбой повторного подключения, возможно, из-за сбоя сети. Чтобы повторить попытку подключения, вызовите метод window.Blazor.reconnect() в JavaScript.
components-reconnect-rejected Повторное подключение отклонено. Сервер был доступен, но отклонил подключение, и информация о пользователе на сервере утеряна. Чтобы перезагрузить приложение, вызовите метод location.reload() в JavaScript. Это состояние соединения может появиться в следующих случаях:
  • произошел сбой в цепи на стороне сервера;
  • клиент был отключен достаточно долго, чтобы сервер сбросил состояние пользователя. Экземпляры пользовательских компонентов удаляются.
  • сервер был перезагружен или был выполнен перезапуск рабочего процесса приложения.

Настройте задержку перед появлением пользовательского интерфейса повторного подключения, задав transition-delay свойство в CSS сайта для модального элемента. В следующем примере задержка перехода изменяется с 500 мс (по умолчанию) на 1000 мс (1 секунда).

wwwroot/app.css:

wwwroot/css/site.css:

#components-reconnect-modal {
    transition: visibility 0s linear 1000ms;
}

Чтобы отобразить текущую попытку повторного подключения, определите элемент с параметром idcomponents-reconnect-current-attempt. Чтобы отобразить максимальное число повторных попыток подключения, определите элемент с id и components-reconnect-max-retries. В следующем примере эти элементы размещаются в модальном окне попытки повторного подключения, следующем после предыдущего примера.

<div id="components-reconnect-modal">
    There was a problem with the connection!
    (Current reconnect attempt: 
    <span id="components-reconnect-current-attempt"></span> /
    <span id="components-reconnect-max-retries"></span>)
</div>

Когда появляется диалоговое окно повторного подключения, оно отображает следующее содержимое с счетчиком попыток повторного подключения.

Возникла проблема с подключением! (Текущая попытка повторного подключения: 1/8)

Рендеринг на стороне сервера

По умолчанию компоненты предварительно создаются на сервере до установки подключения клиента к серверу. Дополнительные сведения см. в разделе Предварительная отрисовка компонентов Razor ASP.NET Core.

По умолчанию компоненты предварительно создаются на сервере до установки подключения клиента к серверу. Дополнительные сведения см. в статье Вспомогательная функция тега компонента в ASP.NET Core.

Мониторинг активности схемы на стороне сервера

Мониторинг активности входящего канала с использованием метода CreateInboundActivityHandler на CircuitHandler. Активность на входящем канале — это любые действия, отправляемые из браузера на сервер, такие как события пользовательского интерфейса или вызовы взаимодействия JavaScript с .NET.

Например, можно использовать обработчик действия канала для обнаружения простоя клиента и регистрации его идентификатора канала (Circuit.Id):

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.Options;
using Timer = System.Timers.Timer;

public sealed class IdleCircuitHandler : CircuitHandler, IDisposable
{
    private Circuit? currentCircuit;
    private readonly ILogger logger;
    private readonly Timer timer;

    public IdleCircuitHandler(ILogger<IdleCircuitHandler> logger, 
        IOptions<IdleCircuitOptions> options)
    {
        timer = new Timer
        {
            Interval = options.Value.IdleTimeout.TotalMilliseconds,
            AutoReset = false
        };

        timer.Elapsed += CircuitIdle;
        this.logger = logger;
    }

    private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs e)
    {
        logger.LogInformation("{CircuitId} is idle", currentCircuit?.Id);
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        currentCircuit = circuit;

        return Task.CompletedTask;
    }

    public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
        Func<CircuitInboundActivityContext, Task> next)
    {
        return context =>
        {
            timer.Stop();
            timer.Start();

            return next(context);
        };
    }

    public void Dispose() => timer.Dispose();
}

public class IdleCircuitOptions
{
    public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
}

public static class IdleCircuitHandlerServiceCollectionExtensions
{
    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services, 
        Action<IdleCircuitOptions> configureOptions)
    {
        services.Configure(configureOptions);
        services.AddIdleCircuitHandler();

        return services;
    }

    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services)
    {
        services.AddScoped<CircuitHandler, IdleCircuitHandler>();

        return services;
    }
}

Зарегистрируйте службу в Program файле. В следующем примере настраивается время ожидания простоя по умолчанию в течение пяти минут до пяти секунд, чтобы проверить предыдущую IdleCircuitHandler реализацию:

builder.Services.AddIdleCircuitHandler(options => 
    options.IdleTimeout = TimeSpan.FromSeconds(5));

Обработчики активности цепи также предоставляют подход для доступа к службам с областью действия Blazor из других областей действия зависимости, отличной от Blazor (DI). Дополнительные сведения и примеры см. в следующих примерах:

Запуск Blazor

Настройте ручной запуск BlazorSignalR канала в App.razor файлеBlazor Web App:

Настройте ручной запуск цепи BlazorSignalR в файле Pages/_Host.cshtml (Blazor Server):

Настройте ручной запуск BlazorSignalR цепи в Pages/_Layout.cshtml файле (Blazor Server):

Настройте ручной запуск цепи SignalRBlazor в файле Pages/_Host.cshtml (Blazor Server):

  • добавьте атрибут autostart="false" в тег <script> для сценария blazor.*.js;
  • Поместите скрипт, который вызывает Blazor.start(), после загрузки скрипта Blazor и внутри закрывающего тега </body>.

При отключении autostart все части приложения, которые не зависят от канала, работают нормально. Например, работает маршрутизация на стороне клиента. Однако любые аспекты, зависящие от схемы, не будут функционировать до вызова Blazor.start(). Пока не будет установлен канал, поведение приложения будет непредсказуемым. Например, когда канал отключен, методы компонентов не выполняются.

Дополнительные сведения о том, как инициализировать Blazor, когда документ готов, и как соединить с JS Promise, см. в разделе Blazorstartup.

Конфигурируйте SignalR время ожидания и Keep-Alive на клиенте

Настройте следующие значения для клиента:

  • withServerTimeout: настраивает время ожидания сервера в миллисекундах. Если это время ожидания истекает без получения сообщений с сервера, подключение завершается ошибкой. По умолчанию время ожидания составляет 30 секунд. Время ожидания сервера должно быть как минимум в два раза больше значения, назначенного интервалу Keep-Alive (withKeepAliveInterval).
  • withKeepAliveInterval: настраивает интервал поддержания соединения в миллисекундах (интервал по умолчанию для проверки связи с сервером). Этот параметр позволяет серверу обнаруживать жесткие отключения, например, когда клиент отключает компьютер из сети. Пинг происходит не чаще, чем пингует сервер. Если сервер посылает сигнал каждые пять секунд, присваивая значение меньшее, чем 5000 (5 секунд), сервер также будет посылать сигнал каждые пять секунд. Значение по умолчанию — 15 секунд. Интервал Keep-Alive должен быть меньше или равен половине значения тайм-аута сервера (withServerTimeout).

В следующем примере файла App.razor (Blazor Web App) показано назначение значений по умолчанию.

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(30000).withKeepAliveInterval(15000);
      }
    }
  });
</script>

Следующий пример для файла Pages/_Host.cshtml (Blazor Server, все версии, кроме ASP.NET Core в .NET 6) или файла Pages/_Layout.cshtml (Blazor Server, ASP.NET Core в .NET 6).

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(30000).withKeepAliveInterval(15000);
    }
  });
</script>

В предыдущем примере заполнитель {BLAZOR SCRIPT} обозначает путь к скрипту и имя файла Blazor. Для получения информации о местоположении скрипта и пути для использования см. в разделе ASP.NET Core структура проекта.

При создании подключения концентратора в компоненте установите ServerTimeout (по умолчанию: 30 секунд) и KeepAliveInterval (по умолчанию: 15 секунд) в компоненте HubConnectionBuilder. Установите HandshakeTimeout (по умолчанию: 15 секунд) на встроенном HubConnection объекте. В следующем примере показано назначение значений по умолчанию:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(30))
        .WithKeepAliveInterval(TimeSpan.FromSeconds(15))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Настройте следующие значения для клиента:

  • serverTimeoutInMilliseconds: время ожидания для сервера в миллисекундах. Если это время ожидания истекает без получения сообщений с сервера, подключение завершается ошибкой. По умолчанию время ожидания составляет 30 секунд. Время ожидания сервера должно быть по крайней мере в два раза больше значения, назначенного интервалу Keep-Alive (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds: интервал по умолчанию, по которому выполняется проверка доступа к серверу. Этот параметр позволяет серверу обнаруживать жесткие отключения, например, когда клиент отключает компьютер из сети. Пинг происходит не чаще, чем у сервера. Если сервер отправляет пинг каждые пять секунд, то при присвоении значения ниже 5000 (5 секунд) пинг снова отправляется каждые пять секунд. Значение по умолчанию — 15 секунд. Интервал поддержания соединения должен быть не более половины значения, назначенного времени ожидания сервера (serverTimeoutInMilliseconds).

Следующий пример для файла Pages/_Host.cshtml (Blazor Server, все версии, кроме ASP.NET Core в .NET 6) или файла Pages/_Layout.cshtml (Blazor Server, ASP.NET Core в .NET 6):

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 30000;
      c.keepAliveIntervalInMilliseconds = 15000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

В предыдущем примере заполнитель {BLAZOR SCRIPT} обозначает путь к скрипту и имя файла Blazor. Сведения о расположении скрипта и используемом пути см. в разделе структура проекта ASP.NET Core.

При создании подключения концентратора в компоненте установите ServerTimeout (по умолчанию: 30 секунд), HandshakeTimeout (по умолчанию: 15 секунд) и KeepAliveInterval (по умолчанию: 15 секунд) на построенном HubConnection объекте. В следующем примере показано назначение значений по умолчанию:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
    hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

При изменении значений времени ожидания сервера (ServerTimeout) или интервала удержания соединения (KeepAliveInterval):

  • Время ожидания сервера должно быть как минимум вдвое больше значения, установленного для интервала Keep-Alive.
  • Интервал "Keep-Alive" должен быть меньше или равен половине значения таймаута сервера.

Дополнительные сведения см. в разделах "Глобальные сбои развертывания и подключения" в следующих статьях:

Изменение обработчика повторного подключения на стороне сервера

Можно изменить события соединения схемы обработчика повторного подключения для создания пользовательских вариантов поведения, таких как:

  • уведомлять пользователя, если подключение было разорвано;.
  • вносить записи в журнал (со стороны клиента) при подключении канала.

Чтобы изменить события соединения, зарегистрируйте обратные вызовы для следующих изменений соединения:

  • Для прерванных соединений используется onConnectionDown;
  • для устанавливаемых и повторно устанавливаемых соединений используется onConnectionUp.

Необходимо задать как onConnectionDown, так и onConnectionUp.

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: (options, error) => console.error(error),
        onConnectionUp: () => console.log("Up, up, and away!")
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    reconnectionHandler: {
      onConnectionDown: (options, error) => console.error(error),
      onConnectionUp: () => console.log("Up, up, and away!")
    }
  });
</script>

В предыдущем примере {BLAZOR SCRIPT} заполнитель — это путь к скрипту Blazor и имя файла. Сведения о расположении скрипта и используемом пути см. в структуре проекта ASP.NET Core.

Автоматическое обновление страницы при сбое повторного подключения на стороне сервера

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

App.razor:

Pages/_Host.cshtml:

<div id="reconnect-modal" style="display: none;"></div>
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script src="boot.js"></script>

В предыдущем примере {BLAZOR SCRIPT} заполнитель — это путь к скрипту Blazor и имя файла. Сведения о расположении скрипта и пути его использования см. в разделе структура проекта ASP.NET Core.

Создайте следующий wwwroot/boot.js файл.

Blazor Web App:

(() => {
  const maximumRetryCount = 3;
  const retryIntervalMilliseconds = 5000;
  const reconnectModal = document.getElementById('reconnect-modal');

  const startReconnectionProcess = () => {
    reconnectModal.style.display = 'block';

    let isCanceled = false;

    (async () => {
      for (let i = 0; i < maximumRetryCount; i++) {
        reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;

        await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));

        if (isCanceled) {
          return;
        }

        try {
          const result = await Blazor.reconnect();
          if (!result) {
            // The server was reached, but the connection was rejected; reload the page.
            location.reload();
            return;
          }

          // Successfully reconnected to the server.
          return;
        } catch {
          // Didn't reach the server; try again.
        }
      }

      // Retried too many times; reload the page.
      location.reload();
    })();

    return {
      cancel: () => {
        isCanceled = true;
        reconnectModal.style.display = 'none';
      },
    };
  };

  let currentReconnectionProcess = null;

  Blazor.start({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
        onConnectionUp: () => {
          currentReconnectionProcess?.cancel();
          currentReconnectionProcess = null;
        }
      }
    }
  });
})();

Blazor Server:

(() => {
  const maximumRetryCount = 3;
  const retryIntervalMilliseconds = 5000;
  const reconnectModal = document.getElementById('reconnect-modal');

  const startReconnectionProcess = () => {
    reconnectModal.style.display = 'block';

    let isCanceled = false;

    (async () => {
      for (let i = 0; i < maximumRetryCount; i++) {
        reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;

        await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));

        if (isCanceled) {
          return;
        }

        try {
          const result = await Blazor.reconnect();
          if (!result) {
            // The server was reached, but the connection was rejected; reload the page.
            location.reload();
            return;
          }

          // Successfully reconnected to the server.
          return;
        } catch {
          // Didn't reach the server; try again.
        }
      }

      // Retried too many times; reload the page.
      location.reload();
    })();

    return {
      cancel: () => {
        isCanceled = true;
        reconnectModal.style.display = 'none';
      },
    };
  };

  let currentReconnectionProcess = null;

  Blazor.start({
    reconnectionHandler: {
      onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
      onConnectionUp: () => {
        currentReconnectionProcess?.cancel();
        currentReconnectionProcess = null;
      }
    }
  });
})();

Дополнительные сведения о запуске Blazor см. в статье Запуск ASP.NET Core Blazor.

Настройка счетчика повторных попыток и интервала повторного подключения на стороне сервера

Чтобы настроить число попыток и интервал повторного подключения, задайте число повторных попыток (maxRetries) и периодичность в миллисекундах для каждой повторной попытки (retryIntervalMilliseconds).

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionOptions: {
        maxRetries: 3,
        retryIntervalMilliseconds: 2000
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    reconnectionOptions: {
      maxRetries: 3,
      retryIntervalMilliseconds: 2000
    }
  });
</script>

В предыдущем примере заполнитель {BLAZOR SCRIPT} представляет собой путь к Blazor и имя файла скрипта. Сведения о расположении скрипта и пути для использования см. в разделе ASP.NET Core Blazor структура проекта.

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

Время повторного подключения по умолчанию использует вычисленную стратегию обратного выхода. Первые попытки повторного подключения происходят в быстрой последовательности, пока между попытками не будут введены вычисленные задержки. Логика по умолчанию для вычисления интервала повторных попыток — это сведения о реализации, подлежащие изменению без уведомления, но вы можете найти логику по умолчанию, которую Blazor платформа использует в computeDefaultRetryInterval функции (источник ссылок).

Примечание.

По ссылкам в документации на справочные материалы по .NET обычно загружается основная ветвь репозитория, которая представляет текущую стадию разработки для следующей версии .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Настройте поведение интервала повторных попыток, указав функцию для вычисления интервала повторных попыток. В следующем примере с экспоненциальной задержкой число предыдущих попыток повторного подключения умножается на 1000 мс для вычисления интервала повторной попытки. Если количество предыдущих попыток повторного подключения (previousAttempts) превышает максимальный лимит повторных попыток (maxRetries), null назначается интервалу повторных попыток (retryIntervalMilliseconds), чтобы прекратить дальнейшие попытки повторного подключения.

Blazor.start({
  circuit: {
    reconnectionOptions: {
      retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
        previousAttempts >= maxRetries ? null : previousAttempts * 1000
    },
  },
});

Альтернативой является указание точной последовательности интервалов повторных попыток. После последнего указанного интервала повторные попытки прекращаются, потому что retryIntervalMilliseconds функция возвращает undefined.

Blazor.start({
  circuit: {
    reconnectionOptions: {
      retryIntervalMilliseconds: 
        Array.prototype.at.bind([0, 1000, 2000, 5000, 10000, 15000, 30000]),
    },
  },
});

Дополнительные сведения о запуске Blazor см. в статье Запуск ASP.NET Core Blazor.

Управление временем появления пользовательского интерфейса повторного подключения

Управление отображением пользовательского интерфейса повторного подключения может оказаться полезным в следующих ситуациях:

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

Время появления пользовательского интерфейса повторного подключения зависит от настройки интервала поддержания активности и тайм-аутов на клиенте. Пользовательский интерфейс повторного подключения отображается при достижении времени ожидания сервера на клиенте (withServerTimeoutраздел конфигурации клиента). Однако изменение значения withServerTimeout требует изменений в других параметрах поддержания связи, времени ожидания и подтверждения, описанных в следующем руководстве.

В качестве общих советов для дальнейших рекомендаций.

  • Интервал поддержания активности должен соответствовать конфигурациям клиента и сервера.
  • Время ожидания должно быть по крайней мере вдвое больше значения интервала Keep-Alive.

Конфигурация сервера

Задайте следующие параметры:

  • ClientTimeoutInterval (по умолчанию: 30 секунд): У клиентов есть временное окно для отправки сообщения, прежде чем сервер закроет подключение.
  • HandshakeTimeout (по умолчанию: 15 секунд): интервал, используемый сервером для ожидания входящих запросов подтверждения клиентами.
  • KeepAliveInterval (по умолчанию: 15 секунд): интервал, используемый сервером для отправки пинг-сообщений для поддержания соединения подключённым клиентам. Обратите внимание, что на клиенте также имеется параметр интервала "Keep-Alive", который должен соответствовать значению на сервере.

ClientTimeoutInterval и HandshakeTimeout могут быть увеличены, и KeepAliveInterval может остаться прежним. Важно учитывать, что при изменении значений убедитесь, что время ожидания составляет как минимум двойное значение интервала Keep-Alive и что интервал Keep-Alive совпадает между сервером и клиентом. Дополнительные сведения см. в разделе "Настройка SignalR времени ожидания" и "Сохранение активности" в клиентском разделе.

В следующем примере :

  • Увеличивается ClientTimeoutInterval до 60 секунд (значение по умолчанию: 30 секунд).
  • Увеличивается HandshakeTimeout до 30 секунд (значение по умолчанию: 15 секунд).
  • KeepAliveInterval не задан в коде разработчика и поэтому использует значение по умолчанию 15 секунд. Уменьшение значения интервала keep-Alive увеличивает частоту связи, что увеличивает нагрузку на приложение, сервер и сеть. Необходимо принять меры, чтобы избежать снижения производительности при уменьшении интервала Keep-Alive.

Blazor Web App (.NET 8 или более поздней версии) в файле проекта Program сервера:

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options =>
{
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
    options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});

Blazor Server в Program файле:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
        options.HandshakeTimeout = TimeSpan.FromSeconds(30);
    });

Дополнительные сведения см. в разделе параметров обработчика канала на стороне сервера.

Настройка клиента

Задайте следующие параметры:

  • withServerTimeout (по умолчанию: 30 секунд): настраивает время ожидания сервера, указанное в миллисекундах, для подключения концентратора сети.
  • withKeepAliveInterval (по умолчанию: 15 секунд): интервал, указанный в миллисекундах, по которому соединение отправляет сообщения о сохранении активности.

Время ожидания сервера может быть увеличено, а интервал Keep-Alive может оставаться неизменным. Важно учитывать, что при изменении значений убедитесь, что время ожидания сервера как минимум вдвое превышает интервал Keep-Alive, и что значения интервала Keep-Alive совпадают между сервером и клиентом. Дополнительные сведения см. в разделе "Настройка SignalR времени ожидания" и "Сохранение активности" в клиентском разделе.

В следующем примере конфигурации запуска (расположение скриптаBlazor) для времени ожидания сервера используется настраиваемое значение 60 секунд. Интервал keep-Alive (withKeepAliveInterval) не задан и использует значение по умолчанию в 15 секунд.

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(60000);
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(60000);
    }
  });
</script>

При создании подключения концентратора в компоненте задайте время ожидания сервера (WithServerTimeoutпо умолчанию: 30 секунд) на сервере HubConnectionBuilder. Установите HandshakeTimeout (по умолчанию: 15 секунд) на встроенном объекте HubConnection. Убедитесь, что тайм-ауты как минимум в два раза превышают интервал Keep-Alive (WithKeepAliveInterval/KeepAliveInterval), и что значения Keep-Alive совпадают между сервером и клиентом.

Следующий пример основан на компоненте Index в руководстве SignalR с Blazor. Время ожидания сервера увеличивается до 60 секунд, а время ожидания подтверждения увеличивается до 30 секунд. Интервал Keep-Alive не задан и использует значение по умолчанию в 15 секунд.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(60))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Задайте следующие параметры:

  • serverTimeoutInMilliseconds (по умолчанию: 30 секунд): настраивает тайм-аут сервера, указанный в миллисекундах, для подключения узла цепи.
  • keepAliveIntervalInMilliseconds (по умолчанию: 15 секунд): интервал, указанный в миллисекундах, по которому соединение отправляет сообщения о сохранении активности.

Время ожидания сервера может быть увеличено, а интервал поддержания соединения может оставаться неизменным. Важно учитывать, что при изменении значений убедитесь, что время ожидания сервера как минимум вдвое превышает значение интервала Keep-Alive, а значения интервала Keep-Alive соответствуют друг другу на сервере и клиенте. Дополнительные сведения см. в разделе "Настройка SignalR времени ожидания" и "Сохранение активности" в клиентском разделе.

В следующем примере конфигурации запуска (расположение скриптаBlazor) для времени ожидания сервера используется настраиваемое значение 60 секунд. Интервал keep-Alive (keepAliveIntervalInMilliseconds) не задан и использует значение по умолчанию в 15 секунд.

В Pages/_Host.cshtml:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 60000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

При создании подключения концентратора в компоненте установите ServerTimeout (по умолчанию: 30 секунд) и HandshakeTimeout (по умолчанию: 15 секунд) для построенного объекта HubConnection. Убедитесь, что время ожидания как минимум вдвое больше интервала Keep-Alive. Убедитесь, что интервал Keep-Alive совпадает между сервером и клиентом.

Следующий пример основан на компоненте Index из учебного пособия с SignalRBlazor. Увеличивается ServerTimeout до 60 секунд, и HandshakeTimeout увеличивается до 30 секунд. Интервал keep-Alive (KeepAliveInterval) не задан и использует значение по умолчанию в 15 секунд.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Отключите цепь BlazorSignalR от клиента

Blazor SignalR Цепь отключается, когда unload событие страницы срабатывает. Чтобы отключить цепь в других сценариях на клиенте, вызовите Blazor.disconnect в соответствующем обработчике событий. В следующем примере цепь отключается, когда страница скрыта (событие pagehide):

window.addEventListener('pagehide', () => {
  Blazor.disconnect();
});

Дополнительные сведения о запуске Blazor см. в статье Запуск ASP.NET Core Blazor.

Обработчик схемы на стороне сервера

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

TrackingCircuitHandler.cs:

using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

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

В файле Program:

builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

В Startup.ConfigureServices из Startup.cs:

services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Если методы пользовательского обработчика цепи создают необработанное исключение, это исключение является фатальным для цепи. Чтобы допускать исключения в коде обработчика или вызываемых методах, оберните этот код в один или несколько операторов try-catch для обработки ошибок и ведения журнала.

Когда канал завершается из-за того, что пользователь отключился и платформа очищает состояние канала, платформа удаляет область DI канала. При удалении области удаляются все службы DI, ограниченные каналом и реализующие System.IDisposable. Если любая служба DI вызывает необработанное исключение во время удаления, платформа регистрирует исключение. Дополнительные сведения см. в статье Внедрение зависимостей Blazor ASP.NET Core.

Обработчик цепи на стороне сервера для захвата пользователей для кастомных служб

Используйте CircuitHandler для захвата пользователя из AuthenticationStateProvider и настройки этого пользователя в службе. Дополнительные сведения и примеры кода см. в разделах серверной части ASP.NET Core и Blazor Web App дополнительных сценариях безопасности.

Закрытие каналов при отсутствии оставшихся компонентов интерактивного сервера

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

Запустите контур SignalR на другом URL-адресе

Предотвратить автоматическое запуск приложения путем добавления autostart="false" в тег Blazor<script> (расположение Blazor запуска скрипта). Вручную установите URL-адрес контура с помощью Blazor.start. В следующих примерах используется путь /signalr.

Blazor Web Apps:

- <script src="_framework/blazor.web.js"></script>
+ <script src="_framework/blazor.web.js" autostart="false"></script>
+ <script>
+   Blazor.start({
+     circuit: {
+       configureSignalR: builder => builder.withUrl("/signalr")
+     },
+   });
+ </script>

Blazor Server:

- <script src="_framework/blazor.server.js"></script>
+ <script src="_framework/blazor.server.js" autostart="false"></script>
+ <script>
+   Blazor.start({
+     configureSignalR: builder => builder.withUrl("/signalr")
+   });
+ </script>

Добавьте следующий вызов MapBlazorHub с путем концентратора к конвейеру обработки промежуточного ПО в файле Program серверного приложения.

Blazor Web Apps:

app.MapBlazorHub("/signalr");

Blazor Server:

Оставьте существующий вызов MapBlazorHub в файле и добавьте новый вызов MapBlazorHub с помощью пути:

app.MapBlazorHub();
+ app.MapBlazorHub("/signalr");

Олицетворение для проверки подлинности Windows

Аутентифицированные подключения к узлу (HubConnection) создаются с UseDefaultCredentials для указания использования учетных данных по умолчанию в HTTP-запросах. Дополнительные сведения см. в разделе Проверка подлинности и авторизация в ASP.NET Core SignalR.

Если приложение работает в IIS Express в качестве пользователя, вошедшего в систему в рамках проверки подлинности Windows, что, скорее всего, личная или рабочая учетная запись пользователя, учетные данные по умолчанию являются учетными данными пользователя, вошедшего в систему.

Когда приложение публикуется в IIS, оно выполняется в пуле приложений Identity. HubConnection подключается как учетная запись IIS "user", на которой размещено приложение, а не пользователь, обращающийся к странице.

Реализуйте олицетворение с помощью HubConnection для использования идентичности пользователя, использующего браузер.

В следующем примере :

  • Пользователь из поставщика состояния аутентификации приводится к типу WindowsIdentity.
  • Токен доступа удостоверения передается в WindowsIdentity.RunImpersonatedAsync с кодом, который создает и запускает HubConnection.
protected override async Task OnInitializedAsync()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();

    if (authState?.User.Identity is not null)
    {
        var user = authState.User.Identity as WindowsIdentity;

        if (user is not null)
        {
            await WindowsIdentity.RunImpersonatedAsync(user.AccessToken, 
                async () =>
                {
                    hubConnection = new HubConnectionBuilder()
                        .WithUrl(NavManager.ToAbsoluteUri("/hub"), config =>
                        {
                            config.UseDefaultCredentials = true;
                        })
                        .WithAutomaticReconnect()
                        .Build();

                        hubConnection.On<string>("name", userName =>
                        {
                            name = userName;
                            InvokeAsync(StateHasChanged);
                        });

                        await hubConnection.StartAsync();
                });
        }
    }
}

В приведенном выше коде элемент NavManager является NavigationManager, а AuthenticationStateProvider представляет собой экземпляр службы AuthenticationStateProvider (см. документациюAuthenticationStateProvider).

Дополнительные серверные ресурсы