Push (Server)
Server-side push notification dispatch for .NET. A provider-agnostic core with four built-in transports —
direct APNs (token-based .p8 / ES256 over HTTP/2, no FCM dependency for Apple), FCM (HTTP v1, with
multicast batching), Web Push (VAPID + RFC 8291), and WNS (Windows, modern Windows App SDK / Entra
auth) — plus pluggable persistence (Shiny.DocumentDb), structured targeting, an interceptor pipeline,
automatic dead-token pruning, multi-app keyed registration, and System.Diagnostics.Metrics telemetry. The
whole library is AOT/trim-safe.
- MyApp/
1<Project Sdk="Microsoft.NET.Sdk.Web">23 <PropertyGroup>4 <TargetFramework>net10.0</TargetFramework>5 <Nullable>enable</Nullable>6 <ImplicitUsings>enable</ImplicitUsings>7 <OpenApiDocumentsDirectory>.</OpenApiDocumentsDirectory>8 <MicrosoftVersion>10.0.14</MicrosoftVersion>9 <ShinyExtensionsLocalizationVersion>2.0.1</ShinyExtensionsLocalizationVersion>10 <ShinyExtensionsReflectorVersion>6.0.1</ShinyExtensionsReflectorVersion>11 <ShinyExtensionsStoresVersion>6.0.1</ShinyExtensionsStoresVersion>12 <ShinyExtensionsDIVersion>6.0.1</ShinyExtensionsDIVersion>13 <MyAppVersion>1.0.0</MyAppVersion>14 <ShinyDocumentDbVersion>14.1.0</ShinyDocumentDbVersion>15 <MediatorVersion>6.12.0</MediatorVersion>16 <NpgsqlVersion>10.0.3</NpgsqlVersion>17 <ScalarVersion>2.18.3</ScalarVersion>18 </PropertyGroup>1920 <ItemGroup>21 <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="$(MicrosoftVersion)" />22 <PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="$(MicrosoftVersion)"/>23 <PackageReference Include="Shiny.AspNet.WebHosting" Version="$(MyAppVersion)" />24 <PackageReference Include="Shiny.Extensions.Push" Version="3.0.0-beta-0008" />2526 </ItemGroup>27</Project>| Transport | Platform setup | Client app setup |
|---|---|---|
| APNs | Apple Developer setup | Apple |
| FCM | Firebase setup | Android |
| WNS | Microsoft setup | Windows |
| Web Push | VAPID keys | Web Push |
| Core (incl. APNs, FCM, Web Push & WNS) | |
| DocumentDb |
Features
Section titled “Features”| Feature | Description |
|---|---|
| Provider-agnostic core | one IPushManager; transports plug in as IPushProvider |
| Four transports in the box | direct APNs (.p8/ES256 over HTTP/2), FCM (HTTP v1), Web Push (VAPID + RFC 8291), and WNS (Windows App SDK / Entra auth) all ship in the core package; each is opt-in via AddApns / AddFcm / AddWebPush / AddWns |
| Structured, AOT-safe targeting | send to a user, tags/segments, explicit tokens, or broadcast |
| Rich payloads | title/body, badge, sound, deep link, collapse id, TTL, silent/background pushes, plus per-platform escape hatches (ApplePushOptions, AndroidPushOptions, WebPushOptions, WindowsPushOptions) |
| Automatic token hygiene | expired/invalid tokens are pruned; rotated tokens are applied back |
| Interceptors | localize, personalize, or suppress per device |
| Event receivers | observe the send lifecycle (batch start/finish, per-device sent/failed) for telemetry, receipts, or dead-letter capture |
| Batching / FCM multicast | batch-capable transports deliver many devices per call (FCM: up to 500 per /batch request); on by default via IPushBatchProvider |
| Multi-app | keyed registrations serve several apps from one server |
| Metrics | counters + latency histogram via System.Diagnostics.Metrics (OpenTelemetry-ready) |
| Pluggable persistence | in-memory out of the box, or Shiny.DocumentDb on any backend |
Getting Started
Section titled “Getting Started”Install the core package — it includes the APNs, FCM, Web Push and WNS transports — and, optionally, a persistence package:
dotnet add package Shiny.Extensions.Pushdotnet add package Shiny.Extensions.Push.DocumentDb # optional; defaults to in-memoryRegister the service:
using Shiny.Extensions.Push;using Shiny.Extensions.Push.Apns;using Shiny.Extensions.Push.DocumentDb;using Shiny.DocumentDb.Sqlite;
builder.Services.AddPushNotifications(push =>{ push.AddApns(o => { o.TeamId = "ABCDE12345"; o.KeyId = "KEY1234567"; o.BundleId = "com.example.app"; o.PrivateKeyPath = "AuthKey_KEY1234567.p8"; // or o.PrivateKey = "<PEM contents>" });
// Persistence (omit to use the in-memory repository) push.UseDocumentDb(o => o.DatabaseProvider = new SqliteDatabaseProvider("Data Source=push.db"));});Register a device when your app reports its token:
await pushManager.RegisterDevice(new DeviceRegistration{ DeviceToken = "<apns-device-token>", Platform = DevicePlatform.iOS, DeviceId = "install-guid", // stable identity across token rotation UserIdentifier = "user-42", Tags = ["beta", "sports"], Environment = PushEnvironment.Production // APNs tokens are environment-specific});Send:
var result = await pushManager.SendToUser("user-42", new PushNotification{ Title = "Goal!", Message = "Your team just scored", Badge = 1, DeepLink = "app://match/123"});// result.BatchId, result.Sent, result.Failed, result.TokensRemoved, result.SkippedNext: Client Apps, Sending, APNs, FCM, WNS, Web Push, Persistence, Metrics.


