Mit .NET MAUI 10 (Release November 2025) hat sich das Notification-Setup deutlich vereinfacht. Trotzdem gibt's ein paar wichtige Änderungen, die man kennen muss, sonst läuft man direkt in die nächste Wand:
- FCM HTTP v1 API ist Pflicht: Die alte Legacy-API wurde von Google im Juni 2024 endgültig abgeschaltet. Backends müssen OAuth 2.0 mit Service Account verwenden – kein Weg drumherum.
- Android 13+ braucht Runtime-Permissions:
POST_NOTIFICATIONS muss explizit zur Laufzeit angefragt werden. Wer das vergisst, wundert sich, warum auf neueren Geräten plötzlich nichts mehr ankommt.
- iOS Provisioning Profile mit Push Capability: Ohne korrekt aktivierte Capability bekommst du nur stille Fehlermeldungen wie "no valid aps-environment entitlement" – ein Klassiker.
- .NET 10 Trimming: Reflection-basierte JSON-Deserialisierung im Notification-Payload kann beim AOT-Compile einfach weggeschnitten werden.
Architektur-Überblick: Wie Push-Notifications technisch ablaufen
Bevor wir Code schreiben, lohnt sich ein klarer Blick auf den End-to-End-Flow. Push-Notifications sind nämlich kein direkter Server-zu-Gerät-Kanal (auch wenn das viele am Anfang denken), sondern laufen immer über die Provider-Cloud:
- Die App registriert sich beim Plattform-Provider (FCM oder APNs) und erhält einen Device Token.
- Die App sendet diesen Token an dein Backend (z. B. ASP.NET Core Web API).
- Das Backend speichert den Token mit User-Bezug und Plattform.
- Beim Senden erstellt das Backend eine Notification-Payload und schickt sie an FCM bzw. APNs.
- FCM/APNs liefern die Nachricht über persistente Verbindungen ans Gerät aus.
- Die App reagiert – im Vorder- oder Hintergrund – auf die Nachricht.
Wichtig zu wissen: iOS-Geräte können seit iOS 13 auch über FCM erreicht werden, aber Firebase leitet die Nachricht intern an APNs weiter. Du kommst also nicht um den Apple-Push-Service herum, selbst wenn du nur ein FCM-SDK einbindest. Das ist eine der Sachen, die ich anfangs nicht wahrhaben wollte – bringt aber nichts, ist halt so.
Schritt 1: Firebase-Projekt einrichten
Erstelle unter console.firebase.google.com ein neues Projekt. Achte darauf, das richtige Google-Cloud-Projekt zuzuordnen, falls du bereits eines hast (z. B. für BigQuery- oder Auth-Integration). Klingt banal, aber ich habe schon mal versehentlich ein zweites Projekt angelegt und mich dann gewundert, warum mein Auth-Token nirgendwo hinpasste.
Android-App in Firebase hinzufügen
- Klicke auf "Android-App hinzufügen".
- Gib den exakten Package Name aus deiner
Platforms/Android/AndroidManifest.xml ein (z. B. com.mobiletechlead.demo).
- Lade die
google-services.json herunter und leg sie unter Platforms/Android/google-services.json ab.
- Setz die Build Action der Datei auf
GoogleServicesJson:
<ItemGroup Condition="$(TargetFramework.Contains('-android'))">
<GoogleServicesJson Include="Platforms\Android\google-services.json" />
</ItemGroup>
iOS-App in Firebase hinzufügen
- Wähl "iOS-App hinzufügen" und trag den Bundle Identifier aus
Info.plist ein.
- Lade
GoogleService-Info.plist herunter und leg sie unter Platforms/iOS/GoogleService-Info.plist ab.
- Setz die Build Action auf
BundleResource.
APNs Authentication Key in Firebase hochladen
Damit Firebase Nachrichten an iOS-Geräte über APNs ausliefern kann, brauchst du einen APNs-Schlüssel. Erstelle unter developer.apple.com → Keys einen neuen Key mit der "Apple Push Notifications service (APNs)"-Capability. Notiere dir Key ID und Team ID, lade die .p8-Datei herunter (die kannst du übrigens nur einmal downloaden – also gut sichern!) und trag alles in den Firebase-Projekteinstellungen unter "Cloud Messaging → Apple App Configuration" ein.
Schritt 2: NuGet-Pakete und MauiProgram-Konfiguration
2026 ist Plugin.Firebase.CloudMessaging die stabilste Wahl für plattformübergreifende FCM-Integration in .NET MAUI 10. Alternativ funktioniert auch Shiny.Push mit eigenem Provider-Modell – meine persönliche Empfehlung ist aber das Plugin.Firebase-Ökosystem, einfach weil die Doku konsistenter ist.
dotnet add package Plugin.Firebase.CloudMessaging --version 3.1.0
dotnet add package Plugin.Firebase.Core --version 3.1.0
In MauiProgram.cs registrierst du das Plugin in der Builder-Pipeline:
using Plugin.Firebase.CloudMessaging;
using Plugin.Firebase.Core;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.RegisterFirebaseServices()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
builder.Services.AddSingleton<IPushNotificationService, PushNotificationService>();
return builder.Build();
}
private static MauiAppBuilder RegisterFirebaseServices(this MauiAppBuilder builder)
{
builder.ConfigureLifecycleEvents(events =>
{
#if IOS
events.AddiOS(ios => ios.FinishedLaunching((app, launchOptions) =>
{
CrossFirebase.Initialize();
FirebaseCloudMessagingImplementation.Initialize();
return false;
}));
#elif ANDROID
events.AddAndroid(android => android.OnCreate((activity, _) =>
CrossFirebase.Initialize(activity)));
#endif
});
return builder;
}
}
Schritt 3: Plattformspezifische Konfiguration
Android: AndroidManifest.xml und Notification-Channel
Füge in Platforms/Android/AndroidManifest.xml die nötigen Permissions ein:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
</manifest>
Ab Android 8.0 (API 26) musst du einen Notification-Channel erzeugen. Sonst zeigt das System gar nichts an – komplett kommentarlos. Leg das in MainApplication.cs an:
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership) { }
public override void OnCreate()
{
base.OnCreate();
CreateNotificationChannel();
}
private void CreateNotificationChannel()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O) return;
var channel = new NotificationChannel(
"default_channel",
"Allgemeine Benachrichtigungen",
NotificationImportance.High)
{
Description = "Standardkanal für App-Benachrichtigungen"
};
var manager = (NotificationManager)GetSystemService(NotificationService)!;
manager.CreateNotificationChannel(channel);
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
iOS: Info.plist, Entitlements und AppDelegate
In Platforms/iOS/Info.plist aktivierst du Background-Modes für Remote-Notifications:
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>fetch</string>
</array>
Erstelle eine Entitlements.plist mit der APNs-Capability:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
Im .csproj verweist du auf die Entitlements:
<PropertyGroup Condition="$(TargetFramework.Contains('-ios'))">
<CodesignEntitlements>Platforms/iOS/Entitlements.plist</CodesignEntitlements>
</PropertyGroup>
Für Release-Builds wird aps-environment automatisch auf production gesetzt – sofern du das richtige Provisioning Profile verwendest.
Schritt 4: Berechtigungen zur Laufzeit anfragen
Auf iOS und Android 13+ ist eine explizite User-Zustimmung Pflicht. Implementier einen plattformübergreifenden Service:
public interface IPushNotificationService
{
Task<bool> RequestPermissionAsync();
Task<string?> GetTokenAsync();
event EventHandler<PushNotificationReceivedEventArgs>? NotificationReceived;
}
public class PushNotificationService : IPushNotificationService
{
public event EventHandler<PushNotificationReceivedEventArgs>? NotificationReceived;
public async Task<bool> RequestPermissionAsync()
{
var granted = await CrossFirebaseCloudMessaging
.Current
.CheckIfValidAsync();
return granted;
}
public Task<string?> GetTokenAsync() =>
CrossFirebaseCloudMessaging.Current.GetTokenAsync();
}
Den Permission-Dialog rufst du idealerweise nicht beim App-Start auf, sondern an einer Stelle, an der der Nutzer den Mehrwert versteht (z. B. nach Login oder bei Aktivierung eines Feature-Toggles). Das verbessert die Akzeptanzquote laut Branchenstudien um 40–60 %. In einem meiner letzten Projekte sind wir von 23 % Opt-in (Dialog beim Start) auf 71 % gesprungen, einfach weil wir den Prompt erst nach dem ersten "Aha-Moment" der App eingebaut haben. Riesiger Hebel, kostet nichts.
Schritt 5: Token-Verwaltung und Backend-Registrierung
Der Device-Token kann sich jederzeit ändern – etwa bei App-Reinstall, Datenwiederherstellung oder Token-Refresh durchs OS. Reagier immer auf das TokenRefresh-Event:
CrossFirebaseCloudMessaging.Current.TokenChanged += async (sender, args) =>
{
var token = args.Token;
await _backendApi.RegisterDeviceAsync(new DeviceRegistration
{
Token = token,
Platform = DeviceInfo.Platform.ToString(),
UserId = _authService.CurrentUserId,
AppVersion = AppInfo.VersionString
});
};
Im Backend speicherst du Tokens mit User- und Plattform-Bezug. Wichtig: Niemals Tokens ohne User-Kontext speichern – sonst sendest du nach einem Logout Nachrichten an den falschen Empfänger. Ich habe genau diesen Fehler einmal in einer Banking-App gesehen, und das ist exakt der Albtraum, den du nicht haben willst.
Schritt 6: ASP.NET Core Backend mit FCM HTTP v1 API
2026 ist die Legacy-FCM-API tot. Du musst das FirebaseAdmin SDK oder direkt OAuth 2.0 mit einem Service Account verwenden. Erstell einen Service Account in der Firebase-Console (Projekteinstellungen → Dienstkonten → Neuen privaten Schlüssel generieren) und speicher die JSON-Datei sicher (z. B. in Azure Key Vault – bitte nicht ins Repo committen, ja?).
dotnet add package FirebaseAdmin --version 3.0.0
using FirebaseAdmin;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
public class FcmPushService
{
private static FirebaseApp? _app;
public FcmPushService(IConfiguration config)
{
if (_app != null) return;
_app = FirebaseApp.Create(new AppOptions
{
Credential = GoogleCredential.FromFile(config["Firebase:ServiceAccountPath"])
});
}
public async Task<string> SendAsync(string token, string title, string body, IDictionary<string, string>? data = null)
{
var message = new Message
{
Token = token,
Notification = new Notification { Title = title, Body = body },
Data = data,
Android = new AndroidConfig
{
Priority = Priority.High,
Notification = new AndroidNotification
{
ChannelId = "default_channel",
Sound = "default"
}
},
Apns = new ApnsConfig
{
Aps = new Aps
{
Sound = "default",
ContentAvailable = true,
MutableContent = true
}
}
};
return await FirebaseMessaging.DefaultInstance.SendAsync(message);
}
}
Für den Versand an mehrere Geräte verwendest du SendEachForMulticastAsync – das ersetzt das in 2024 abgekündigte SendMulticastAsync. Klingt nach einer Kleinigkeit, aber ich habe es schon erlebt, dass ältere Tutorials genau hier hängen bleiben.
Schritt 7: Notifications im Vorder- und Hintergrund verarbeiten
.NET MAUI unterscheidet drei Zustände:
- Foreground: App ist aktiv. Du musst die Notification selbst anzeigen.
- Background: App läuft im Hintergrund. Das System zeigt die Notification automatisch.
- Terminated: App ist beendet. Beim Tap auf die Notification startet die App und du bekommst die Daten als Launch-Argument.
CrossFirebaseCloudMessaging.Current.NotificationReceived += (sender, args) =>
{
if (args.Data.TryGetValue("deepLink", out var route))
{
Shell.Current.Dispatcher.Dispatch(async () =>
await Shell.Current.GoToAsync(route));
}
};
Topic-Subscriptions: Broadcast ohne Token-Liste
Statt einzelne Tokens zu pflegen, kannst du Geräte zu Topics abonnieren – ideal für Newsletter, Sport-Scores oder regionale Benachrichtigungen:
await CrossFirebaseCloudMessaging.Current.SubscribeToTopicAsync("news_de");
// Backend sendet einfach an /topics/news_de
Der Vorteil: Skalierbarkeit. Der Nachteil: Du kannst einen Topic-Send nicht zuverlässig user-individualisieren. Für personalisierte Inhalte bleiben Token-basierte Sends die richtige Wahl. Beide Ansätze schließen sich übrigens nicht aus – ich kombiniere in der Praxis gerne beides.
Testen ohne Backend: FCM-Test-Console und APNs-Tools
Für schnelle Tests reicht die Firebase-Console: Cloud Messaging → "Erste Kampagne erstellen" → Device-Token einfügen. Für lokale APNs-Tests unter iOS empfiehlt sich NWPusher oder die mit Xcode 15+ ausgelieferte Push Notifications-App.
Im iOS-Simulator (ab Xcode 11.4) kannst du eine .apns-Datei per Drag-and-Drop ins Simulator-Fenster ziehen:
{
"Simulator Target Bundle": "com.mobiletechlead.demo",
"aps": {
"alert": {
"title": "Test",
"body": "Hallo aus dem Simulator"
},
"sound": "default"
}
}
Häufige Fehler und Troubleshooting
"FirebaseApp with name [DEFAULT] doesn't exist"
Du hast CrossFirebase.Initialize() nicht oder zu spät aufgerufen. Initialisierung muss in FinishedLaunching (iOS) bzw. OnCreate (Android) passieren – vor dem ersten FCM-Zugriff.
iOS: "no valid 'aps-environment' entitlement string found"
Das Provisioning Profile enthält keine Push-Capability. Aktivier unter developer.apple.com → Identifiers die Capability für deine App-ID, regenerier das Provisioning Profile und lade es neu in Visual Studio. Ja, das nervt jedes Mal aufs Neue.
Android: Notifications kommen nur, wenn die App offen ist
Wahrscheinlich blockiert die Hersteller-spezifische Battery-Optimization deine App. Bitt den Nutzer einmalig, die Optimierung zu deaktivieren – besonders wichtig auf Xiaomi, Huawei und OnePlus. (Spoiler: Das ist die Nummer 1 in unserem Support-Postfach.)
iOS: Notification kommt im Sandbox, aber nicht in Production
Sandbox- und Production-Tokens sind unterschiedliche Tokens. Stell sicher, dass dein Backend zwischen Debug- und Release-Builds unterscheidet und an den richtigen APNs-Endpoint sendet.
Trimming entfernt Notification-Payload-Klassen
Bei NativeAOT/Trimming kann der Linker JSON-Klassen entfernen. Nutz Source-Generated JsonSerializerContext oder füg ein TrimmerRoots.xml-Descriptor hinzu.
Best Practices für 2026
- Frag Permissions kontextuell, nicht beim ersten Start.
- Speicher Tokens mit Plattform-Tag – iOS und Android haben unterschiedliche Payload-Limits (4 KB FCM, 5 KB APNs für Critical Alerts).
- Implementier Idempotenz: Bei Token-Refresh kann derselbe Token mehrfach gesendet werden. Auf Backend-Seite mit Upsert arbeiten.
- Nutz Notification Channels feingranular – ein "default_channel" reicht selten. Trenn Marketing, Transaktionen und Sicherheit, damit Nutzer einzelne Kategorien stummschalten können.
- Logge Delivery-Failures: FCM markiert ungültige Tokens mit
UNREGISTERED oder INVALID_ARGUMENT. Räum diese aus deiner DB.
- Respektier Quiet Hours: Sende keine Marketing-Notifications zwischen 22 und 8 Uhr lokaler Zeit. APNs hat dafür den
interruption-level-Parameter (passive, active, time-sensitive, critical).
FAQ: Push-Notifications in .NET MAUI 10
Funktionieren Push-Notifications auch ohne Firebase?
Ja. Auf iOS kannst du direkt gegen APNs senden – Firebase ist optional. Auf Android brauchst du zwingend einen Provider: FCM (Standard auf Google-Geräten), Huawei Push Kit (für Huawei-Geräte ohne Google-Services) oder Amazon Device Messaging. Eine plattformübergreifende Alternative ist OneSignal, das beide Provider abstrahiert, aber pro Gerät pricing-relevant ist.
Was ist der Unterschied zwischen Notification- und Data-Messages?
Notification-Messages werden vom System automatisch angezeigt, wenn die App im Hintergrund ist. Data-Messages kommen immer direkt in deinen Code, auch im Hintergrund – du musst die Anzeige selbst übernehmen. Für Deep-Linking, Inhaltsänderungen und Silent-Updates solltest du reine Data-Messages verwenden (auf iOS mit content-available: 1).
Wie hoch ist das Payload-Limit bei FCM und APNs?
FCM erlaubt bis zu 4 KB Payload. APNs erlaubt 4 KB für Standard-Notifications und 5 KB für Voice-over-IP- und Critical-Alerts. Wenn deine Daten größer sind, sendest du nur eine ID und lädst den Inhalt per HTTPS-Call nach. Klingt umständlich, ist aber Standard-Praxis.
Kann ich Push-Notifications im iOS-Simulator testen?
Ja, ab Xcode 11.4 unterstützt der Simulator Remote-Push-Notifications. Du kannst entweder eine .apns-Datei per Drag-and-Drop ins Simulator-Fenster ziehen oder mit xcrun simctl push aus der Kommandozeile senden. Echte APNs-Tokens funktionieren im Simulator allerdings nicht – nur lokale Tests.
Muss ich Push-Tokens in der DSGVO-Datenschutzerklärung erwähnen?
Ja. Push-Tokens gelten in vielen EU-Auslegungen als personenbezogene Daten, da sie ein bestimmtes Gerät identifizieren. Dokumentiere Zweck, Speicherdauer und Drittlandtransfer (Firebase liegt auf US-Servern!). Praktisch: Verzicht auf Marketing-Pushes ohne Opt-In, Token-Löschung bei Account-Deletion und Standard-Vertragsklauseln (SCCs) für die Firebase-Nutzung sind die drei wichtigsten Bausteine.
Fazit: Stabile Push-Notifications brauchen Disziplin auf drei Ebenen
Push-Notifications in .NET MAUI 10 sind 2026 nicht mehr die Frickel-Aufgabe von Xamarin-Zeiten – sofern du auf drei Dinge achtest: korrekte Provider-Konfiguration (FCM mit aktueller HTTP-v1-API, APNs mit gültigem Auth-Key), sauberes Token-Lifecycle-Management (Refresh, Backend-Sync, Cleanup ungültiger Tokens) und kontextuelle UX (Permissions zur richtigen Zeit, sinnvolle Channels, Quiet Hours respektieren).
Mit dem hier gezeigten Setup hast du ein produktionsreifes Fundament für jede Mobile App – egal ob B2C-Marketing-App, Enterprise-Tool oder Finance-Lösung mit DSGVO-Anforderungen. Und ehrlich? Wenn du diese drei Ebenen ernst nimmst, sparst du dir die Hälfte der Support-Tickets, die sonst landen würden. Viel Erfolg beim Implementieren.