備註
這不是本文的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本。
警告
此版本的 ASP.NET Core 已不再受支援。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援政策。 關於目前版本,請參閱 本文的 .NET 10 版本。
Blazor PWA 可以從後端伺服器接收和顯示 推播通知 (資料訊息),即使使用者未主動使用應用程式也是如此。 例如,當不同使用者在其已安裝的 PWA 中執行動作,或當應用程式或使用者直接與後端伺服器應用程式互動時,可以傳送推播通知。
使用推播通知來:
- 通知用戶發生重要事件,提示他們返回應用程式。
- 更新儲存在應用程式中的數據,例如新聞摘要,讓使用者在下一次返回應用程式時有全新的數據,即使發佈推播通知時脫機也一樣。
傳送、接收和顯示推播通知的機制與Blazor WebAssembly無關。 傳送推播通知是由後端伺服器實作,其可以使用任何技術。 在用戶端上接收和顯示推送通知是在 Service Worker JavaScript(JS)檔案中實作。
本文中的範例會使用推播通知,根據 Blazing Pizza Workshop PWA 示範應用程式,為披薩餐廳的客戶提供訂單狀態更新。 您不需要參與在線研討會才能使用本文,但研討會是 PWA 開發的實用簡介 Blazor 。
備註
Blazing Pizza 應用程式採用存放 庫模式 ,在 UI 層和數據存取層之間建立抽象層。 如需詳細資訊,請參閱 工作單位 (UoW) 模式 和 設計基礎結構持續性層。
建立公開和私鑰
產生用於保護推播通知的密碼編譯公開和私鑰,可以在本機上生成,例如使用 PowerShell 或 IIS,或使用線上工具。
用於本文範例代碼的佔位符:
-
{PUBLIC KEY}:公鑰。 -
{PRIVATE KEY}:私鑰。
針對本文的 C# 範例,更新 someone@example.com 電子郵件位址以符合建立自定義密鑰組時所使用的位址。
實作推播通知時,請確定密碼編譯密鑰受到安全管理:
- 密鑰產生:使用受信任的連結庫或工具來產生公開和私鑰。 避免使用弱式或過時的演算法。
- 金鑰記憶體:使用硬體安全性模組 (HSM) 或加密記憶體等安全儲存機制,安全地在伺服器上儲存私鑰。 永遠不要向客戶端公開私鑰。
- 密鑰使用方式:僅使用私鑰來簽署推播通知承載。 請確定公鑰會安全地散發給用戶端。
如需密碼編譯最佳做法的詳細資訊,請參閱 密碼編譯服務。
建立訂用帳戶
將推播通知傳送給使用者之前,應用程式必須要求使用者取得許可權。 如果他們授與接收通知的許可權,其瀏覽器會產生 訂用帳戶,其中包含應用程式可用來將通知路由傳送給使用者的一組令牌。
應用程式可以隨時取得許可權,但只有在清楚為何要訂閱應用程式通知時,我們才建議要求使用者取得許可權。 下列範例會在使用者抵達結帳頁面(Checkout 元件)時詢問使用者,因為此時很明顯用戶對於下單很認真。
如果使用者同意接收通知,下列範例會將推播通知訂閱數據傳送至伺服器,其中推播通知令牌會儲存在資料庫中以供日後使用。
新增推播通知 JS 檔案以要求訂用帳戶:
- 呼叫
navigator.serviceWorker.getRegistration以取得服務工作者的註冊。 - 呼叫
worker.pushManager.getSubscription以判斷訂用帳戶是否存在。 - 如果訂用帳戶不存在,請使用 函
PushManager.subscribe式建立新的訂用帳戶,並傳回新訂用帳戶的 URL 和令牌。
在 Blazing Pizza 應用程式中,JS 檔案被命名為 pushNotifications.js,並位於方案的 wwwroot 類別庫專案的公用靜態資產資料夾 (RazorBlazingPizza.ComponentsLibrary)。 函式 blazorPushNotifications.requestSubscription 會要求訂用帳戶。
BlazingPizza.ComponentsLibrary/wwwroot/pushNotifications.js:
(function () {
const applicationServerPublicKey = '{PUBLIC KEY}';
window.blazorPushNotifications = {
requestSubscription: async () => {
const worker = await navigator.serviceWorker.getRegistration();
const existingSubscription = await worker.pushManager.getSubscription();
if (!existingSubscription) {
const newSubscription = await subscribe(worker);
if (newSubscription) {
return {
url: newSubscription.endpoint,
p256dh: arrayBufferToBase64(newSubscription.getKey('p256dh')),
auth: arrayBufferToBase64(newSubscription.getKey('auth'))
};
}
}
}
};
async function subscribe(worker) {
try {
return await worker.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerPublicKey
});
} catch (error) {
if (error.name === 'NotAllowedError') {
return null;
}
throw error;
}
}
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
})();
備註
如需上述函式 arrayBufferToBase64 的詳細資訊,請參閱 如何將ArrayBuffer轉換成base64編碼字串?(Stack Overflow).
訂用帳戶物件和通知訂閱端點會在伺服器上建立。 端點會接收用戶端 Web API 呼叫,其中包含推播通知訂用帳戶數據,包括密碼編譯令牌。 數據會儲存在每個應用程式使用者的資料庫中。
在 Blazing Pizza 應用程式中,訂用帳戶對像是 類別 NotificationSubscription 。
P256dh和 Auth 屬性是用戶的密碼編譯令牌。
BlazingPizza.Shared/NotificationSubscription.cs:
public class NotificationSubscription
{
public int? NotificationSubscriptionId { get; set; }
public string? UserId { get; set; }
public string? Url { get; set; }
public string? P256dh { get; set; }
public string? Auth { get; set; }
}
notifications/subscribe端點定義於應用程式的MapPizzaApi擴充方法中,這會在應用程式的Program檔案中呼叫,以設定應用程式的Web API端點。 使用者的通知訂閱(NotificationSubscription),其中包含推播通知的令牌,會儲存在資料庫中。 每個使用者只能儲存一個訂用帳戶。 或者,您可以允許使用者從不同的瀏覽器或裝置註冊多個訂用帳戶。
app.MapPut("/notifications/subscribe",
[Authorize] async (
HttpContext context,
PizzaStoreContext db,
NotificationSubscription subscription) =>
{
var userId = GetUserId(context);
if (userId is null)
{
return Results.Unauthorized();
}
// Remove old subscriptions for this user
var oldSubscriptions = db.NotificationSubscriptions.Where(
e => e.UserId == userId);
db.NotificationSubscriptions.RemoveRange(oldSubscriptions);
// Store the new subscription
subscription.UserId = userId;
db.NotificationSubscriptions.Add(subscription);
await db.SaveChangesAsync();
return Results.Ok(subscription);
});
在 BlazingPizza.Client/HttpRepository.cs 中,方法 SubscribeToNotifications 會向伺服器上的訂閱端點發送 HTTP PUT 請求。
public class HttpRepository : IRepository
{
private readonly HttpClient _httpClient;
public HttpRepository(HttpClient httpClient)
{
_httpClient = httpClient;
}
...
public async Task SubscribeToNotifications(NotificationSubscription subscription)
{
var response = await _httpClient.PutAsJsonAsync("notifications/subscribe",
subscription);
response.EnsureSuccessStatusCode();
}
}
存放庫介面(BlazingPizza.Shared/IRepository.cs)包含方法簽章SubscribeToNotifications:
public interface IRepository
{
...
Task SubscribeToNotifications(NotificationSubscription subscription);
}
定義方法,以在建立訂用帳戶時要求訂閱並訂閱通知。 將訂用帳戶儲存在資料庫中以供稍後使用。
在 Checkout Blazing Pizza 應用程式的元件中, RequestNotificationSubscriptionAsync 方法會執行下列職責:
- 訂用帳戶是透過 JS Interop 建立,方法是呼叫
blazorPushNotifications.requestSubscription。 元件會將 IJSRuntime 服務注入來調用 JS 函數。 - 呼叫
SubscribeToNotifications方法以儲存訂用帳戶。
在 BlazingPizza.Client/Components/Pages/Checkout.razor 中:
async Task RequestNotificationSubscriptionAsync()
{
var subscription = await JSRuntime.InvokeAsync<NotificationSubscription>(
"blazorPushNotifications.requestSubscription");
if (subscription is not null)
{
try
{
await Repository.SubscribeToNotifications(subscription);
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
}
}
在 Checkout 元件中,RequestNotificationSubscriptionAsync會在生命週期方法中OnInitialized呼叫 ,並在元件初始化時執行。 該方法是非同步的,可以在背景中執行,其傳回的 Task 可以被捨棄。 因此,在初始化元件的異步生命週期方法中不會呼叫該方法。OnInitializedAsync。 此方法會更快速地呈現元件。
protected override void OnInitialized()
{
_ = RequestNotificationSubscriptionAsync();
}
若要示範程式代碼的運作方式,請執行 Blazing Pizza 應用程式並開始下訂單。 移至結帳畫面以查看訂用帳戶要求:
選擇 允許 並在瀏覽器開發者工具的控制台中檢查是否有錯誤。 您可以在PizzaApiExtensions的MapPut("/notifications/subscribe"...)程式代碼中設定斷點,並使用偵錯執行應用程式,以檢查來自瀏覽器的傳入資料。 數據報含端點 URL 和密碼編譯令牌。
在使用者允許或封鎖指定網站的通知之後,瀏覽器將不會再次詢問。 若要重設 Google Chrome 或 Microsoft Edge 進一步測試的許可權,請選取瀏覽器網址列左側的 [資訊] 圖示 (🛈),並將 [通知 ] 變更回 [詢問] (預設值),如下圖所示:
傳送通知
傳送通知涉及在伺服器上執行一些複雜的密碼編譯作業,以保護傳輸中的數據。 大部分的複雜度是由第三方 NuGet 套件處理,此套件 WebPush是由 Blazing Pizza 應用程式中的伺服器專案 (BlazingPizza.Server) 所使用。
SendNotificationAsync 方法使用已捕獲的訂閱分派訂單通知。 下列程式代碼使用的 WebPush API 來分派通知。 通知的有效負載會序列化為 JSON,並包含訊息和 URL。 訊息會顯示給使用者,而 URL 可讓使用者觸達與通知相關聯的披薩訂單。 其他通知情境可以視需要序列化其他參數。
謹慎
在下列範例中,我們建議使用安全方法來提供私鑰。 在環境中本地工作 Development 時,可以使用 秘密管理 工具提供私鑰給應用程式。 在 Development、 Staging、 及 Production 環境中,可以使用帶有 Azure 管理身份的Azure Key Vault,順帶一提,要從金鑰保險庫取得憑證的私鑰,該憑證必須具備可匯出的私鑰。
private static async Task SendNotificationAsync(Order order,
NotificationSubscription subscription, string message)
{
var publicKey = "{PUBLIC KEY}";
var privateKey = "{PRIVATE KEY}";
var pushSubscription = new PushSubscription(subscription.Url,
subscription.P256dh, subscription.Auth);
var vapidDetails = new VapidDetails("mailto:<someone@example.com>", publicKey,
privateKey);
var webPushClient = new WebPushClient();
try
{
var payload = JsonSerializer.Serialize(new
{
message,
url = $"myorders/{order.OrderId}",
});
await webPushClient.SendNotificationAsync(pushSubscription, payload,
vapidDetails);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error sending push notification: {ex.Message}");
}
}
上述範例可讓伺服器傳送通知,但瀏覽器不會在沒有其他邏輯的情況下回應通知。 顯示通知涵蓋在 [顯示通知] 區 段中。
瀏覽器的開發人員工具控制台顯示,在 Blazing Pizza 應用程式中下訂單後 10 秒,通知才會到達。 在 [ 應用程式] 索引標籤上,開啟 [推送傳訊 ] 區段。 選取要 開始錄製的圓圈:
顯示通知
PWA 的服務工作者(service-worker.js)必須處理推送通知,應用程式才能顯示通知。
Blazing Pizza 應用程式中的下列 push 事件處理程式 會呼叫 showNotification ,以建立作用中服務工作者的通知。
在 BlazingPizza/wwwroot/service-worker.js 中:
self.addEventListener('push', event => {
const payload = event.data.json();
event.waitUntil(
self.registration.showNotification('Blazing Pizza', {
body: payload.message,
icon: 'img/icon-512.png',
vibrate: [100, 50, 100],
data: { url: payload.url }
})
);
});
在瀏覽器記錄 Installing service worker... 並於下一頁載入之後,之前的程式碼才會生效。 當難以讓服務工作者更新時,請使用瀏覽器開發人員工具控制台中的 [ 應用程式 ] 索引標籤。 在 Service Workers 下,選擇 更新 或使用 取消註冊 來強制下一次載入進行新註冊。
由於上述程式碼已準備就緒,當使用者下新的訂單時,訂單會在 10 秒後依應用程式的內建示範邏輯移至 送貨中 狀態。 瀏覽器會收到推播通知:
在 Google Chrome 或 Microsoft Edge 中使用應用程式時,即使使用者未主動使用 Blazing Pizza 應用程式,也會顯示通知。 不過,瀏覽器必須執行,或下次開啟瀏覽器時,就會顯示通知。
使用已安裝的 PWA 時,即使使用者未執行應用程式,也應該傳遞通知。
處理通知點選
notificationclick註冊事件處理程式以處理使用者選取 (按兩下) 裝置上的推播通知:
- 呼叫
event.notification.close來關閉通知。 - 呼叫
clients.openWindow以建立新的最上層瀏覽內容,並載入傳遞至 方法的URL。
Blazing Pizza 應用程式中的下列範例會將使用者帶到與通知相關的訂單訂單狀態頁面。 URL 是由 event.notification.data.url 參數所提供,由通知承載中的伺服器傳送。
在 Service Worker 文件中(service-worker.js):
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data.url));
});
如果 PWA 安裝在裝置上,PWA 會顯示在裝置上。 如果未安裝 PWA,則會將使用者帶到其瀏覽器中的應用程式頁面。