Udostępnij za pomocą


wskazówki dotyczące platformy ASP.NET Core BlazorSignalR

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącym wydaniem, zobacz wersję .NET 9 tego artykułu.

W tym artykule wyjaśniono, jak konfigurować połączenia w SignalR aplikacjach i zarządzać nimiBlazor.

Aby uzyskać ogólne wskazówki dotyczące konfiguracji ASP.NET CoreSignalR, zobacz tematy w sekcji Omówienie ASP.NET Core SignalR w dokumentacji, szczególnie konfiguracja ASP.NET CoreSignalR.

Aplikacje po stronie serwera używają ASP.NET Core SignalR do komunikowania się z przeglądarką. SignalRWarunki hostingu i skalowania mają zastosowanie do aplikacji po stronie serwera.

Blazor działa najlepiej w przypadku korzystania z protokołu WebSocket jako SignalR transportu ze względu na mniejsze opóźnienia, niezawodność i bezpieczeństwo. Długie sondowanie jest używane przez SignalR, gdy WebSocket nie są dostępne lub kiedy aplikacja jest skonfigurowana do korzystania z długiego sondowania.

Usługa platformy Azure SignalR ze stanowym ponownym połączeniem

Usługa Azure SignalR z SDK w wersji 1.26.1 lub nowszej obsługuje SignalR łączenie z zachowaniem stanu (WithStatefulReconnect).

Kompresja protokołu WebSocket dla składników interactive server

Domyślnie składniki interaktywnego serwera:

  • Włącz kompresję dla połączeń protokołu WebSocket. DisableWebSocketCompression (ustawienie domyślne: false) steruje kompresją protokołu WebSocket.

  • frame-ancestors Przyjmij zestaw dyrektyw Polityki Bezpieczeństwa Zawartości (CSP) ustawiony na 'self', który jest domyślny i zezwala jedynie na osadzanie aplikacji w <iframe> oryginalnej lokalizacji, z której aplikacja jest obsługiwana po włączeniu kompresji lub po podaniu konfiguracji dla kontekstu WebSocket.

Domyślną frame-ancestors politykę CSP można zmienić, ustawiając wartość ContentSecurityFrameAncestorsPolicy na null, jeśli chcesz skonfigurować zasady CSP w sposób scentralizowany lub na 'none' dla jeszcze bardziej rygorystycznej polityki. frame-ancestors Gdy CSP jest zarządzane w sposób scentralizowany, ważne jest zastosowanie zasad za każdym razem, gdy pierwszy dokument zostaje renderowany. Nie zalecamy całkowitego usunięcia zasad, ponieważ spowoduje to, że aplikacja będzie podatna na ataki. Aby uzyskać więcej informacji, zobacz Wymuszanie zasad zabezpieczeń zawartości dla ASP.NET Core Blazor i przewodnik MDN dotyczący CSP.

Służy ConfigureWebSocketAcceptContext do konfigurowania WebSocketAcceptContext połączeń protokołu WebSocket używanych przez składniki serwera. Domyślnie jest stosowana polityka, która umożliwia kompresję oraz ustawia CSP dla przodków ramki zdefiniowanych w ContentSecurityFrameAncestorsPolicy.

Przykłady użycia:

Wyłącz kompresję, ustawiając DisableWebSocketCompression na true, co zmniejsza podatność aplikacji na atak, co jednak może prowadzić do obniżenia wydajności:

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

Po włączeniu kompresji skonfiguruj bardziej rygorystyczną polisę CSP z wartością frame-ancestors (wymagane pojedyncze cudzysłowy), co umożliwia kompresję WebSocket, ale uniemożliwia przeglądarkom osadzanie aplikacji w obiekcie 'none':

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

Po włączeniu kompresji usuń CSP, ustawiając frame-ancestors na ContentSecurityFrameAncestorsPolicy. Ten scenariusz jest zalecany tylko w przypadku aplikacji, które ustawiają CSP w scentralizowany sposób:

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

Ważne

Przeglądarki stosują dyrektywy CSP z wielu nagłówków CSP przy użyciu najściślejszej wartości dyrektywy zasad. W związku z tym deweloper nie może dodać słabszych frame-ancestors zasad niż 'self' celowo ani przez pomyłkę.

Pojedyncze cudzysłowy są wymagane dla wartości ciągu przekazanej do ContentSecurityFrameAncestorsPolicy:

Nieobsługiwane wartości:none, self

Obsługiwane wartości:'none', 'self'

Dodatkowe opcje obejmują określanie co najmniej jednego źródła hosta i źródeł schematu.

Aby uzyskać informacje na temat wpływu na zabezpieczenia, zobacz Blazor. Aby uzyskać więcej informacji, zobacz Wymuszanie zasad zabezpieczeń zawartości dla ASP.NET Core Blazor i CSP: frame-ancestors (dokumentacja MDN).

Wyłącz kompresję odpowiedzi dla gorącego przeładowania

W przypadku korzystania z Przeładowywania na gorąco, wyłącz Response Compression Middleware w Development środowisku. Niezależnie od tego, czy jest używany domyślny kod z szablonu projektu, zawsze należy wywołać UseResponseCompression najpierw w potoku przetwarzania żądań.

W pliku Program:

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

Negocjacje międzymiędzyźródłowe po stronie klienta na potrzeby uwierzytelniania

W tej sekcji wyjaśniono, jak skonfigurować bazowego klienta SignalR do wysyłania poświadczeń, takich jak pliki cookie lub nagłówki uwierzytelniania HTTP.

Użyj SetBrowserRequestCredentials, aby ustawić Include na żądania między źródłami 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);
    }
}

Gdy zostanie zbudowane połączenie koncentratora, przypisz HttpMessageHandler do opcji HttpMessageHandlerFactory.

private HubConnectionBuilder? hubConnection;

...

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

W poprzednim przykładzie adres URL połączenia koncentratora jest konfigurowany na bezwzględny adres URI /chathub. Identyfikator URI można również ustawić za pomocą ciągu, na przykład https://signalr.example.com, lub za pośrednictwem konfiguracji. Navigation jest wstrzykniętym NavigationManagerelementem .

Aby uzyskać więcej informacji, zobacz ASP.NET Core SignalR konfigurację.

Renderowanie po stronie klienta

Jeśli wstępne renderowanie zostało skonfigurowane, następuje ono przed nawiązaniem połączenia klienta z serwerem. Aby uzyskać więcej informacji, zobacz Trwałość stanu wstępnego ASP.NET CoreBlazor.

Jeśli wstępne renderowanie zostało skonfigurowane, następuje ono przed nawiązaniem połączenia klienta z serwerem. Aby uzyskać więcej informacji, zobacz następujące artykuły:

Rozmiar renderowanego stanu wstępnego oraz limit rozmiaru komunikatu SignalR

Duży wstępny rozmiar stanu może przekroczyć limit rozmiaru komunikatu obwodu BlazorSignalR, co skutkuje następującym:

  • Obwód SignalR nie inicjalizuje się z powodu błędu na kliencie: Circuit host not initialized.
  • Interfejs użytkownika ponownego nawiązywania połączenia na kliencie jest wyświetlany, gdy obwód ulegnie awarii. Odzyskiwanie nie jest możliwe.

Aby rozwiązać ten problem, użyj jednej z następujących metod:

  • Zmniejsz ilość danych umieszczanych w stanie wstępnie utworzonym.
  • Zwiększ limit SignalR rozmiaru wiadomości. OSTRZEŻENIE: Zwiększenie limitu może zwiększyć ryzyko ataków typu "odmowa usługi" (DoS).

Dodatkowe zasoby po stronie klienta

Używanie koligacji sesji (sesji sticky) na potrzeby hostingu farmy internetowej po stronie serwera

Gdy używane są więcej niż jeden serwer zaplecza, aplikacja musi zaimplementować przylepność sesji, nazywaną sesjami typu sticky. Koligacja sesji gwarantuje, że obwód klienta ponownie łączy się z tym samym serwerem, jeśli połączenie zostanie przerwane, co jest ważne, ponieważ stan klienta jest przechowywany tylko w pamięci serwera, który po raz pierwszy ustanowił obwód klienta.

Następujący błąd jest zgłaszany przez aplikację, która nie włączyła przylegania sesji w środowisku serwerowym:

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

Aby uzyskać więcej informacji na temat afinitetu sesji w hostingu usługi Azure App Service, zobacz Blazor.

Usługa platformy Azure SignalR

Opcjonalna Usługa SignalR Azure działa w połączeniu z SignalR hubem aplikacji, aby skalować aplikację po stronie serwera do dużej liczby współbieżnych połączeń. Ponadto globalne zasięg usługi i centra danych o wysokiej wydajności znacznie pomagają zmniejszyć opóźnienia ze względu na lokalizację geograficzną.

Usługa nie jest wymagana w przypadku Blazor aplikacji hostowanych w usłudze aplikacja systemu Azure Lub Azure Container Apps, ale może być przydatna w innych środowiskach hostingu:

  • Aby ułatwić skalowanie połączeń w poziomie.
  • Obsługa dystrybucji globalnej.

Aby uzyskać więcej informacji, zobacz Hostuj i wdrażaj aplikacje po stronie serwera ASP.NET Core .

Opcje obsługi obwodu po stronie serwera

Skonfiguruj obwód za pomocą polecenia CircuitOptions. Wyświetl wartości domyślne w źródle odniesienia.

Uwaga

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Odczytywanie lub ustawianie opcji w pliku Program za pomocą delegata opcji AddInteractiveServerComponents. Symbol {OPTION} zastępczy reprezentuje opcję, a {VALUE} symbol zastępczy jest wartością.

W pliku Program:

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

Odczytywanie lub ustawianie opcji w pliku Program za pomocą delegata opcji AddServerSideBlazor. Symbol {OPTION} zastępczy reprezentuje opcję, a {VALUE} symbol zastępczy jest wartością.

W pliku Program:

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

Przeczytaj lub ustaw opcje w Startup.ConfigureServices za pomocą delegata opcji w AddServerSideBlazor. Symbol {OPTION} zastępczy reprezentuje opcję, a {VALUE} symbol zastępczy jest wartością.

W Startup.ConfigureServices pliku :Startup.cs

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

Aby skonfigurować HubConnectionContext, użyj HubConnectionContextOptions z AddHubOptions. Wyświetl wartości domyślne opcji kontekstu połączenia centrum w źródle referencyjnym. W celu opisu opcji w dokumentacji SignalR, zobacz konfigurację SignalR ASP.NET Core. Symbol {OPTION} zastępczy reprezentuje opcję, a {VALUE} symbol zastępczy jest wartością.

Uwaga

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).

W pliku Program:

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

W pliku Program:

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

W Startup.ConfigureServices pliku :Startup.cs

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

Ostrzeżenie

Wartość domyślna to MaximumReceiveMessageSize 32 KB. Zwiększenie wartości może zwiększyć ryzyko ataków typu "odmowa usługi" (DoS).

Blazor opiera się na MaximumParallelInvocationsPerClient ustawionym na 1, co jest wartością domyślną. Aby uzyskać więcej informacji, zobacz MaximumParallelInvocationsPerClient > 1 przerywa przekazywanie plików w Blazor Server trybie (dotnet/aspnetcore #53951).

Aby uzyskać informacje na temat zarządzania pamięcią, zobacz Zarządzanie pamięcią we wdrożonych aplikacjach po stronie Blazor serwera ASP.NET Core.

Blazor opcje koncentratora

Skonfiguruj opcje MapBlazorHub, aby kontrolować HttpConnectionDispatcherOptions centrum Blazor. Wyświetl wartości domyślne opcji dyspozytora połączeń koncentratora w źródle referencyjnym. Symbol {OPTION} zastępczy reprezentuje opcję, a {VALUE} symbol zastępczy jest wartością.

Uwaga

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Umieść wywołanie do app.MapBlazorHub po wywołaniu do app.MapRazorComponents w pliku Program aplikacji:

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

Konfigurowanie koncentratora używanego przez AddInteractiveServerRenderMode z MapBlazorHub kończy się niepowodzeniem z powodu błędu AmbiguousMatchException.

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

Aby obejść problem dotyczący aplikacji przeznaczonych dla platformy .NET 8/9, wykonaj następujące podejście.

W górnej Program części pliku dodaj instrukcję using dla polecenia Microsoft.AspNetCore.Http.Connections:

using Microsoft.AspNetCore.Http.Connections;

Gdzie MapRazorComponents jest wywoływana, należy połączyć następującą konwencję punktu końcowego z :RazorComponentsEndpointConventionBuilder

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .Add(e =>
    {
        var metadata = e.Metadata;
        var dispatcherOptions = metadata.OfType<HttpConnectionDispatcherOptions>().FirstOrDefault();

        if (dispatcherOptions != null)
        {
            dispatcherOptions.CloseOnAuthenticationExpiration = true;
        }
    });

Aby uzyskać więcej informacji, zobacz następujące zasoby:

Podaj opcje app.MapBlazorHub w pliku Program aplikacji.

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

Podaj opcje do konfiguracji routingu punktu końcowego app.MapBlazorHub.

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

Maksymalny rozmiar komunikatu odbioru

Ta sekcja dotyczy tylko projektów implementujących SignalR.

Maksymalny rozmiar komunikatu przychodzącego SignalR dozwolony dla metod centrum jest ograniczony ( HubOptions.MaximumReceiveMessageSize domyślnie: 32 KB). SignalR wiadomości większe niż MaximumReceiveMessageSize wywołują błąd. Struktura nie nakłada limitu rozmiaru komunikatu SignalR z centrum na klienta.

Jeśli SignalR logowanie nie jest ustawione na Debug lub Trace, w konsoli narzędzi deweloperskich przeglądarki pojawia się tylko błąd dotyczący wielkości komunikatu:

Błąd: Połączenie zostało rozłączone z powodu błędu "Błąd: Serwer zwrócił błąd podczas zamykania: połączenie zostało zamknięte z powodu błędu".

Gdy SignalR rejestrowanie po stronie serwera jest ustawione na Debug lub Trace, na rejestrowaniu po stronie serwera pojawia się InvalidDataException błąd dotyczący rozmiaru komunikatu.

appsettings.Development.json:

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

Błąd:

System.IO.InvalidDataException: przekroczono maksymalny rozmiar komunikatu 32768B. Rozmiar komunikatu można skonfigurować w obszarze AddHubOptions.

Jedno podejście polega na zwiększeniu limitu przez ustawienie MaximumReceiveMessageSize w Program pliku. Poniższy przykład ustawia maksymalny rozmiar komunikatu odbioru na 64 KB:

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

Zwiększenie limitu rozmiaru SignalR komunikatów przychodzących wiąże się z kosztem wymagania większej ilości zasobów serwera i zwiększa ryzyko ataków typu "odmowa usługi" (DoS). Ponadto odczytywanie dużej ilości zawartości do pamięci jako ciągi znaków lub tablice bajtów może również spowodować alokacje, które słabo współpracują z modułem odśmiecenia pamięci, co powoduje dodatkowe straty wydajności.

Lepszym rozwiązaniem do odczytywania dużych ładunków jest wysłanie zawartości w mniejszych fragmentach i przetworzenie ładunku jako Stream. Można tego używać podczas odczytywania dużych ładunków JSON przeznaczonych do współpracy z JavaScript (JS) lub jeśli dane współpracy są dostępne jako surowe bajty. Przykład przedstawiający wysyłanie dużych ładunków binarnych w aplikacjach po stronie serwera, które używają technik podobnych do InputFile, można znaleźć w aplikacji przykładowej Binary Submit oraz w Przykładzie Komponentu BlazorInputLargeTextArea.

Uwaga

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Formularze, które przetwarzają duże ładunki za pomocą SignalR, mogą również bezpośrednio korzystać z przesyłania strumieniowego JS. Aby uzyskać więcej informacji, zobacz Wywoływanie metod platformy .NET z funkcji Języka JavaScript w programie ASP.NET Core Blazor. Aby zapoznać się z przykładem formularzy <textarea> przesyłających strumieniowo dane do serwera, zobacz Rozwiązywanie problemów z formularzami ASP.NET CoreBlazor.

Jedno podejście polega na zwiększeniu limitu przez ustawienie MaximumReceiveMessageSize w Program pliku. Poniższy przykład ustawia maksymalny rozmiar komunikatu odbioru na 64 KB:

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

Zwiększenie limitu rozmiaru SignalR komunikatów przychodzących wiąże się z kosztem wymagania większej ilości zasobów serwera i zwiększa ryzyko ataków typu "odmowa usługi" (DoS). Ponadto odczytywanie dużej ilości zawartości do pamięci jako ciągi znaków lub tablice bajtów może również spowodować alokacje, które słabo współpracują z modułem odśmiecenia pamięci, co powoduje dodatkowe straty wydajności.

Lepszym rozwiązaniem do odczytywania dużych ładunków jest wysłanie zawartości w mniejszych fragmentach i przetworzenie ładunku jako Stream. Można tego używać podczas odczytywania dużych ładunków JSON przeznaczonych do współpracy z JavaScript (JS) lub jeśli dane współpracy są dostępne jako surowe bajty. Aby zapoznać się z przykładem wysyłania dużych ładunków binarnych w Blazor Server, które używają technik podobnych do InputFile składnika, zobacz aplikację przykładową Binary Submit i składnik przykładowy BlazorInputLargeTextArea.

Uwaga

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Formularze, które przetwarzają duże ładunki za pomocą SignalR, mogą również bezpośrednio korzystać z przesyłania strumieniowego JS. Aby uzyskać więcej informacji, zobacz Wywoływanie metod platformy .NET z funkcji Języka JavaScript w programie ASP.NET Core Blazor. Aby zapoznać się z przykładem formularzy, które przesyłają strumieniowo dane w aplikacji <textarea>, zapoznaj się z artykułem Blazor Server.

Zwiększ limit, ustawiając MaximumReceiveMessageSize w Startup.ConfigureServices.

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

Zwiększenie limitu rozmiaru SignalR komunikatów przychodzących wiąże się z kosztem wymagania większej ilości zasobów serwera i zwiększa ryzyko ataków typu "odmowa usługi" (DoS). Ponadto odczytywanie dużej ilości zawartości do pamięci jako ciągi znaków lub tablice bajtów może również spowodować alokacje, które słabo współpracują z modułem odśmiecenia pamięci, co powoduje dodatkowe straty wydajności.

Podczas tworzenia kodu, który przesyła dużą ilość danych, należy wziąć pod uwagę następujące wskazówki:

  • Podziel dane na mniejsze elementy i wysyłaj segmenty danych sekwencyjnie do momentu odebrania wszystkich danych przez serwer.
  • Nie przydzielaj dużych obiektów w kodzie JS i C#.
  • Nie blokuj głównego wątku interfejsu użytkownika przez długi czas podczas wysyłania lub odbierania danych.
  • Zwolnij zajętą pamięć po zakończeniu lub anulowaniu procesu.
  • Wymuś następujące dodatkowe wymagania dotyczące zabezpieczeń:
    • Zadeklaruj maksymalny rozmiar pliku lub danych, który można przekazać.
    • Zadeklaruj minimalną szybkość przekazywania od klienta do serwera.
  • Po odebraniu danych przez serwer dane mogą być następujące:
    • Tymczasowo przechowywane w buforze pamięci do momentu zebrania wszystkich segmentów.
    • Skonsumować natychmiast. Na przykład dane mogą być przechowywane natychmiast w bazie danych lub zapisywane na dysku w miarę odbierania poszczególnych segmentów.

Blazor Konfiguracja ścieżki punktu końcowego Hubu po stronie serwera

W pliku Program wywołaj MapBlazorHub, aby zamapować element BlazorHub na domyślną ścieżkę aplikacji. Skrypt Blazor (blazor.*.js) automatycznie wskazuje na punkt końcowy utworzony przez MapBlazorHub.

Odzwierciedlanie stanu połączenia po stronie serwera w interfejsie użytkownika

Jeśli klient wykryje utracone połączenie z serwerem, domyślny interfejs użytkownika jest wyświetlany użytkownikowi, gdy klient próbuje ponownie nawiązać połączenie:

Domyślny interfejs użytkownika do ponownego nawiązywania połączenia.

Domyślny interfejs użytkownika do ponownego nawiązywania połączenia.

Jeśli ponowne nawiązanie połączenia nie powiedzie się, użytkownik zostanie poinstruowany, aby ponowić próbę lub ponownie załadować stronę:

Domyślny interfejs użytkownika ponawiania prób.

Domyślny interfejs użytkownika ponawiania prób.

Jeśli ponowne nawiązanie połączenia powiedzie się, stan użytkownika jest często utracony. Kod niestandardowy można dodać do dowolnego składnika w celu zapisania i ponownego załadowania stanu użytkownika w przypadku niepowodzeń połączenia. Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor przegląd zarządzania stanem oraz zarządzanie stanem po stronie serwera ASP.NET Core Blazor.

Aby utworzyć elementy interfejsu użytkownika, które śledzą stan ponownego połączenia, w poniższej tabeli opisano:

  • Zestaw klas CSS components-reconnect-* (klasa CSS kolumny), które są ustawiane lub wyłączane przez Blazor na elemencie z idcomponents-reconnect-modal.
  • Zdarzenie components-reconnect-state-changed (kolumna zdarzenia), które wskazuje zmianę stanu ponownego połączenia.
Klasa CSS Zdarzenie Wskazuje...
components-reconnect-show show Utracone połączenie. Klient próbuje ponownie nawiązać połączenie. Pokazano modalne ponowne połączenie.
components-reconnect-hide hide Aktywne połączenie zostanie ponownie nawiązane z serwerem. Model ponownego nawiązywania połączenia jest zamknięty.
components-reconnect-retrying retrying Klient próbuje ponownie nawiązać połączenie.
components-reconnect-failed failed Ponowne nawiązywanie połączenia nie powiodło się, prawdopodobnie z powodu awarii sieci.
components-reconnect-rejected rejected Ponowne połączenie odrzucone.

Gdy zmiana stanu ponownego połączenia w components-reconnect-state-changed jest failed, wywołaj Blazor.reconnect() w JavaScript, aby spróbować ponownego połączenia.

Gdy zmiana stanu ponownego połączenia jest rejected, serwer został osiągnięty, ale odmówił połączenia, a stan użytkownika na serwerze zostanie utracony. Aby ponownie załadować aplikację, wywołaj metodę location.reload() w języku JavaScript. Ten stan połączenia może spowodować, że:

  • Występuje awaria obwodu po stronie serwera.
  • Klient jest odłączony wystarczająco długo, aby serwer usunął stan użytkownika. Wystąpienia składników użytkownika są usuwane.
  • Serwer jest uruchamiany ponownie lub proces roboczy aplikacji jest odzyskiwany.

Deweloper dodaje odbiornik zdarzeń w elemecie modalnym ponownego łączenia, aby monitorować zmiany stanu ponownego łączenia i reagować na nie, jak pokazano w poniższym przykładzie:

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

Element z idcomponents-reconnect-max-retries wyświetla maksymalną liczbę ponownych prób ponownego połączenia:

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

Element o id z components-reconnect-current-attempt pokazuje bieżącą próbę ponownego nawiązania połączenia.

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

Element o wartościach id i components-seconds-to-next-attempt wyświetla liczbę sekund do kolejnej próby połączenia.

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

SzablonBlazor Web App projektu zawiera składnik (ReconnectModal) ze skomponowanym arkuszem Components/Layout/ReconnectModal.razor stylów i plikami JavaScript (ReconnectModal.razor.css, ReconnectModal.razor.js), które można dostosować zgodnie z potrzebami. Te pliki można zbadać w źródle referencyjnym ASP.NET Core lub sprawdzając aplikację utworzoną na podstawie szablonu projektu Blazor Web App. Składnik jest dodawany do projektu po utworzeniu projektu w programie Visual Studio z trybem renderowania Interaktywnym ustawionym na Server lub Auto lub utworzony za pomocą .NET CLI z opcją --interactivity server (ustawienie domyślne) lub --interactivity auto.

Uwaga

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Aby dostosować interfejs użytkownika, zdefiniuj pojedynczy element z id i components-reconnect-modal w zawartości elementu <body>. Poniższy przykład umieszcza element w składniku App .

App.razor:

Aby dostosować interfejs użytkownika, zdefiniuj pojedynczy element z id i components-reconnect-modal w zawartości elementu <body>. Poniższy przykład umieszcza element na stronie hosta.

Pages/_Host.cshtml:

Aby dostosować interfejs użytkownika, zdefiniuj pojedynczy element z id i components-reconnect-modal w zawartości elementu <body>. Poniższy przykład umieszcza element na stronie układu.

Pages/_Layout.cshtml:

Aby dostosować interfejs użytkownika, zdefiniuj pojedynczy element z id i components-reconnect-modal w zawartości elementu <body>. Poniższy przykład umieszcza element na stronie hosta.

Pages/_Host.cshtml:

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

Uwaga

Jeśli aplikacja renderuje więcej niż jeden element z elementem idcomponents-reconnect-modal , tylko pierwszy renderowany element otrzymuje zmiany klasy CSS w celu wyświetlenia lub ukrycia elementu.

Dodaj następujące style CSS do arkusza stylów witryny.

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

W poniższej tabeli opisano klasy CSS zastosowane do components-reconnect-modal elementu przez platformę Blazor .

Klasa CSS Wskazuje...
components-reconnect-show Utracone połączenie. Klient próbuje ponownie nawiązać połączenie. Pokaż okno modalne.
components-reconnect-hide Aktywne połączenie zostanie ponownie nawiązane z serwerem. Ukryj okno modalne.
components-reconnect-failed Ponowne nawiązywanie połączenia nie powiodło się, prawdopodobnie z powodu awarii sieci. Aby spróbować ponownie nawiązać połączenie, wywołaj metodę window.Blazor.reconnect() w języku JavaScript.
components-reconnect-rejected Ponowne połączenie odrzucone. Serwer został osiągnięty, ale odmówił połączenia, a stan użytkownika na serwerze zostanie utracony. Aby ponownie załadować aplikację, wywołaj metodę location.reload() w języku JavaScript. Ten stan połączenia może spowodować, że:
  • Występuje awaria obwodu po stronie serwera.
  • Klient jest odłączony wystarczająco długo, aby serwer usunął stan użytkownika. Wystąpienia składników użytkownika są usuwane.
  • Serwer jest uruchamiany ponownie lub proces roboczy aplikacji jest odzyskiwany.

Dostosuj opóźnienie przed wyświetleniem interfejsu użytkownika ponownego połączenia, ustawiając transition-delay właściwość w pliku CSS witryny dla elementu modalnego. W poniższym przykładzie ustawiono opóźnienie przejścia z 500 ms (wartość domyślna) na 1000 ms (1 sekunda).

wwwroot/app.css:

wwwroot/css/site.css:

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

Aby wyświetlić bieżącą próbę ponownego nawiązania połączenia, zdefiniuj element z atrybutem id o wartości components-reconnect-current-attempt. Aby wyświetlić maksymalną liczbę ponownych prób ponownego połączenia, zdefiniuj element z wartością idcomponents-reconnect-max-retries. Poniższy przykład umieszcza te elementy wewnątrz elementu modalnego ponowienia próby połączenia, zgodnie z wcześniejszym przykładem.

<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>

Kiedy pojawi się niestandardowe okno modalne do ponownego połączenia, prezentuje następującą zawartość z licznikiem prób ponownego połączenia.

Wystąpił problem z połączeniem! (Bieżąca próba ponownego połączenia: 1/8)

Renderowanie po stronie serwera

Domyślnie komponenty są wstępnie renderowane na serwerze przed nawiązaniem połączenia klienta z serwerem. Aby uzyskać więcej informacji, zobacz Trwałość stanu wstępnego ASP.NET CoreBlazor.

Domyślnie komponenty są wstępnie renderowane na serwerze przed nawiązaniem połączenia klienta z serwerem. Aby uzyskać więcej informacji, zobacz Pomocnik tagów składników w programie ASP.NET Core.

Monitorowanie aktywności obwodu po stronie serwera

Monitoruj aktywność obwodu przychodzącego CreateInboundActivityHandler przy użyciu metody w pliku CircuitHandler. Aktywność obwodu przychodzącego to każda aktywność wysyłana z przeglądarki do serwera, taka jak zdarzenia interfejsu użytkownika lub wywołania interoperacyjne JavaScript-.NET.

Można na przykład użyć programu obsługi działań obwodu, aby wykryć, czy klient jest w stanie bezczynności i zarejestrować jego identyfikator obwodu (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;
    }
}

Zarejestruj usługę Program w pliku. Poniższy przykład konfiguruje domyślny limit czasu bezczynności od pięciu minut do pięciu sekund w celu przetestowania poprzedniej IdleCircuitHandler implementacji:

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

Programy obsługi działań obwodu zapewniają również podejście do uzyskiwania dostępu do usług o określonym Blazor zakresie z innychBlazor zakresów iniekcji zależności (DI). Aby uzyskać więcej informacji i przykładów, zobacz:

Blazor nowy biznes

Skonfiguruj ręczne uruchamianie obwodu Blazor w pliku SignalR dla App.razor:

Skonfiguruj ręczne uruchamianie obwodu BlazorSignalR w pliku Pages/_Host.cshtml (Blazor Server):

Skonfiguruj ręczne uruchamianie obwodu BlazorSignalR w pliku Pages/_Layout.cshtml (Blazor Server):

Skonfiguruj ręczne uruchamianie obwodu BlazorSignalR w pliku Pages/_Host.cshtml (Blazor Server):

  • autostart="false" Dodaj atrybut do tagu <script> skryptublazor.*.js.
  • Umieść skrypt wywołujący Blazor.start() po załadowaniu skryptu Blazor i wewnątrz tagu zamykającego </body> .

Gdy autostart funkcja jest wyłączona, każdy aspekt aplikacji, który nie zależy od obwodu, działa normalnie. Na przykład routing po stronie klienta działa. Jednak każdy aspekt, który zależy od obwodu, nie działa, dopóki Blazor.start() nie zostanie wywołany. Zachowanie aplikacji jest nieprzewidywalne bez ustalonego obwodu. Na przykład metody komponentów nie są wykonywane, gdy obwód jest odłączony.

Aby uzyskać więcej informacji, w tym sposób inicjowania Blazor , gdy dokument jest gotowy i jak utworzyć łańcuch do elementu JS Promise, zobacz ASP.NET Core Blazor start.

Konfigurowanie SignalR limitów czasu i utrzymania aktywności na kliencie

Skonfiguruj następujące wartości dla klienta:

  • withServerTimeout: konfiguruje limit czasu serwera w milisekundach. Jeśli ten limit czasu upłynie bez odbierania komunikatów z serwera, połączenie zostanie zakończone z powodu błędu. Domyślna wartość limitu czasu to 30 sekund. Limit czasu serwera powinien być co najmniej dwukrotnie większy niż wartość przypisana do interwału Keep-Alive (withKeepAliveInterval).
  • withKeepAliveInterval: konfiguruje interwał Keep-Alive w milisekundach (domyślny interwał, w którym ma być wysyłana polecenie ping do serwera). To ustawienie umożliwia serwerowi wykrywanie twardych rozłączeń, takich jak odłączanie komputera od sieci przez klienta. Polecenie ping występuje co najwyżej tak często, jak ping serwera. Jeśli serwer wysyła polecenia ping co pięć sekund, przypisanie wartości niższej niż 5000 (5 sekund) spowoduje, że ping będzie wysyłany co pięć sekund. Wartość domyślna to 15 sekund. Interwał Keep-Alive powinien być mniejszy lub równy połowie wartości przypisanej do limitu czasu serwera (withServerTimeout).

Poniższy przykład dla App.razor pliku (Blazor Web App) pokazuje przypisanie wartości domyślnych.

Blazor Web App:

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

Poniższy przykład dla Pages/_Host.cshtml pliku (Blazor Serverwszystkie wersje z wyjątkiem ASP.NET Core na platformie .NET 6) lub Pages/_Layout.cshtml plik (Blazor ServerASP.NET Core na platformie .NET 6).

Blazor Server:

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

W poprzednim przykładzie {BLAZOR SCRIPT} jest symbolem zastępczym dla ścieżki skryptu Blazor i nazwy pliku. Aby uzyskać informacje o lokalizacji skryptu i ścieżce do użycia, zobacz strukturę projektu ASP.NET Core.

Podczas tworzenia połączenia koncentratora w składniku, ustaw ServerTimeout (domyślnie: 30 sekund) i KeepAliveInterval (domyślnie: 15 sekund) na HubConnectionBuilder. Ustaw HandshakeTimeout(domyślnie: 15 sekund) na zbudowanym HubConnection. W poniższym przykładzie pokazano przypisanie wartości domyślnych:

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

Skonfiguruj następujące wartości dla klienta:

  • serverTimeoutInMilliseconds: limit czasu serwera w milisekundach. Jeśli ten limit czasu upłynie bez odbierania komunikatów z serwera, połączenie zostanie zakończone z powodu błędu. Domyślna wartość limitu czasu to 30 sekund. Limit czasu serwera powinien być co najmniej dwukrotnie większy niż wartość przypisana do interwału Keep-Alive (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds: domyślny interwał ping do serwera. To ustawienie umożliwia serwerowi wykrywanie twardych rozłączeń, takich jak odłączanie komputera od sieci przez klienta. Polecenie ping występuje co najwyżej tak często, jak ping serwera. Jeśli serwer wysyła polecenia ping co pięć sekund, przypisanie wartości niższej niż 5000 (5 sekund) spowoduje, że ping będzie wysyłany co pięć sekund. Wartość domyślna to 15 sekund. Interwał Keep-Alive powinien być mniejszy lub równy połowie wartości przypisanej do limitu czasu serwera (serverTimeoutInMilliseconds).

Poniższy przykład dla Pages/_Host.cshtml pliku (Blazor Serverwszystkie wersje z wyjątkiem ASP.NET Core na platformie .NET 6) lub Pages/_Layout.cshtml plik (Blazor ServerASP.NET Core na platformie .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>

W poprzednim przykładzie {BLAZOR SCRIPT} jest symbolem zastępczym dla ścieżki skryptu Blazor i nazwy pliku. Aby uzyskać informacje o lokalizacji skryptu i ścieżce do użycia, zobacz strukturę projektu ASP.NET Core.

Podczas tworzenia połączenia koncentratora w składniku ustaw ServerTimeout wartość na (domyślnie: 30 sekund), HandshakeTimeout (domyślnie: 15 sekund) i KeepAliveInterval (domyślnie: 15 sekund) na zbudowanych HubConnection. W poniższym przykładzie pokazano przypisanie wartości domyślnych:

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

Podczas zmieniania wartości limitu czasu serwera (ServerTimeout) lub interwału Keep-Alive (KeepAliveInterval):

  • Czas oczekiwania serwera powinien być co najmniej dwukrotnie większy niż wartość przypisana do okresu Keep-Alive.
  • Interwał Keep-Alive powinien być mniejszy lub równy połowie wartości przypisanej do limitu czasu serwera.

Aby uzyskać więcej informacji, zobacz sekcje Globalne błędy wdrażania i połączeń w następujących artykułach:

Dostosowywanie liczby ponownych prób i interwału ponownego połączenia po stronie serwera

Aby dostosować liczbę ponownych prób połączenia i interwał, ustaw liczbę ponownych prób (maxRetries) i okres w milisekundach dozwolonych dla każdej próby ponawiania próby (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>

W poprzednim przykładzie {BLAZOR SCRIPT} jest symbolem zastępczym dla ścieżki skryptu Blazor i nazwy pliku. Aby uzyskać informacje o lokalizacji skryptu i ścieżce do użycia, zobacz strukturę projektu ASP.NET Core.

Gdy użytkownik przechodzi z powrotem do aplikacji z odłączonym obwodem, następuje natychmiastowe ponowne nawiązanie połączenia, a nie oczekiwanie na czas trwania następnego interwału ponownego połączenia. To zachowanie ma na celu wznowienie połączenia tak szybko, jak to możliwe dla użytkownika.

Domyślny czas ponownego nawiązywania połączenia używa obliczonej strategii opóźnienia. Pierwsze kilka ponownych prób połączenia następuje w krótkim odstępie czasu, zanim zostaną wprowadzone obliczone opóźnienia między próbami. Domyślna logika obliczania interwału ponawiania prób jest szczegółem implementacyjnym, który może ulec zmianie bez powiadomienia, ale można znaleźć domyślną logikę używaną przez platformę w funkcji Blazor (źródło odwołania).

Uwaga

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Dostosuj zachowanie interwału ponawiania, określając funkcję, aby obliczyć interwał ponawiania prób. W poniższym przykładzie wycofywania wykładniczego liczba poprzednich prób ponownego połączenia jest pomnożona przez 1000 ms w celu obliczenia interwału ponawiania prób. Jeśli liczba poprzednich prób ponownego nawiązania połączenia (previousAttempts) jest większa niż maksymalny limit ponawiania prób (maxRetries), null jest przypisywana do interwału ponawiania prób (retryIntervalMilliseconds) w celu zaprzestania dalszych prób ponownego nawiązania połączenia:

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

Alternatywą jest określenie dokładnej sekwencji interwałów ponawiania prób. Po ostatnim określonym interwale ponawiania próby zatrzymują się, ponieważ funkcja retryIntervalMilliseconds zwraca undefined.

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

Aby uzyskać więcej informacji na temat uruchamiania platformy Blazor, zobacz Uruchamianie platformy ASP.NET Core Blazor.

Kontroluj, kiedy pojawia się interfejs użytkownika ponownego połączenia

Kontrolowanie, kiedy pojawia się UI do ponownego łączenia, może być przydatne w następujących sytuacjach:

  • Wdrożona aplikacja często wyświetla interfejs użytkownika ponownego nawiązywania połączenia z powodu przekroczenia limitu czasu ping wynikającego z opóźnień sieci wewnętrznej lub internetowych, więc chciałbyś zwiększyć czas opóźnienia.
  • Aplikacja powinna zgłaszać użytkownikom, że połączenie spadło wcześniej i chcesz skrócić opóźnienie.

Termin pojawienia się interfejsu użytkownika ponownego nawiązywania połączenia jest zależny od dostosowania interwałów podtrzymania aktywności (Keep-Alive) i limitów czasu na kliencie. Interfejs użytkownika ponownego nawiązywania połączenia pojawia się, gdy na kliencie zostanie osiągnięty limit czasu serwera (withServerTimeout, konfiguracja klienta sekcja). Jednak zmiana wartości parametru withServerTimeout wymaga zmian w innych ustawieniach Keep-Alive, timeoutu i handshake'u opisanych w poniższych wytycznych.

Jako ogólne zalecenia dla poniższych wskazówek:

  • Interwał Keep-Alive powinien być zgodny z konfiguracjami klienta i serwera.
  • Limity czasowe powinny być przynajmniej dwukrotnie większe niż wartość przypisana interwałowi Keep-Alive.

Konfiguracja serwera

Ustaw następujące ustawienia:

  • ClientTimeoutInterval (ustawienie domyślne: 30 sekund): Okno czasowe, w którym klienci mogą wysłać wiadomość, zanim serwer zamknie połączenie.
  • HandshakeTimeout (ustawienie domyślne: 15 sekund): Interwał używany przez serwer na wygaśnięcie przychodzących żądań uzgadniania przez klientów.
  • KeepAliveInterval (wartość domyślna: 15 sekund): interwał używany przez serwer do wysyłania pingów podtrzymujących aktywność do połączonych klientów. Należy pamiętać, że istnieje również ustawienie interwału Keep-Alive na kliencie, które powinno być zgodne z wartością serwera.

Element ClientTimeoutInterval i HandshakeTimeout można zwiększyć, a element KeepAliveInterval może pozostać taki sam. Należy pamiętać, że jeśli zmienisz wartości, upewnij się, że limity czasu są co najmniej dwukrotnie równe interwałowi Keep-Alive i że interwał Keep-Alive jest zgodny między serwerem a klientem. Aby uzyskać więcej informacji, zobacz sekcję Konfigurowanie SignalR limitów czasu i funkcji Keep-Alive na kliencie .

W poniższym przykładzie:

  • Wartość ClientTimeoutInterval jest zwiększana do 60 sekund (wartość domyślna: 30 sekund).
  • Wartość HandshakeTimeout jest zwiększana do 30 sekund (wartość domyślna: 15 sekund).
  • Parametr KeepAliveInterval nie jest ustawiony w kodzie dewelopera i używa jego wartości domyślnej 15 sekund. Zmniejszenie wartości interwału Keep-Alive zwiększa częstotliwość wysyłania poleceń ping do komunikacji, co zwiększa obciążenie aplikacji, serwera i sieci. Należy zachować ostrożność, aby uniknąć wprowadzania spadku wydajności podczas obniżania interwału Keep-Alive.

Blazor Web App (.NET 8 lub nowszy) w pliku projektu Program serwera:

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

W pliku: Blazor ServerProgram

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

Aby uzyskać więcej informacji, zobacz sekcję Opcje obsługi obwodu po stronie serwera.

Konfiguracja klientów

Ustaw następujące ustawienia:

  • withServerTimeout (wartość domyślna: 30 sekund): konfiguruje limit czasu serwera, określony w milisekundach, dla połączenia centralnego układu.
  • withKeepAliveInterval (wartość domyślna: 15 sekund): interwał określony w milisekundach, w którym połączenie wysyła komunikaty Keep-Alive.

Limit czasu serwera można zwiększyć, a interwał Keep-Alive może pozostać taki sam. Należy pamiętać, że jeśli zmienisz wartości, upewnij się, że limit czasu serwera jest co najmniej dwukrotnie większy niż wartość interwału Keep-Alive i że wartości interwału Keep-Alive są zgodne między serwerem a klientem. Aby uzyskać więcej informacji, zobacz sekcję Konfigurowanie SignalR limitów czasu i funkcji Keep-Alive na kliencie .

W poniższym przykładzie konfiguracji startowej (Blazor), dla serwera używana jest niestandardowa wartość limitu czasu wynosząca 60 sekund. Interwał Keep-Alive (withKeepAliveInterval) nie jest ustawiony i używa wartości domyślnej 15 sekund.

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>

Podczas tworzenia połączenia koncentratora w składniku ustaw limit czasu serwera (domyślnie: 30 sekund) na WithServerTimeout. Ustaw HandshakeTimeout(domyślnie: 15 sekund) na zbudowanym HubConnection. Upewnij się, że limity czasu są co najmniej dwukrotnie równe interwałowi Keep-Alive (WithKeepAliveInterval/KeepAliveInterval) i że wartość Keep-Alive jest zgodna między serwerem a klientem.

Poniższy przykład jest oparty na składniku Index w SignalR z Blazor samouczku. Limit czasu serwera jest zwiększany do 60 sekund, a limit czasu uzgadniania jest zwiększany do 30 sekund. Interwał Keep-Alive nie jest ustawiony i używa wartości domyślnej 15 sekund.

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

Ustaw następujące ustawienia:

  • serverTimeoutInMilliseconds (wartość domyślna: 30 sekund): konfiguruje limit czasu serwera, określony w milisekundach, dla połączenia centralnego układu.
  • keepAliveIntervalInMilliseconds (wartość domyślna: 15 sekund): interwał określony w milisekundach, w którym połączenie wysyła komunikaty Keep-Alive.

Limit czasu serwera można zwiększyć, a interwał Keep-Alive może pozostać taki sam. Należy pamiętać, że jeśli zmienisz wartości, upewnij się, że limit czasu serwera jest co najmniej dwukrotnie większy niż wartość interwału Keep-Alive i że wartości interwału Keep-Alive są zgodne między serwerem a klientem. Aby uzyskać więcej informacji, zobacz sekcję Konfigurowanie SignalR limitów czasu i funkcji Keep-Alive na kliencie .

W poniższym przykładzie konfiguracji startowej (Blazor), dla serwera używana jest niestandardowa wartość limitu czasu wynosząca 60 sekund. Interwał Keep-Alive (keepAliveIntervalInMilliseconds) nie jest ustawiony i używa wartości domyślnej 15 sekund.

W pliku 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>

Podczas tworzenia połączenia z koncentratorem w składniku, ustaw ServerTimeout (domyślnie: 30 sekund) i HandshakeTimeout (domyślnie: 15 sekund) na zbudowanym HubConnection. Upewnij się, że limity czasu są co najmniej dwukrotnie równe interwałowi Keep-Alive. Upewnij się, że interwał Keep-Alive jest zgodny między serwerem a klientem.

Poniższy przykład jest oparty na składniku Index w SignalR z Blazor samouczku. Wartość ServerTimeout jest zwiększana do 60 sekund i HandshakeTimeout zwiększa się do 30 sekund. Interwał Keep-Alive (KeepAliveInterval) nie jest ustawiony i używa wartości domyślnej 15 sekund.

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

Modyfikowanie programu obsługi ponownego łączenia po stronie serwera

Zdarzenia połączenia obwodu przez program obsługi ponownego nawiązywania połączenia można modyfikować, aby dostosować je do zachowań niestandardowych, takich jak:

  • Aby powiadomić użytkownika o usunięciu połączenia.
  • Aby zrealizować logowanie (z poziomu klienta), gdy obwód jest połączony.

Aby zmodyfikować zdarzenia połączenia, zarejestruj wywołania zwrotne dla następujących zmian połączenia:

  • Porzucone połączenia używają polecenia onConnectionDown.
  • Nawiązane/ponownie nawiązane połączenia korzystają z onConnectionUp.

Należy określić zarówno onConnectionDown jak i 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>

W poprzednim przykładzie {BLAZOR SCRIPT} jest symbolem zastępczym dla ścieżki skryptu Blazor i nazwy pliku. Aby uzyskać informacje o lokalizacji skryptu i ścieżce do użycia, zobacz strukturę projektu ASP.NET Core.

Programowa kontrola zachowania ponownego nawiązywania połączenia i ponownego ładowania

Blazor program automatycznie próbuje ponownie nawiązać połączenie i odświeża przeglądarkę, gdy ponowne nawiązywanie połączenia zakończy się niepowodzeniem. Aby uzyskać więcej informacji, zobacz sekcję Dostosowywanie ponownego łączenia po stronie serwera z liczbą ponownych prób i interwałem . Jednak kod dewelopera może zaimplementować niestandardową procedurę obsługi ponownego łączenia, aby przejąć pełną kontrolę nad zachowaniem ponownego nawiązywania połączenia.

Domyślne zachowanie przy ponownym nawiązywaniu połączenia wymaga od użytkownika ręcznego odświeżenia strony po niepowodzeniu ponownego nawiązywania połączenia. Jednak kod dewelopera może zaimplementować niestandardową procedurę ponownego łączenia, aby przejąć pełną kontrolę nad zachowaniem ponownego nawiązywania połączenia, w tym zaimplementować automatyczne odświeżanie strony po nieudanych próbach ponownego połączenia.

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>

W poprzednim przykładzie {BLAZOR SCRIPT} jest symbolem zastępczym dla ścieżki skryptu Blazor i nazwy pliku. Aby uzyskać informacje o lokalizacji skryptu i ścieżce do użycia, zobacz strukturę projektu ASP.NET Core.

Utwórz następujący wwwroot/boot.js plik.

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

Aby uzyskać więcej informacji na temat uruchamiania platformy Blazor, zobacz Uruchamianie platformy ASP.NET Core Blazor.

Blazor Odłącz obwód SignalR od klienta

Blazor SignalR obwód jest odłączony, gdy unload wydarzenie na stronie jest wyzwalane. Aby rozłączyć obwód w innych scenariuszach po stronie klienta, wywołaj Blazor.disconnect w odpowiednim programie obsługi zdarzeń. W poniższym przykładzie obwód jest odłączony, gdy strona jest ukryta (pagehide zdarzenie):

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

Aby uzyskać więcej informacji na temat uruchamiania platformy Blazor, zobacz Uruchamianie platformy ASP.NET Core Blazor.

Obsługa cyklu po stronie serwera

Można zdefiniować procedurę obsługi obwodu użytkownika, która umożliwia uruchamianie kodu przy zmianie stanu tego obwodu. Obsługa obwodu jest implementowana przez dziedziczenie z CircuitHandler i rejestrowanie klasy w kontenerze usług aplikacji. Poniższy przykład procedury obsługi obwodów śledzi otwarte SignalR połączenia.

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

Procedury obsługi obwodów są rejestrowane przy użyciu DI. Instancje z zakresem są tworzone na instancję obwodu. W przykładzie powyżej przy użyciu TrackingCircuitHandler tworzona jest usługa singleton, ponieważ stan wszystkich obwodów musi być śledzony.

W pliku Program:

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

W Startup.ConfigureServices pliku :Startup.cs

services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Jeśli metody niestandardowego programu obsługi obwodu zgłaszają nieobsługiwany wyjątek, wyjątek jest śmiertelny dla obwodu. Aby tolerować wyjątki w kodzie programu obsługi lub wywoływanych metodach, opakuj kod w jednej lub więcej instrukcji try-catch z obsługą błędów i rejestrowaniem.

Gdy obwód kończy się, ponieważ użytkownik rozłączył się, a struktura czyści stan obwodu, struktura usuwa zakres di obwodu. Usunięcie zakresu powoduje usunięcie wszystkich usług DI o zakresie działania obwodu, które implementują System.IDisposable. Jeśli jakakolwiek usługa DI zgłasza nieobsługiwany wyjątek podczas usuwania, struktura rejestruje wyjątek. Aby uzyskać więcej informacji, zobacz wstrzykiwanie zależności w ASP.NET Core.

Handler obwodów po stronie serwera do przechwytywania użytkowników w celu świadczenia usług niestandardowych

Użyj CircuitHandler do przechwycenia użytkownika z AuthenticationStateProvider i ustawienia tego użytkownika w usłudze. Aby uzyskać więcej informacji i przykładowy kod, zobacz ASP.NET Core po stronie serwera i Blazor Web App dodatkowe scenariusze zabezpieczeń.

Zamykanie obwodów, gdy nie ma pozostałych składników programu Interactive Server

Składniki interaktywnego serwera obsługują zdarzenia interfejsu użytkownika w przeglądarce internetowej, korzystając z połączenia w czasie rzeczywistym, określanego jako obwód. Obwód i skojarzony z nim stan są tworzone, gdy renderowany jest główny składnik Serwera Interaktywnego. Obwód zostaje zamknięty, gdy na stronie nie ma żadnych pozostałych składników Interaktywnego Serwera, co zwalnia zasoby serwera.

Uruchamianie obwodu SignalR pod innym adresem URL

Zapobiegaj automatycznemu uruchamianiu aplikacji przez dodanie autostart="false" do tagu Blazor<script> (lokalizacji skryptu uruchamiania Blazor). Ręcznie ustanów adres URL obwodu przy użyciu Blazor.start. W poniższych przykładach użyto ścieżki /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>

Dodaj następujące wywołanie MapBlazorHub ze ścieżką centrum do potoku przetwarzania oprogramowania pośredniczącego w pliku Program aplikacji serwera.

Blazor Web Apps:

app.MapBlazorHub("/signalr");

Blazor Server:

Pozostaw istniejące wywołanie do MapBlazorHub w pliku i dodaj nowe wywołanie do MapBlazorHub ze ścieżką:

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

Imitacja uwierzytelniania systemu Windows

Uwierzytelnione połączenia koncentratora (HubConnection) są tworzone przy użyciu UseDefaultCredentials, aby wskazać użycie domyślnych poświadczeń dla żądań HTTP. Aby uzyskać więcej informacji, zobacz Uwierzytelnianie i autoryzacja na platformie ASP.NET Core SignalR.

Gdy aplikacja jest uruchomiona w usłudze IIS Express jako zalogowany użytkownik z użyciem Uwierzytelniania Windows, co prawdopodobnie oznacza konto osobiste lub służbowe użytkownika, domyślne poświadczenia to poświadczenia zalogowanego użytkownika.

Po opublikowaniu aplikacji w usługach IIS aplikacja jest uruchamiana w ramach puli aplikacji Identity. HubConnection łączy się jako konto "użytkownika" usług IIS hostujące aplikację, a nie użytkownik, który uzyskuje dostęp do strony.

Zaimplementuj podszywanie się za pomocą HubConnection, aby użyć tożsamości użytkownika przeglądającego.

W poniższym przykładzie:

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

W poprzednim kodzie NavManager jest NavigationManager, a AuthenticationStateProvider jest instancją usługi AuthenticationStateProvider (AuthenticationStateProvider dokumentacja).

Dodatkowe zasoby po stronie serwera