Udostępnij przez


Hostowanie i wdrażanie aplikacji po stronie Blazor serwera

Uwaga / Notatka

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 aktualną wersją, zobacz artykuł w wersji .NET 10.

W tym artykule wyjaśniono, jak hostować i wdrażać aplikacje po stronie Blazor serwera (Blazor Web Appi Blazor Server aplikacje) przy użyciu platformy ASP.NET Core.

Wartości konfiguracji hosta

Aplikacje po stronie Blazor serwera mogą akceptować ogólne wartości konfiguracji hosta.

Wdrożenie

Użycie modelu Blazor hostingu po stronie serwera jest wykonywane na serwerze z poziomu aplikacji ASP.NET Core. Aktualizacje interfejsu użytkownika, obsługa zdarzeń i wywołania języka JavaScript są obsługiwane za pośrednictwem SignalR połączenia.

Wymagany jest serwer internetowy obsługujący aplikację ASP.NET Core. Program Visual Studio zawiera szablon projektu aplikacji po stronie serwera. Aby uzyskać więcej informacji na Blazor temat szablonów projektów, zobacz Blazor projektu ASP.NET Core).

Opublikuj aplikację w konfiguracji wydania i wdróż zawartość bin/Release/{TARGET FRAMEWORK}/publish folderu, w {TARGET FRAMEWORK} którym symbol zastępczy jest platformą docelową.

Skalowalność

Biorąc pod uwagę skalowalność pojedynczego serwera (skalowanie w górę), pamięć dostępna dla aplikacji jest prawdopodobnie pierwszym zasobem, który aplikacja wyczerpała w miarę wzrostu zapotrzebowania użytkownika. Dostępna pamięć na serwerze ma wpływ na:

  • Liczba aktywnych obwodów, które może obsługiwać serwer.
  • Opóźnienie interfejsu użytkownika na kliencie.

Aby uzyskać wskazówki dotyczące tworzenia bezpiecznych i skalowalnych aplikacji po stronie Blazor serwera, zobacz następujące zasoby:

Każdy obwód używa około 250 KB pamięci dla minimalnej aplikacji hello world stylu. Rozmiar obwodu zależy od kodu aplikacji i wymagań dotyczących konserwacji stanu skojarzonych z każdym składnikiem. Zalecamy mierzenie zapotrzebowania na zasoby podczas opracowywania aplikacji i infrastruktury, ale następujący punkt odniesienia może stanowić punkt początkowy planowania celu wdrożenia: Jeśli oczekujesz, że aplikacja będzie obsługiwać 5000 współbieżnych użytkowników, rozważ budżetowanie co najmniej 1,3 GB pamięci serwera do aplikacji (lub ok. 273 KB na użytkownika).

Blazor WebAssembly wstępne ładowanie zasobów statycznych

Składnik ResourcePreloader w zawartości head składnika App (App.razor) jest używany do odwołania do statycznych zasobów Blazor. Składnik jest umieszczany po tagu podstawowego adresu URL (<base>):

<ResourcePreloader />

Składnik Razor jest używany zamiast elementów, <link> ponieważ:

  • Składnik umożliwia bazowemu adresowi URL (wartości atrybutu <base> tagu href) poprawne zidentyfikowanie korzenia aplikacji Blazor w aplikacji ASP.NET Core.
  • Tę funkcję można usunąć, usuwając ResourcePreloader tag składnika ze App składnika. Jest to przydatne w przypadkach, gdy aplikacja używa wywołania zwrotnegoloadBootResource do modyfikowania adresów URL.

SignalR konfiguracja

SignalRWarunki hostingu i skalowania mają zastosowanie do Blazor aplikacji korzystających z programu SignalR.

Aby uzyskać więcej informacji na SignalR temat aplikacji, w Blazor tym wskazówek dotyczących konfiguracji, zobacz Blazor dotyczące platformy ASP.NET CoreSignalR.

Transport

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

Jeśli jest używane długie sondowanie, zostanie wyświetlone ostrzeżenie konsoli:

Nie można nawiązać połączenia za pośrednictwem obiektów WebSocket przy użyciu transportu rezerwowego Long Polling. Może to być spowodowane blokowaniem połączenia przez sieć VPN lub serwer proxy.

Globalne błędy wdrażania i połączenia

Zalecenia dotyczące wdrożeń globalnych w centrach danych geograficznych:

  • Wdróż aplikację w regionach, w których większość użytkowników mieszka.
  • Weź pod uwagę zwiększone opóźnienie ruchu na różnych kontynentach. Aby kontrolować wygląd interfejsu użytkownika ponownego łączenia, zobacz Blazor ASP.NET CoreSignalR.
  • Rozważ użycie usługiSignalRAzure.

Azure App Service

Hosting w usłudze aplikacja systemu Azure wymaga konfiguracji obiektów WebSocket i koligacji sesji, nazywanej również koligacją routingu żądań aplikacji (ARR).

Uwaga / Notatka

Blazor Aplikacja w usłudze aplikacja systemu Azure nie wymaga SignalR platformy Azure.

Włącz następujące informacje dotyczące rejestracji aplikacji w usłudze aplikacja systemu Azure Service:

  • Protokoły WebSocket umożliwiające działanie transportu obiektów WebSocket. Domyślne ustawienie to Wyłączone.
  • Koligacja sesji w celu kierowania żądań od użytkownika z powrotem do tego samego wystąpienia usługi App Service. Ustawienie domyślne to Włączone.
  1. W witrynie Azure Portal przejdź do aplikacji internetowej w usłudze App Services.
  2. Otwórz pozycję Konfiguracja ustawień>.
  3. Ustaw pozycję Gniazda internetowe na wartość Włączone.
  4. Sprawdź, czy koligacja sesji jest ustawiona na włączone.

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.

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

W przypadku, gdy aplikacja używa długiego sondowania lub wraca do długiego sondowania zamiast obiektów WebSocket, może być konieczne skonfigurowanie maksymalnego interwału sondowania (MaxPollIntervalInSecondsdomyślnie: 5 sekund, limit: 1–300 sekund), który definiuje maksymalny interwał sondowania dozwolony dla połączeń długich sondowania w usłudze Platformy Azure SignalR . Jeśli następne żądanie sondowania nie zostanie dostarczone w maksymalnym interwale sondowania, usługa zamknie połączenie klienta.

Aby uzyskać wskazówki dotyczące dodawania usługi jako zależności do wdrożenia produkcyjnego, zobacz Publikowanie aplikacji ASP.NET Core SignalR w usłudze aplikacja systemu Azure Service.

Aby uzyskać więcej informacji, zobacz:

Azure Container Apps

Aby dowiedzieć się więcej na temat skalowania aplikacji po stronie Blazor serwera w usłudze Azure Container Apps, zobacz Scaling ASP.NET Core Apps on Azure (Skalowanie aplikacji ASP.NET Core Apps na platformie Azure). W tym samouczku wyjaśniono, jak tworzyć i integrować usługi wymagane do hostowania aplikacji w usłudze Azure Container Apps. Podstawowe kroki są również dostępne w tej sekcji.

  1. Skonfiguruj usługę Azure Container Apps pod kątem koligacji sesji, postępując zgodnie ze wskazówkami w temacie Koligacja sesji w usłudze Azure Container Apps (dokumentacja platformy Azure).

  2. Usługa ASP.NET Core Data Protection (DP) musi być skonfigurowana do utrwalania kluczy w scentralizowanej lokalizacji, do których mogą uzyskiwać dostęp wszystkie wystąpienia kontenerów. Klucze mogą być przechowywane w usłudze Azure Blob Storage i chronione za pomocą usługi Azure Key Vault. Usługa DP używa kluczy do deserializacji Razor składników. Aby skonfigurować usługę DP do korzystania z usług Azure Blob Storage i Azure Key Vault, zapoznaj się z następującymi pakietami NuGet:

    Uwaga / Notatka

    Aby uzyskać wskazówki dotyczące dodawania pakietów do aplikacji .NET, zobacz artykuły w Instalowanie i zarządzanie pakietami oraz w proces korzystania z pakietów (dokumentacja NuGet). Sprawdź prawidłowe wersje pakietów pod adresem NuGet.org.

  3. Zaktualizuj Program.cs za pomocą następującego wyróżnionego kodu:

    using Azure.Identity;
    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.Extensions.Azure;
    
    var builder = WebApplication.CreateBuilder(args);
    var BlobStorageUri = builder.Configuration["AzureURIs:BlobStorage"];
    var KeyVaultURI = builder.Configuration["AzureURIs:KeyVault"];
    
    builder.Services.AddRazorPages();
    builder.Services.AddHttpClient();
    builder.Services.AddServerSideBlazor();
    
    builder.Services.AddAzureClientsCore();
    
    builder.Services.AddDataProtection()
                    .PersistKeysToAzureBlobStorage(new Uri(BlobStorageUri),
                                                    new DefaultAzureCredential())
                    .ProtectKeysWithAzureKeyVault(new Uri(KeyVaultURI),
                                                    new DefaultAzureCredential());
    var app = builder.Build();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapRazorPages();
    
    app.Run();
    

    Powyższe zmiany umożliwiają aplikacji zarządzanie usługą DP przy użyciu scentralizowanej, skalowalnej architektury. DefaultAzureCredential odkrywa zarządzaną tożsamość aplikacji kontenera po wdrożeniu kodu na platformie Azure i wykorzystuje ją do połączenia się z magazynem obiektów blob oraz skarbcem kluczy aplikacji.

  4. Aby utworzyć zarządzaną tożsamość aplikacji kontenerowej i przyznać jej dostęp do magazynu obiektów Blob oraz magazynu Key Vault, wykonaj następujące kroki:

    1. W witrynie Azure Portal przejdź do strony przeglądu aplikacji kontenera.
    2. Wybierz pozycję Łącznik usługi w obszarze nawigacji po lewej stronie.
    3. Wybierz pozycję + Utwórz z górnej nawigacji.
    4. W menu wysuwany Tworzenie połączenia wprowadź następujące wartości:
      • Kontener: wybierz utworzoną aplikację kontenera do hostowania aplikacji.
      • Typ usługi: wybierz pozycję Blob Storage.
      • Subskrypcja: wybierz subskrypcję, która jest właścicielem aplikacji kontenera.
      • Nazwa połączenia: wprowadź nazwę scalablerazorstorage.
      • Typ klienta: wybierz pozycję .NET , a następnie wybierz przycisk Dalej.
    5. Wybierz pozycję Tożsamość zarządzana przypisana przez system i wybierz pozycję Dalej.
    6. Użyj domyślnych ustawień sieciowych i wybierz pozycję Dalej.
    7. Po zweryfikowaniu ustawień przez platformę Azure wybierz pozycję Utwórz.

    Powtórz powyższe ustawienia dla magazynu kluczy. Wybierz odpowiednią usługę i klucz magazynu kluczy na karcie Podstawy .

Uwaga / Notatka

W poprzednim przykładzie użyto DefaultAzureCredential metody upraszczania uwierzytelniania podczas opracowywania aplikacji wdrażanych na platformie Azure przez połączenie poświadczeń używanych w środowiskach hostingu platformy Azure z poświadczeniami używanymi podczas programowania lokalnego. Podczas przechodzenia do środowiska produkcyjnego lepszym wyborem jest skorzystanie z alternatywy, takiej jak ManagedIdentityCredential. Aby uzyskać więcej informacji, zobacz Uwierzytelnianie aplikacji platformy .NET hostowanych na platformie Azure do zasobów platformy Azure przy użyciu tożsamości zarządzanej przypisanej przez system.

IIS

W przypadku korzystania z usług IIS włącz:

Aby uzyskać więcej informacji, zobacz wskazówki i zewnętrzne linki do zasobów usług IIS w temacie Publikowanie aplikacji ASP.NET Core w usługach IIS.

Kubernetes

Utwórz definicję ruchu przychodzącego z następującymi adnotacjami Kubernetes dla koligacji sesji:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: <ingress-name>
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "affinity"
    nginx.ingress.kubernetes.io/session-cookie-expires: "14400"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "14400"

System Linux z serwerem Nginx

Postępuj zgodnie ze wskazówkami dotyczącymi SignalR z następującymi zmianami:

  • Zmień ścieżkę location z /hubroute (location /hubroute { ... }) na ścieżkę / główną (location / { ... }).
  • Usuń konfigurację buforowania serwera proxy (proxy_buffering off;), ponieważ ustawienie ma zastosowanie tylko do zdarzeń wysłanych przez serwer (SSE), które nie są istotne dla Blazor interakcji między klientem aplikacji a serwerem.

Aby uzyskać więcej informacji i wskazówek dotyczących konfiguracji, zapoznaj się z następującymi zasobami:

System Linux z serwerem Apache

Aby hostować aplikację za platformą Blazor Apache w systemie Linux, skonfiguruj ProxyPass ruch HTTP i WebSocket.

W poniższym przykładzie:

  • Kestrel serwer jest uruchomiony na maszynie hosta.
  • Aplikacja nasłuchuje ruchu na porcie 5000.
ProxyPreserveHost   On
ProxyPassMatch      ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass           /_blazor ws://localhost:5000/_blazor
ProxyPass           / http://localhost:5000/
ProxyPassReverse    / http://localhost:5000/

Włącz następujące moduły:

a2enmod   proxy
a2enmod   proxy_wstunnel

Sprawdź konsolę przeglądarki pod kątem błędów obiektów WebSocket. Przykładowe błędy:

  • Firefox nie może nawiązać połączenia z serwerem pod adresem ws://the-domain-name.tld/_blazor?id=XXX
  • Błąd: Nie można uruchomić transportu "WebSockets": Błąd: Wystąpił błąd podczas transportu.
  • Błąd: Nie można uruchomić transportu "LongPolling": TypeError: this.transport jest niezdefiniowany
  • Błąd: Nie można nawiązać połączenia z serwerem z żadnym z dostępnych transportów. Nie można uruchomić obiektów WebSocket
  • Błąd: Nie można wysłać danych, jeśli połączenie nie znajduje się w stanie "Połączono".

Aby uzyskać więcej informacji i wskazówek dotyczących konfiguracji, zapoznaj się z następującymi zasobami:

Mierzenie opóźnienia sieci

JS Interop może służyć do mierzenia opóźnienia sieci, jak pokazano w poniższym przykładzie.

MeasureLatency.razor:

@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}

W przypadku rozsądnego środowiska interfejsu użytkownika zalecamy trwałe opóźnienie interfejsu użytkownika 250 ms lub mniej.