備註
這不是本文的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
本文說明如何使用 ASP.NET Core 來裝載和部署伺服器端 Blazor 應用程式 (Blazor Web App 和 Blazor Server 應用程式)。
主機組態值
伺服器端 Blazor 應用程式可以接受一般主機設定值。
部署
Blazor 使用伺服器端託管模型,在伺服器上從 ASP.NET Core 應用程式中執行。 UI 更新、事件處理及 JavaScript 呼叫透過 SignalR 連線進行處理。
需要能夠裝載 ASP.NET Core 應用程式的網路伺服器。 Visual Studio 包含伺服器端應用程式專案範本。 如需 Blazor 專案範本的詳細資訊,請參閱 ASP.NET Core Blazor 專案結構。
在發行組態中發佈應用程式並部署 bin/Release/{TARGET FRAMEWORK}/publish
資料夾的內容,其中 {TARGET FRAMEWORK}
預留位置是目標架構。
延展性
在考慮單一伺服器的可擴縮性 (擴大) 時,隨著使用者需求的增加,應用程式可用的記憶體可能是應用程式耗盡的第一個資源。 伺服器上的可用記憶體會影響:
- 伺服器可支援的作用中線路數目。
- 用戶端上的 UI 延遲。
如需建置安全且可調整伺服器端 Blazor 應用程式的指導,請參閱下列資源:
對於最小的 Hello World 樣式的應用程式,每個線路使用大約 250 KB 的記憶體。 線路的大小取決於應用程式的程式碼,以及與每個元件相關聯的狀態維護需求。 建議您在應用程式和基礎結構的開發期間測量資源的需求量,但以下基準可以作為規劃部署目標的起點:如果您預期應用程式支援 5,000 位並行使用者,請考慮為應用程式預定至少 1.3 GB 的伺服器記憶體 (或每位使用者約 273 KB)。
SignalR 設定
SignalR 的裝載和縮放情況適用於使用 Blazor 的 SignalR 應用程式。
如需 SignalR 應用程式中 Blazor 的詳細資訊,包括設定指導,請參閱 ASP.NET Core BlazorSignalR 指導。
運輸
使用 Blazor 時,選擇 WebSockets 作為 SignalR 傳輸可以達到最佳效果,因為延遲會較低、可靠性會更高,而且安全性會改善。 長輪詢 是當 WebSocket 無法使用或應用程式被明確設定為使用長輪詢時由 SignalR 使用的技術。
如果使用長輪詢,主控台會顯示警告:
無法使用長輪詢後援傳輸來透過 WebSockets 連線。 這可能是因為 VPN 或 Proxy 封鎖該連線。
全域部署和連線失敗
全域部署至地理資料中心的建議:
- 將應用程式部署至大部分使用者所在的區域。
- 將跨洲時流量延遲會增加的情形納入考量。 若要控制重新連線 UI 的外觀,請參閱 ASP.NET Core BlazorSignalR 指導。
- 考慮使用 Azure SignalR 服務。
Azure App Service
在 Azure App Service 上託管時需要設定 WebSockets 和工作階段親和性,也稱為應用程式要求路由 (ARR) 親和性。
備註
Azure App Service 上的 Blazor 應用程式不需要 Azure SignalR Service。
在 Azure App Service 中為應用程式註冊啟用下列項目:
- WebSockets 以允許 WebSockets 傳輸運作。 預設的設定為 [關閉]。
- 會話綁定可將使用者的請求路由回同一個 App 服務實例。 默認設定為 [開啟]。
- 在 Azure 入口網站中,瀏覽至 [應用程式服務] 中的 Web 應用程式。
- 開啟 設定>設定。
- 將 [Web 通訊端] 設定為 On。
- 確認 Session affinity 已設定為 On。
Azure SignalR 服務
可選的 Azure SignalR Service 與應用程式的 SignalR 集線器配合使用,可將伺服器端應用程式擴充至大量的同時連線。 此外,服務的全球觸達和高效能資料中心可大幅協助降低因地理位置造成的延遲。
在 Azure App Service 或 Azure Container Apps 中託管的 Blazor 應用程式不需要這項服務,但在其他託管環境中可能會有所幫助:
- 為了方便連線向外延展。
- 處理全球配送。
Azure SignalR 服務搭配 SDK v1.26.1 或更新版本,支援 SignalR 具狀態的重新連線(WithStatefulReconnect)。
如果應用程式使用 Long Polling 或回退到 Long Polling 而不是 WebSockets ,您可能需要設定最大輪詢間隔 (MaxPollIntervalInSeconds
,預設值:5 秒,限制:1-300 秒),它定義了 Azure SignalR 服務中 Long Polling 連線允許的最大輪詢間隔。 如果下一次輪詢請求未在最大輪詢時間間隔內到達,服務會關閉用戶端連線。。
有關如何將服務新增為生產部署的依賴,請參閱 Publish an ASP.NET Core SignalR app to Azure App Service。
如需詳細資訊,請參閱:
- Azure SignalR Service
- 什麼是 Azure SignalR 服務?
- ASP.NET Core SignalR 生產環境託管和擴展
- 將 ASP.NET Core SignalR 應用程式發佈至 Azure App Service
Azure 容器應用服務
如需在 Azure 容器應用程式服務上調整伺服器端 Blazor 應用程式的更深入探索,請參閱在 Azure 上調整 ASP.NET Core 應用程式。 本教學課程說明如何建立和整合在 Azure 容器應用程式上託管應用程式所需的服務。 本節也提供了基本步驟。
遵循 Azure 容器應用程式中的工作階段親和性 (Azure 文件) 中的指導來設定 Azure 容器應用程式服務的工作階段親和性。
必須設定 ASP.NET Core 資料保護服務 (DP),以將金鑰保存在一個所有容器執行個體都可以存取的集中位置。 金鑰可以儲存在 Azure Blob 儲存體中,並使用 Azure Key Vault 進行保護。 DP 服務使用鍵來反序列化 Razor 元件。 若要設定 DP 服務以使用 Azure Blob 儲存體 和 Azure Key Vault,請參考下列 NuGet 套件:
-
Azure.Identity
:提供類別來處理 Azure 身分識別和存取管理服務。 -
Microsoft.Extensions.Azure
:提供有用的擴充方法來執行核心 Azure 設定。 -
Azure.Extensions.AspNetCore.DataProtection.Blobs
:允許在 Azure Blob 儲存體中儲存 ASP.NET Core 資料保護金鑰,以便可在 Web 應用程式的數個實例之間共用金鑰。 -
Azure.Extensions.AspNetCore.DataProtection.Keys
:啟用使用 Azure Key Vault 的鍵加密/封裝功能來保護靜態密鑰。
-
使用下列醒目的程式碼更新
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 儲存體和應用程式的金鑰保存庫。
若要建立容器應用程式受控識別,並授與 Blob 記憶體和密鑰保存庫的存取權,請完成下列步驟:
- 在 Azure 入口網站中,瀏覽至容器應用程式的概觀頁面。
- 從左側導覽中選取 [服務連接器]。
- 在頂端導覽中選取 [+ 建立]。
- 在 [建立連線] 飛出視窗中,輸入下列值:
- 容器:選取您建立用於託管應用程式的容器應用程式。
- 服務類型:選取 [Blob 儲存體]。
- 訂用帳戶:選取擁有容器應用程式的訂用帳戶。
-
連線名稱:輸入
scalablerazorstorage
的名稱。 - 用戶端類型:選取 [.NET],然後選取 [下一步]。
- 選取 系統指派的受控識別,然後選取 下一步。
- 使用預設的網路設定,然後選取 [下一步]。
- 在 Azure 驗證設定之後,選取 [建立]。
對金鑰保存庫重複上述設定。 在 [基本] 索引標籤中選取適當的金鑰保存庫服務和金鑰。
IIS
使用 IIS 時,請啟用:
如需詳細資訊,請參閱將 ASP.NET Core 應用程式發佈到 IIS 中的指導和外部 IIS 資源交叉連結。
Kubernetes
使用下列 用於工作階段親和性的 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"
使用 Nginx 的 Linux
遵循 ASP.NET Core SignalR 應用程式的指導,並進行下列變更:
- 將
location
路徑從/hubroute
(location /hubroute { ... }
) 變更為根路徑/
(location / { ... }
)。 - 移除 Proxy 緩衝的組態 (
proxy_buffering off;
),因為此設定僅適用於伺服器傳送的事件 (SSE),而這與 Blazor 應用程式的用戶端伺服器互動無關。
如需詳細資訊和組態方面的指導,請參閱下列資源:
- ASP.NET Core SignalR 生產環境託管與擴展
- 在 Linux 上使用 Nginx 裝載 ASP.NET Core
- 設定 ASP.NET Core 以處理 Proxy 伺服器和負載平衡器
- NGINX 作為 WebSocket Proxy
- WebSocket 代理轉發
- 請洽詢非 Microsoft 支援論壇上的開發人員:
使用 Apache 的 Linux
若要在 Linux 上的 Apache 後面託管 Blazor 應用程式,請為 HTTP 和 WebSocket 流量設定 ProxyPass
。
在以下範例中:
- 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
檢查瀏覽器主控台是否有 WebSocket 錯誤。 範例錯誤:
- Firefox 無法與位於 ws://the-domain-name.tld/_blazor?id=XXX 的伺服器建立連線
- 錯誤:無法啟動傳輸 'WebSockets':錯誤:傳輸發生錯誤。
- 錯誤:無法啟動傳輸 'LongPolling': TypeError: this.transport 未定義
- 錯誤:無法使用任何可用的傳輸連接到伺服器。 WebSocket 失敗
- 錯誤:如果連線未處於「已連線」狀態,則無法傳送資料。
如需詳細資訊和組態方面的指導,請參閱下列資源:
- 設定 ASP.NET Core 以處理 Proxy 伺服器和負載平衡器
- Apache 文件
- 請洽詢非 Microsoft 支援論壇上的開發人員:
測量網路延遲
JS Interop 可用來測量網路延遲,如下列範例所示。
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();
}
}
}
為了獲得合理的 UI 體驗,我們建議持續的 UI 延遲為 250 毫秒或更短。