Delen via


Apps aan de serverzijde Blazor hosten en implementeren

Opmerking

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikelvoor de huidige release.

Dit artikel legt uit hoe u server-side Blazor apps (Blazor Web Apps en Blazor Server apps) host en implementeert met behulp van ASP.NET Core.

Hostconfiguratiewaarden

Apps aan de serverzijde Blazor kunnen algemene hostconfiguratiewaarden accepteren.

Implementatie

Bij gebruik van een server-side hostingmodel wordt Blazor binnen een ASP.NET Core-app op de server uitgevoerd. Ui-updates, gebeurtenisafhandeling en JavaScript-aanroepen worden verwerkt via een SignalR verbinding.

Een webserver die een ASP.NET Core-app kan hosten, is vereist. Visual Studio bevat een app-projectsjabloon aan de serverzijde. Zie Blazorvoor meer informatie over Blazor projectsjablonen.

Publiceer een app in de Release-configuratie en implementeer de inhoud van de bin/Release/{TARGET FRAMEWORK}/publish map, waarbij de {TARGET FRAMEWORK} tijdelijke aanduiding op het doelframework doelt.

Schaalbaarheid

Wanneer u rekening houdt met de schaalbaarheid van één server (omhoog schalen), is het geheugen dat beschikbaar is voor een app waarschijnlijk de eerste resource die door de app wordt uitgeput naarmate de vraag van de gebruiker toeneemt. Het beschikbare geheugen op de server is van invloed op het volgende:

  • Aantal actieve circuits dat een server kan ondersteunen.
  • UI-latentie aan de clientkant.

Zie de volgende bronnen voor hulp bij het bouwen van beveiligde en schaalbare apps aan de serverzijde Blazor :

Elk circuit gebruikt ongeveer 250 KB geheugen voor een minimale Hello World-app. De grootte van een circuit is afhankelijk van de code van de app en de onderhoudsvereisten voor de status die aan elk onderdeel zijn gekoppeld. We raden u aan om de resourcevereisten tijdens de ontwikkeling voor uw app en infrastructuur te meten, maar de volgende basislijn kan een uitgangspunt zijn bij het plannen van uw implementatiedoel: als u verwacht dat uw app ondersteuning biedt voor 5000 gelijktijdige gebruikers, kunt u overwegen om ten minste 1,3 GB servergeheugen te budgetteren voor de app (of ongeveer 273 KB per gebruiker).

Blazor WebAssembly statische asset vooraf laden

Het ResourcePreloader onderdeel in de hoofdinhoud van het App onderdeel (App.razor) wordt gebruikt om te verwijzen naar Blazor statische assets. Het onderdeel wordt geplaatst na de basis-URL-tag (<base>):

<ResourcePreloader />

Een Razor onderdeel wordt gebruikt in plaats van <link> elementen omdat:

  • Met het onderdeel kan de basis-URL (de kenmerkwaarde <base> van de tag) de hoofdmap van de href app binnen een ASP.NET Core-app correct herkennen.
  • De functie kan worden verwijderd door de ResourcePreloader onderdeeltag uit het App onderdeel te verwijderen. Dit is handig in gevallen waarin de app een loadBootResource callback gebruikt om URL's te wijzigen.

configuratie van SignalR

SignalRDe hosting- en schaalvoorwaarden zijn van toepassing op Blazor apps die gebruikmaken van SignalR.

Zie SignalRvoor meer informatie over Blazor appsBlazor, inclusief configuratierichtlijnen.

Transporten

Blazor werkt het beste bij het gebruik van WebSockets als transport SignalR vanwege een lagere latentie, betere betrouwbaarheid en verbeterde beveiliging. Long Polling wordt gebruikt SignalR wanneer WebSockets niet beschikbaar is of wanneer de app expliciet is geconfigureerd voor het gebruik van Long Polling.

Er wordt een consolewaarschuwing weergegeven als Long Polling wordt gebruikt:

Aansluiting via WebSockets is mislukt, wordt gebruik gemaakt van long polling als back-uptransport. Dit kan het gevolg zijn van een VPN of proxy die de verbinding blokkeert.

Algemene implementatie- en verbindingsfouten

Aanbevelingen voor wereldwijde implementaties naar geografische datacenters:

  • Implementeer de app in de regio's waar de meeste gebruikers zich bevinden.
  • Let op de toegenomen latentie voor verkeer over continenten. Zie ASP.NET Core-richtlijnen BlazorSignalRvoor het beheren van het uiterlijk van de gebruikersinterface voor opnieuw verbinden.
  • Overweeg het gebruik van de Azure-serviceSignalR.

Azure App Service

Hosting in Azure App Service vereist configuratie voor WebSockets en sessieaffiniteit, ook wel ARR-affiniteit (Application Request Routing) genoemd.

Opmerking

Voor een Blazor app in Azure App Service is geen Azure SignalR Service vereist.

Schakel het volgende in voor de registratie van de app in Azure App Service:

  • WebSockets om het WebSockets-transport mogelijk te maken. De standaardinstelling is uitgeschakeld.
  • Sessieaffiniteit om aanvragen van een gebruiker terug te sturen naar hetzelfde App Service-exemplaar. De standaardinstelling is Aan.
  1. Navigeer in Azure Portal naar de web-app in App Services.
  2. Open Instellingenconfiguratie>.
  3. Stel websockets in op Aan.
  4. Controleer of sessieaffiniteit is ingesteld op Aan.

Azure SignalR Dienst

De optionele Azure SignalR Service- werkt in combinatie met de SignalR hub van de app voor het omhoog schalen van een app aan de serverzijde naar een groot aantal gelijktijdige verbindingen. Bovendien helpen het wereldwijde bereik en de hoogwaardige prestatiedatacenters van de service aanzienlijk om de latentie te verminderen vanwege geografische factoren.

De service is niet vereist voor Blazor apps die worden gehost in Azure App Service of Azure Container Apps, maar kan nuttig zijn in andere hostingomgevingen:

  • Om het uitschalen van verbindingen te vergemakkelijken.
  • Globale distributie beheren.

De Azure SignalR-service met SDK v1.26.1 of hoger ondersteunt SignalR opnieuw verbinden met toestandsbehoud (WithStatefulReconnect).

In het geval dat de app gebruikmaakt van Long Polling of terugvalt op Long Polling in plaats van WebSockets, moet u mogelijk het maximale poll-interval configureren (MaxPollIntervalInSecondsstandaard: 5 seconden, limiet: 1-300 seconden), waarmee het maximale poll-interval wordt gedefinieerd dat is toegestaan voor Long Polling-verbindingen in de Azure-service SignalR . Als de volgende poll-aanvraag niet binnen het maximale poll-interval binnenkomt, sluit de service de clientverbinding.

Zie Een ASP.NET Core-app SignalR publiceren naar Azure App Service voor hulp bij het toevoegen van de service als een afhankelijkheid aan een productie-implementatie.

Zie voor meer informatie:

Azure Container Apps - een dienst van Microsoft waarmee je containers kunt uitvoeren en beheren in de cloud.

Zie Blazor voor meer informatie over het schalen van apps aan de serverzijde in de Azure Container Apps-service. In de zelfstudie wordt uitgelegd hoe u de services maakt en integreert die nodig zijn voor het hosten van apps in Azure Container Apps. In deze sectie vindt u ook basisstappen.

  1. Configureer de Azure Container Apps-service voor sessieaffiniteit door de richtlijnen in sessieaffiniteit in Azure Container Apps (Azure-documentatie) te volgen.

  2. De service ASP.NET Core Data Protection (DP) moet worden geconfigureerd om sleutels te behouden op een centrale locatie waartoe alle containerinstanties toegang hebben. De sleutels kunnen worden opgeslagen in Azure Blob Storage en worden beveiligd met Azure Key Vault. De DP-service gebruikt de sleutels om Razor-onderdelen te deserialiseren. Raadpleeg de volgende NuGet-pakketten om de DP-service te configureren voor het gebruik van Azure Blob Storage en Azure Key Vault:

    Opmerking

    Zie de artikelen onder Pakketten installeren en beheren bij Verbruikswerkstroom voor pakketten (NuGet-documentatie)voor begeleiding bij het toevoegen van pakketten in .NET-apps. Bevestig de juiste pakketversies op NuGet.org.

  3. Werk Program.cs bij met de volgende gemarkeerde code:

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

    Met de voorgaande wijzigingen kan de app de DP-service beheren met behulp van een gecentraliseerde, schaalbare architectuur. DefaultAzureCredential detecteert de beheerde identiteit van de container-app nadat de code is geïmplementeerd in Azure en gebruikt deze om verbinding te maken met blobopslag en de sleutelkluis van de app.

  4. Voer de volgende stappen uit om de beheerde identiteit voor de container-app te creëren en toegang te verlenen tot blobopslag en een sleutelkluis:

    1. Navigeer in Azure Portal naar de overzichtspagina van de container-app.
    2. Selecteer ServiceConnector in het linkernavigatievenster.
    3. Selecteer + Maken in de bovenste navigatiebalk.
    4. Voer in het flyoutmenu Verbinding maken de volgende waarden in:
      • Container: Selecteer de container-app die u hebt gemaakt om uw app te hosten.
      • Servicetype: Selecteer Blob Storage.
      • Abonnement: Selecteer het abonnement dat eigenaar is van de container-app.
      • Verbindingsnaam: Voer een naam in van scalablerazorstorage.
      • Clienttype: Selecteer .NET en selecteer vervolgens 'Volgende'.
    5. Selecteer Door het systeem toegewezen beheerde identiteit en selecteer Volgende.
    6. Gebruik de standaardnetwerkinstellingen en selecteer Volgende.
    7. Nadat Azure de instellingen heeft gevalideerd, selecteert u maken.

    Herhaal de voorgaande instellingen voor de Key Vault. Selecteer de juiste sleutelkluisservice en -sleutel op het tabblad Basisbeginselen .

Opmerking

In het voorgaande voorbeeld wordt gebruikgemaakt DefaultAzureCredential van het vereenvoudigen van verificatie bij het ontwikkelen van apps die in Azure worden geïmplementeerd door referenties te combineren die worden gebruikt in Azure-hostingomgevingen met referenties die worden gebruikt in lokale ontwikkeling. Wanneer u overstapt op productie, is een alternatief een betere keuze, zoals ManagedIdentityCredential. Zie Azure-gehoste .NET-apps verifiëren bij Azure-resources met behulp van een door het systeem toegewezen beheerde identiteit voor meer informatie.

IIS

Wanneer u IIS gebruikt, schakelt u het volgende in:

Zie de richtlijnen en externe IIS-resourcekruiskoppelingen in Een ASP.NET Core-app publiceren naar IIS voor meer informatie.

Kubernetes

Maak een ingress-definitie met de volgende Kubernetes-annotaties voor sessieaffiniteit:

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"

Linux met Nginx

Volg de richtlijnen voor een ASP.NET Core SignalR-app met de volgende wijzigingen:

  • Wijzig het location pad van /hubroute (location /hubroute { ... }) in het hoofdpad / (location / { ... }).
  • Verwijder de configuratie voor proxybuffering (proxy_buffering off;) omdat de instelling alleen van toepassing is op Server-Sent Gebeurtenissen (SSE), die niet relevant zijn voor Blazor client-serverinteracties van de app.

Raadpleeg de volgende bronnen voor meer informatie en configuratierichtlijnen:

Linux met Apache

Als u een Blazor app wilt hosten achter Apache op Linux, configureert u ProxyPass deze voor HTTP- en WebSockets-verkeer.

In het volgende voorbeeld:

  • Kestrel de server draait op de host.
  • De app luistert naar verkeer op poort 5000.
ProxyPreserveHost   On
ProxyPassMatch      ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass           /_blazor ws://localhost:5000/_blazor
ProxyPass           / http://localhost:5000/
ProxyPassReverse    / http://localhost:5000/

Schakel de volgende modules in:

a2enmod   proxy
a2enmod   proxy_wstunnel

Controleer de browserconsole op WebSockets-fouten. Voorbeeldfouten:

  • Firefox kan geen verbinding maken met de server op ws://the-domain-name.tld/_blazor?id=XXX
  • Fout: Kan het transport WebSockets niet starten: fout: er is een fout opgetreden bij het transport.
  • Fout: Kan het transport 'LongPolling' niet starten: TypeError: this.transport is undefined
  • Fout: Kan geen verbinding maken met de server met een van de beschikbare transporten. WebSockets zijn mislukt
  • Fout: kan geen gegevens verzenden als de verbinding niet de status Verbonden heeft.

Raadpleeg de volgende bronnen voor meer informatie en configuratierichtlijnen:

Netwerklatentie meten

JS interop kan worden gebruikt om de netwerklatentie te meten, zoals in het volgende voorbeeld wordt gedemonstreert.

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

Voor een redelijke gebruikersinterface-ervaring raden we u aan om een duurzame UI-latentie van 250 ms of minder te gebruiken.