注
これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
Warnung
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
Blazor PWA は、ユーザーがアプリをアクティブに使用していない場合でも、バックエンド サーバーから プッシュ通知 (データ メッセージ) を受信して表示できます。 たとえば、別のユーザーがインストールされている PWA でアクションを実行したとき、またはバックエンド サーバー アプリと直接対話しているアプリまたはユーザーがアクションを実行したときに、プッシュ通知を送信できます。
プッシュ通知を使用して次の手順を実行します。
- 重要な問題が発生したことをユーザーに通知し、アプリに戻るように促します。
- アプリに格納されているデータ (ニュース フィードなど) を更新して、プッシュ通知が発行されたときにオフラインになっている場合でも、ユーザーが次回アプリに戻ったときに新しいデータを取得できるようにします。
プッシュ通知の送信、受信、表示のメカニズムは、 Blazor WebAssemblyに依存しません。 プッシュ通知の送信は、任意のテクノロジを使用できるバックエンド サーバーによって実装されます。 クライアントでのプッシュ通知の受信と表示は、サービス ワーカー 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 クラス ライブラリ プロジェクト (Razor) のパブリック静的アセット フォルダー (BlazingPizza.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 でエンコードされた文字列に変換する方法」を参照してください。(スタック オーバーフロー)。
サブスクリプション オブジェクトと通知サブスクリプション エンドポイントがサーバー上に作成されます。 エンドポイントは、暗号化トークンを含むプッシュ通知サブスクリプション データを含むクライアント 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) は、データベースに格納されます。 ユーザーごとに 1 つのサブスクリプションのみが格納されます。 または、ユーザーが異なるブラウザーまたはデバイスから複数のサブスクリプションを登録することを許可することもできます。
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);
}
サブスクリプションを要求し、サブスクリプションが確立された場合に通知をサブスクライブするメソッドを定義します。 後で使用するために、サブスクリプションをデータベースに保存します。
Blazing Pizza アプリの Checkout コンポーネントでは、 RequestNotificationSubscriptionAsync メソッドは次の作業を実行します。
- サブスクリプションは、JS を呼び出すことにより
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 の追加テストのアクセス許可をリセットするには、ブラウザーのアドレス バーの左側にある [情報] アイコン (🛈) を選択し、次の図に示すように 通知 を Ask (既定値) に戻します。
通知を送信する
通知を送信するには、転送中のデータを保護するために、サーバーで複雑な暗号化操作を実行する必要があります。 複雑さの大部分は、Blazing Pizza アプリのサーバー プロジェクト (WebPush) によって使用されるサードパーティの NuGet パッケージBlazingPizza.Serverによってアプリに対して処理されます。
SendNotificationAsync メソッドは、キャプチャされたサブスクリプションを使用して注文通知をディスパッチします。 次のコードでは、WebPush API を使用して通知をディスパッチします。 通知のペイロードは JSON シリアル化され、メッセージと URL が含まれます。 メッセージがユーザーに表示され、URL により、ユーザーは通知に関連付けられているピザの注文に到達できます。 その他の通知シナリオでは、必要に応じて追加のパラメーターをシリアル化できます。
注意事項
次の例では、秘密キーを提供するためにセキュリティで保護された方法を使用することをお勧めします。
Development環境でローカルで作業する場合は、シークレット マネージャー ツールを使用して秘密キーをアプリに提供できます。
Development、Staging、およびProduction環境では、Azure Key Vault と Azure Managed Identities を使用できますが、証明書の秘密キーをキー・ボールトから取得するためには、証明書がエクスポート可能な秘密キーを持っている必要があります。
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...ログに記録したときに次のページが読み込まれるまで有効になりません。 サービス ワーカーの更新に苦労している場合は、ブラウザーの開発者ツール コンソールの [ アプリケーション ] タブを使用します。
[サービス ワーカー] で、[更新] を選択するか、[登録解除] を使用して、次の読み込み時に新しい登録を強制します。
上記のコードが配置され、ユーザーによって新しい注文が行われると、アプリの組み込みデモ ロジックごとに 10 秒後に 注文が配信状態として Out に移動します。 ブラウザーがプッシュ通知を受け取ります。
ユーザーに通知する、ピザの注文が発送されたことを示すポップアップ通知。
Google Chrome または Microsoft Edge でアプリを使用している場合、ユーザーが Blazing Pizza アプリをアクティブに使用していない場合でも、通知が表示されます。 ただし、ブラウザーが実行されている必要があります。または、次回ブラウザーを開いた際に通知が表示されます。
インストールされている PWA を使用する場合、ユーザーがアプリを実行していない場合でも通知を配信する必要があります。
通知のクリックを処理する
notificationclick イベント ハンドラーを登録して、デバイスでプッシュ通知を選択 (クリック) するユーザーを処理します。
-
event.notification.closeを呼び出して通知を閉じます。 -
clients.openWindowを呼び出して、新しい最上位レベルの参照コンテキストを作成し、メソッドに渡された URL を読み込みます。
Blazing Pizza アプリの次の例では、通知に関連する注文の注文状態ページにユーザーを移動します。 URL は、 event.notification.data.url パラメーターによって提供されます。このパラメーターは、通知のペイロードでサーバーによって送信されます。
サービス ワーカー ファイル (service-worker.js) 内:
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data.url));
});
PWA がデバイスにインストールされている場合、PWA はデバイスに表示されます。 PWA がインストールされていない場合、ユーザーはブラウザーでアプリのページに移動します。
その他のリソース
ASP.NET Core