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


Размещение и развертывание серверных Blazor приложений

Примечание.

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

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

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

В этой статье объясняется, как размещать и развертывать серверные Blazor приложения (Blazor Web Apps и Blazor Server приложения) с помощью ASP.NET Core.

Значения конфигурации хоста

Серверные приложения Blazor могут принимать значения конфигурации универсального хоста.

Развертывание

Использование серверной модели Blazor размещения выполняется на сервере из приложения ASP.NET Core. Обновления пользовательского интерфейса, обработка событий и вызовы JavaScript обрабатываются по подключению SignalR .

Веб-сервер, способный размещать приложение ASP.NET Core, требуется. Visual Studio включает шаблон проекта приложения на стороне сервера. Дополнительные сведения о шаблонах проектов Blazor см. в статье Структура проекта ASP.NET Core Blazor.

Опубликуйте приложение в конфигурации Release и разверните содержимое папки bin/Release/{TARGET FRAMEWORK}/publish, где {TARGET FRAMEWORK} заполнитель является целевой средой.

Масштабируемость

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

  • Количество активных каналов, которые может поддерживать сервер.
  • Задержка пользовательского интерфейса на клиенте.

Рекомендации по созданию безопасных и масштабируемых серверных Blazor приложений см. в следующих ресурсах:

Каждый канал использует около 250 КБ памяти для минимального приложения в стиле Hello World. Размер схемы зависит от кода приложения и требований к поддержанию состояния, связанных с каждым компонентом. Мы рекомендуем измерять требования к ресурсам во время разработки приложения и инфраструктуры, но следующие базовые показатели могут быть отправной точкой при планировании целевого объекта развертывания. Если приложение будет поддерживать 5000 одновременных пользователей, рассмотрите возможность бюджетирования по крайней мере 1,3 ГБ памяти сервера приложению (или около 273 КБ на пользователя).

Blazor WebAssembly Предварительная загрузка статических активов

Компонент ResourcePreloader в содержимом головного компонента App используется для ссылки на статические ресурсы App.razor. Компонент помещается после базового тега URL-адреса (<base>):

<ResourcePreloader />

Компонент Razor используется вместо <link> элементов, так как:

  • Компонент позволяет базовому URL-адресу (<base> значению атрибута тега href ) правильно определить корневой каталог Blazor приложения в приложении ASP.NET Core.
  • Эту функцию можно удалить, удалив ResourcePreloader тег компонента из App компонента. Это полезно в случаях, когда приложение использует обратныйloadBootResource вызов для изменения URL-адресов.

SignalRКонфигурация

SignalRУсловия размещения и масштабирования применяются к Blazor приложениям, которые используют SignalR.

Дополнительные сведения о SignalR в приложениях Blazor, включая руководство по настройке, см. в руководствах по ASP.NET Core BlazorSignalR.

Транспорты

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

При использовании долгого опрашивания появится предупреждение в консоли.

Не удалось подключиться через WebSockets, поэтому используется резервный транспорт Long Polling. Это может быть связано с VPN или прокси-сервером, блокирующим подключение.

Глобальные сбои развертывания и подключения

Рекомендации по глобальным развертываниям в географических центрах обработки данных:

  • Разверните приложение в регионах, где проживает большинство пользователей.
  • Учитывайте повышенную задержку трафика на разных континентах. Сведения об управлении внешним видом пользовательского интерфейса повторного подключения см. в ASP.NET BlazorSignalR основных руководствах.
  • Рассмотрите возможность использования службы AzureSignalR.

Служба приложений Azure

Для размещения в Службе приложений Azure требуется настройка WebSockets и привязки сеансов, также называемой привязкой маршрутизации запросов приложений (ARR).

Примечание.

Приложению Blazor в Службе приложений Azure не требуется служба AzureSignalR.

Включите следующее для регистрации приложения в Службе приложений Azure:

  • WebSockets позволяет транспорту WebSockets выполнять функцию. Значение по умолчанию — Off.
  • Привязка сессий для направления запросов от пользователя обратно к тому же экземпляру службы приложений. Значение по умолчанию — Включено.
  1. В портале Azure перейдите к веб-приложению в Службах приложений.
  2. Откройте Настройки>Конфигурация.
  3. Установите Web sockets в положение Вкл.
  4. Убедитесь, что сходство сеансов установлено в значение Включено.

Служба Azure SignalR

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

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

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

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

Если приложение использует длинный опрос или возвращается к long Polling вместо WebSockets, возможно, потребуется настроить максимальный интервал опроса (MaxPollIntervalInSecondsпо умолчанию: 5 секунд, ограничение: 1–300 секунд), который определяет максимальный интервал опроса, разрешенный для подключений long Polling в службе Azure SignalR . Если следующий запрос опроса не поступает в пределах максимального интервала опроса, служба закрывает клиентское подключение.

Инструкции по добавлению службы в рабочую среду см. в статье "Публикация приложения ASP.NET Core SignalR в Службе приложений Azure".

Дополнительные сведения можно найти здесь

Приложения контейнеров Azure

Более подробное изучение масштабируемых серверных Blazor приложений в службе "Приложения контейнеров Azure" см. в статье "Масштабирование ASP.NET Core Apps в Azure". В этом руководстве объясняется, как создавать и интегрировать службы, необходимые для размещения приложений в приложениях контейнеров Azure. Основные шаги также приведены в этом разделе.

  1. Настройте службу приложений контейнеров Azure для сопоставления сеансов, следуя инструкциям в руководстве по сопоставлению сеансов в приложениях контейнеров Azure (документация Azure).

  2. Служба ASP.NET Core Data Protection (DP) должна быть настроена для сохранения ключей в централизованном расположении, к которому могут получить доступ все экземпляры контейнеров. Ключи можно хранить в хранилище BLOB-объектов Azure и защищать с помощью Azure Key Vault. Служба DP использует ключи для десериализации Razor компонентов. Чтобы настроить службу DP для использования Azure Blob Storage и Azure Key Vault, обратитесь к следующим пакетам NuGet:

    • Azure.Identity: предоставляет классы для работы со службами управления идентификацией и доступом Azure.
    • Microsoft.Extensions.Azure: предоставляет полезные методы расширения для выполнения основных конфигураций Azure.
    • Azure.Extensions.AspNetCore.DataProtection.Blobs: позволяет хранить ключи ASP.NET Core Data Protection в хранилище BLOB-объектов Azure, чтобы ключи могли совместно использоваться в нескольких экземплярах веб-приложения.
    • Azure.Extensions.AspNetCore.DataProtection.Keys: включает защиту неактивных ключей с помощью функции шифрования и упаковки ключей Azure Key Vault.

    Примечание.

    Рекомендации по добавлению пакетов в приложения .NET см. в статьях, приведенных в разделе Установка пакетов и управление ими в рабочий процесс использования пакетов (документация по NuGet). Проверьте правильность версий пакета на сайте NuGet.org.

  3. Обновите Program.cs, используя выделенный код:

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

    Предыдущие изменения позволяют приложению управлять службой DP с помощью централизованной масштабируемой архитектуры. DefaultAzureCredential обнаруживает управляемое удостоверение контейнерного приложения после развертывания кода в Azure и использует его для подключения к хранилищу BLOB и хранилищу ключей приложения.

  4. Чтобы создать управляемое удостоверение приложения контейнера и предоставить ему доступ к хранилищу BLOB-объектов и хранилищу ключей, выполните следующие действия.

    1. На портале Azure перейдите на страницу обзора приложения-контейнера.
    2. Выберите сервисный коннектор в левой навигационной панели.
    3. Нажмите кнопку "+ Создать " в верхней области навигации.
    4. В меню "Создание подключения" введите следующие значения:
      • Контейнер. Выберите приложение-контейнер, созданное для размещения приложения.
      • Тип службы: выберите Blob Storage.
      • Подписка: выберите подписку, которая владеет приложением-контейнером.
      • Имя подключения: введите имя scalablerazorstorage.
      • Тип клиента: выберите .NET и нажмите кнопку "Далее".
    5. Выберите системно назначенное управляемое удостоверение и выберите Далее.
    6. Используйте параметры сети по умолчанию и нажмите кнопку "Далее".
    7. После проверки параметров в Azure нажмите кнопку Создать.

    Повторите предыдущие настройки для хранилища ключей. Выберите соответствующую службу хранилища ключей и ключ на вкладке "Основные сведения".

Примечание.

В предыдущем примере используется DefaultAzureCredential для упрощения проверки подлинности при разработке приложений, развертываемых в Azure, путем объединения учетных данных, используемых в средах размещения Azure с учетными данными, используемыми в локальной разработке. При переходе в рабочую среду альтернатива является лучшим выбором, например ManagedIdentityCredential. Дополнительные сведения см. в статье Аутентификация размещенных в Azure приложений .NET в ресурсах Azure с помощью управляемого удостоверения, назначаемого системой.

IIS

При использовании IIS включите следующее:

Для получения дополнительной информации см. руководство и перекрёстные ссылки на внешние ресурсы IIS в разделе Публикация приложения ASP.NET Core в IIS.

Kubernetes

Создайте определение ingress со следующими аннотациями Kubernetes для привязки сессий:

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

Следуйте указаниям для приложения ASP.NET Core SignalR со следующими изменениями:

  • Измените location путь с /hubroute (location /hubroute { ... }) на корневой путь / (location / { ... }).
  • Удалите конфигурацию для буферизации прокси-сервера (proxy_buffering off;), так как этот параметр применяется только к событиям, отправленным сервером (SSE), которые не относятся к взаимодействию между клиентом и сервером в приложении Blazor.

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

Linux с Apache

Чтобы разместить Blazor приложение за Apache на Linux, настройте ProxyPass для HTTP и WebSockets трафика.

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

  • Kestrel сервер работает на хост-компьютере.
  • Приложение прослушивает трафик через порт 5000.
ProxyPreserveHost   On
ProxyPassMatch      ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass           /_blazor ws://localhost:5000/_blazor
ProxyPass           / http://localhost:5000/
ProxyPassReverse    / http://localhost:5000/

Включите следующие модули:

a2enmod   proxy
a2enmod   proxy_wstunnel

Проверьте консоль браузера на наличие ошибок WebSockets. Примеры ошибок:

  • Firefox не может установить подключение к серверу в ws://the-domain-name.tld/_blazor?id=XXX
  • Ошибка: не удалось запустить транспорт WebSockets: ошибка: произошла ошибка с транспортом.
  • Ошибка: не удалось запустить транспорт LongPolling: TypeError: this.transport не определен
  • Ошибка. Не удается подключиться к серверу с любым из доступных транспортов. Сбой WebSockets
  • Ошибка. Не удается отправить данные, если подключение не находится в состоянии "Подключено".

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

Измерение задержки сети

JS Интероперабельность можно использовать для измерения задержки сети, как показано в следующем примере.

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

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