Handledning: Skicka push-meddelanden till .NET MAUI-appar med hjälp av Azure Notification Hubs via en backend-tjänst
Push-meddelanden levererar information från ett serverdelssystem till en klientapp. Apple, Google och andra plattformar har var och en sin egen push-meddelandetjänst (PNS). Med Azure Notification Hubs kan du centralisera meddelanden mellan plattformar så att din serverdelsapp kan kommunicera med en enda hubb, som tar hand om distribution av meddelanden till varje PNS.
Azure Notification Hubs kräver att appar registrerar sig med hubben och om du vill kan du definiera mallar och/eller prenumerera på taggar:
- När du utför en enhetsinstallation länkas en PNS-referens till en identifierare i Azure Notification Hub. Mer information om registreringar finns i Registreringshantering.
- Med mallar kan enheter ange parameteriserade meddelandemallar. Inkommande meddelanden kan anpassas per enhet. Mer information finns i Notification Hubs-mallar.
- Taggar kan användas för att prenumerera på meddelandekategorier som nyheter, sport och väder. Mer information finns i Routning och tagguttryck.
I den här självstudien använder du Azure Notification Hubs för att skicka push-meddelanden till en .NET Multi-Platform App UI-app (.NET MAUI) som riktar sig till Android och iOS. En ASP.NET Core Web API-serverdel används för att hantera enhetsregistrering för klienten och för att initiera ett push-meddelande. Dessa åtgärder hanteras med hjälp av Microsoft.Azure.NotificationHubs NuGet-paketet. Mer information om den övergripande metoden finns i Registreringshantering från en backend.
I den här handledningen:
- Konfigurera push-meddelandetjänster och Azure Notification Hub.
- Skapa en ASP.NET Core WebAPI-serverdelsapp.
- Skapa en .NET MAUI-app.
- Konfigurera Android-appen för push-meddelanden.
- Konfigurera iOS-appen för push-meddelanden.
- Testa appen.
- Felsöka eventuella konfigurationsproblem.
Förutsättningar
För att slutföra den här självstudien behöver du:
- Ett Azure-konto med en aktiv prenumeration.
- En PC eller Mac som kör den senaste versionen av Visual Studio/Visual Studio Code med arbetsbelastningen för .NET Multi-platform App UI-utveckling och arbetsbelastningarna för ASP.NET och webbapplikationsutveckling installerade.
För Android måste du ha:
- En utvecklare har låst upp en fysisk enhet eller en emulator som kör API 26+ med Google Play Services installerat.
För iOS måste du ha:
- Ett aktivt Apple-utvecklarkonto.
- En Mac som kör Xcode, tillsammans med ett giltigt utvecklarcertifikat installerat i nyckelringen.
Sedan bör du på iOS antingen ha:
En iOS 16+ simulator som körs i macOS 13+ på Mac-datorer med Apple-kisel- eller T2-processorer.
ELLER
En fysisk iOS-enhet som är registrerad på ditt utvecklarkonto (kör iOS 13.0+).
Din fysiska enhet som är registrerad på ditt Apple-utvecklarkonto och som är associerad med ditt certifikat.
Viktig
iOS-simulatorn stöder fjärrmeddelanden i iOS 16+ när den körs i macOS 13+ på Mac-datorer med Apple-kisel- eller T2-processorer. Om du inte uppfyller dessa maskinvarukrav behöver du ett aktivt Apple-utvecklarkonto och en fysisk enhet.
Om du vill följa den här handledningen bör du känna till:
Den här självstudien är avsedd för Visual Studio, men den kan följas med hjälp av Visual Studio Code på en PC eller Mac. Det kommer dock att finnas vissa skillnader som behöver förenas. Till exempel beskrivningar av användargränssnitt och arbetsflöden, mallnamn och miljökonfiguration.
Konfigurera push-meddelandetjänster och Azure Notification Hub
I det här avsnittet konfigurerar du Firebase Cloud Messaging och Apple Push Notification Services (APNS). Sedan skapar och konfigurerar du en Azure Notification Hub- så att den fungerar med dessa tjänster.
Skapa ett Firebase-projekt
Så här skapar du ett Firebase-projekt:
Logga in på Firebase-konsolen i en webbläsare.
I Firebase-konsolen väljer du knappen Lägg till projekt och skapar ett nytt Firebase-projekt och anger PushDemo- som Projektnamn.
Not
Ett unikt namn genereras åt dig. Som standard består detta av en gemen variant av namnet du angav plus ett genererat tal som avgränsas med ett bindestreck. Du kan ändra detta om du vill, förutsatt att dina redigeringar fortfarande är globalt unika.
När projektet har skapats väljer du Android-logotypen för att lägga till Firebase i en Android-app:
På sidan Lägg till Firebase i din Android-app anger du ett namn för ditt paket, valfritt ett appnamn och väljer knappen Registrera app:
På sidan Lägg till Firebase i din Android-app väljer du knappen Ladda ned google-services.json och sparar filen i en lokal mapp innan du väljer knappen Nästa:
På sidan Lägg till Firebase i din Android-app väljer du knappen Nästa.
På sidan Lägg till Firebase i din Android-app väljer du knappen Fortsätt till konsolen.
I Firebase-konsolen väljer du ikonen Projektöversikt och väljer sedan Project-inställningar:
I Project-inställningarväljer du fliken Cloud Messaging. Du ser att Firebase Cloud Messaging API (V1) är aktiverat:
I Project-inställningarnaväljer du fliken Tjänstkonton och väljer sedan knappen Generera ny privat nyckel.
I dialogrutan Generera ny privat nyckel väljer du knappen Generera nyckel:
En JSON-fil laddas ned, som innehåller värden som du anger i Azure Notification Hub.
Registrera din iOS-app för push-meddelanden
Om du vill skicka push-meddelanden till en iOS-app måste du registrera din app med Apple och registrera dig för push-meddelanden. Detta kan du göra genom att utföra stegen i följande Dokumentation om Azure Notification Hub:
- Generera begärandefilen för certifikatsignering
- Registrera din app för push-meddelanden
- Skapa ett certifikat för din meddelandehubb
Om du vill ta emot push-meddelanden på en fysisk enhet måste du också skapa en distributionsprofil.
Viktig
Om du vill ta emot bakgrundsmeddelanden i iOS måste du lägga till bakgrundsläget för fjärrmeddelanden i appen. Mer information finns i Aktivera funktionen för fjärrmeddelanden på developer.apple.com.
Skapa en Azure Notification Hub
Så här skapar du en meddelandehubb i Azure-portalen:
- Logga in på Azure-portalen i en webbläsare.
- I Azure-portalen klickar du på knappen Skapa en resurs och söker sedan efter och väljer Notification Hub innan du väljer knappen Skapa.
- Utför följande steg på sidan Notification Hub:
I fältet Prenumeration väljer du namnet på den Azure-prenumeration som du vill använda och väljer sedan en befintlig resursgrupp eller skapar en ny.
I fältet Namnområdesinformation anger du ett unikt namn för det nya namnområdet.
I fältet Notification Hub Details anger du ett namn för meddelandehubben. Detta krävs eftersom ett namnområde innehåller en eller flera meddelandehubbar.
I listrutan Plats väljer du ett värde som anger den plats där du vill skapa meddelandehubben.
Granska alternativet tillgänglighetszoner. Om du väljer en region som har tillgänglighetszoner är kryssrutan markerad som standard.
Notis
Tillgänglighetszoner är en betald funktion, så en extra avgift läggs till på din nivå.
Välj ett katastrofåterställningsalternativ: ingen, parerad återställningsregion eller flexibel återställningsregion. Om du väljer parkopplad återställningsregionvisas failoverregionen. Om du väljer flexibel återställningsregionanvänder du listrutan för att välja från en lista över återställningsregioner.
Välj knappen Skapa. Meddelandehubben skapas.
- I Azure-portalen bläddrar du till din nyligen skapade meddelandehubb och sedan till bladet Hantera > åtkomstprinciper.
- Anteckna anslutningssträngen för policyn
DefaultFullSharedAccessSignature
i bladet Åtkomstprinciper. Du behöver detta senare när du skapar en serverdelstjänst som kommunicerar med din meddelandehubb.
Mer information om hur du skapar en meddelandehubb finns i Skapa en Azure-meddelandehubb i Azure-portalen.
Konfigurera Firebase Cloud Messaging i meddelandehubben
Så här konfigurerar du meddelandehubben så att den kommunicerar med Firebase Cloud Messaging:
I Azure-portalenbläddrar du till meddelandehubben och väljer bladet Inställningar > Google (FCM v1).
På bladet Google (FCM v1) anger du värden för fälten Private Key, Client Emailoch Project ID. Dessa värden finns i JSON-filen med privat nyckel som du laddade ned från Firebase Cloud Messaging:
Azure-fält JSON-nyckel JSON-värdeexempel Privat nyckel private_key
Det här värdet bör börja med -----BEGIN PRIVATE KEY-----\n
och sluta med-----END PRIVATE KEY-----\n
.E-post för klient client_email
firebase-adminsdk-55sfg@pushdemo-d6ab2.iam.gserviceaccount.com
Projekt-ID project_id
pushdemo-d6ab2
I bladet Google (FCM v1) väljer du knappen Spara.
Konfigurera Apple Push Notification Service i meddelandehubben
I Azure-portalenbläddrar du till meddelandehubben och väljer bladet Inställningar > Apple (APNS). Följ sedan lämpliga steg baserat på den metod du valde tidigare när du skapade ett certifikat för meddelandehubben.
Viktig
När du anger programlägeväljer du bara Production om du vill skicka push-meddelanden till användare som har köpt din app från butiken.
Alternativ 1 – Använd ett .p12-pushcertifikat
- På bladet Apple (APNS) väljer du autentiseringsläget Certifikat.
- I bladet Apple (APNS) väljer du fil-ikonen bredvid fältet Ladda upp certifikat. Välj sedan den .p12-fil som du exporterade tidigare och ladda upp den.
- På bladet Apple (APNS) anger du certifikatlösenordet i fältet Lösenord om det behövs.
- På bladet Apple (APNS) väljer du Sandbox- applikationsläge.
- På bladet Apple (APNS) väljer du knappen Spara.
Alternativ 2 – Använda tokenbaserad autentisering
- På bladet Apple (APNS) väljer du Token autentiseringsmetod.
- På bladet Apple (APNS) anger du de värden som du tidigare skaffade för fälten Key ID, Bundle ID, Team IDoch Token.
- På bladet Apple (APNS) väljer du Sandbox- applikationsläge.
- På bladet Apple (APNS) väljer du knappen Spara.
Skapa en ASP.NET Core Web API-serverdelsapp
I det här avsnittet skapar du en ASP.NET Core Web API-serverdel för att hantera enhetsinstallation och skicka meddelanden till .NET MAUI-appen.
Skapa ett webb-API-projekt
Så här skapar du ett webb-API-projekt:
Skapa ett ASP.NET Core Web API- projekt i Visual Studio:
I dialogrutan Konfigurera ditt nya projekt namnger du projektet PushNotificationsAPI.
I dialogrutan Ytterligare information kontrollerar du att kryssrutorna Configure for HTTPS and Use controllers är aktiverade:
När projektet har skapats trycker du på F5 för att köra projektet.
Appen är för närvarande konfigurerad att använda
WeatherForecastController
somlaunchUrl
, som anges i Properties\launchSettings.jspå-filen. Appen startas i en webbläsare och visar vissa JSON-data.Viktig
När du kör ett ASP.NET Core-projekt som använder HTTPS identifierar Visual Studio om ASP.NET Core HTTPS-utvecklingscertifikatet är installerat i ditt lokala användarcertifikatarkiv och erbjuder att installera det och lita på det om det saknas.
Stäng webbläsaren.
I Solution Explorerexpanderar du mappen Controllers och tar bort WeatherForecastController.cs.
I Solution Exploreri roten av projektet tar du bort WeatherForecast.cs.
Öppna ett kommandofönster och navigera till katalogen som innehåller projektfilen. Kör sedan följande kommandon:
dotnet user-secrets init dotnet user-secrets set "NotificationHub:Name" <value> dotnet user-secrets set "NotificationHub:ConnectionString" "<value>"
Ersätt platshållarvärdena med ditt eget Azure Notification Hub-namn och anslutningssträngsvärden. Dessa finns på följande platser i Azure Notification Hub:
Konfigurationsvärde Plats NotificationHub:Name
Se Namn i Essentials-sammanfattningen överst på Översikt-sidan . NotificationHub:ConnectionString
Se DefaultFullSharedAccessSignature* på sidan Åtkomstprinciper. Detta konfigurerar lokala konfigurationsvärden med hjälp av verktyget Secret Manager. Detta frikopplar dina Azure Notification Hub-hemligheter från Visual Studio-lösningen för att säkerställa att de inte hamnar i källkontrollen.
Tips
För produktionsscenarier bör du överväga en tjänst som Azure KeyVault för att lagra anslutningssträngen på ett säkert sätt.
Autentisera klienter med en API-nyckel
Så här autentiserar du klienter med en API-nyckel:
Öppna ett kommandofönster och navigera till katalogen som innehåller projektfilen. Kör sedan följande kommandon:
dotnet user-secrets set "Authentication:ApiKey" <value>
Ersätt platshållarvärdet med din API-nyckel, vilket kan vara valfritt värde.
I Visual Studio lägger du till en ny mapp med namnet Authentication i projektet och lägger sedan till en ny klass med namnet
ApiKeyAuthOptions
i mappen Authentication och ersätter koden med följande kod:using Microsoft.AspNetCore.Authentication; namespace PushNotificationsAPI.Authentication; public class ApiKeyAuthOptions : AuthenticationSchemeOptions { public const string DefaultScheme = "ApiKey"; public string Scheme => DefaultScheme; public string ApiKey { get; set; } }
I Visual Studio lägger du till en ny klass med namnet
ApiKeyAuthHandler
i mappen Authentication och ersätter koden med följande kod:using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; using System.Security.Claims; using System.Text.Encodings.Web; namespace PushNotificationsAPI.Authentication; public class ApiKeyAuthHandler : AuthenticationHandler<ApiKeyAuthOptions> { const string ApiKeyIdentifier = "apikey"; public ApiKeyAuthHandler( IOptionsMonitor<ApiKeyAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) { } protected override Task<AuthenticateResult> HandleAuthenticateAsync() { string key = string.Empty; if (Request.Headers[ApiKeyIdentifier].Any()) { key = Request.Headers[ApiKeyIdentifier].FirstOrDefault(); } else if (Request.Query.ContainsKey(ApiKeyIdentifier)) { if (Request.Query.TryGetValue(ApiKeyIdentifier, out var queryKey)) key = queryKey; } if (string.IsNullOrWhiteSpace(key)) return Task.FromResult(AuthenticateResult.Fail("No api key provided")); if (!string.Equals(key, Options.ApiKey, StringComparison.Ordinal)) return Task.FromResult(AuthenticateResult.Fail("Invalid api key.")); var identities = new List<ClaimsIdentity> { new ClaimsIdentity("ApiKeyIdentity") }; var ticket = new AuthenticationTicket(new ClaimsPrincipal(identities), Options.Scheme); return Task.FromResult(AuthenticateResult.Success(ticket)); } }
En autentiseringshanterare är en typ som implementerar beteendet för ett schema, vilket i det här fallet är ett anpassat API-nyckelschema.
I Visual Studio lägger du till en ny klass med namnet
AuthenticationBuilderExtensions
i mappen Authentication och ersätter koden med följande kod:using Microsoft.AspNetCore.Authentication; namespace PushNotificationsAPI.Authentication; public static class AuthenticationBuilderExtensions { public static AuthenticationBuilder AddApiKeyAuth( this AuthenticationBuilder builder, Action<ApiKeyAuthOptions> configureOptions) { return builder .AddScheme<ApiKeyAuthOptions, ApiKeyAuthHandler>( ApiKeyAuthOptions.DefaultScheme, configureOptions); } }
Den här tilläggsmetoden används för att förenkla konfigurationskoden för mellanprogram i Program.cs.
I Visual Studio öppnar du Program.cs och uppdaterar koden för att konfigurera API-nyckelautentiseringen under anropet till metoden
builder.Services.AddControllers
:using PushNotificationsAPI.Authentication; builder.Services.AddControllers(); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = ApiKeyAuthOptions.DefaultScheme; options.DefaultChallengeScheme = ApiKeyAuthOptions.DefaultScheme; }).AddApiKeyAuth(builder.Configuration.GetSection("Authentication").Bind);
I Program.csuppdaterar du koden under kommentaren
// Configure the HTTP request pipeline
för att anropa metodernaUseRouting
,UseAuthentication
ochMapControllers
tillägg:// Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
Metoden
UseAuthentication
tillägg registrerar mellanprogrammet som använder det tidigare registrerade autentiseringsschemat.UseAuthentication
måste anropas innan mellanprogram som är beroende av att användare autentiseras.Not
Även om en API-nyckel inte är lika säker som en token, kommer den att räcka för den här självstudien och kan enkelt konfigureras via ASP.NET Middleware.
Lägga till och konfigurera tjänster
Så här lägger du till och konfigurerar tjänster i din webb-API-serverdelsapp:
I Visual Studio lägger du till Microsoft.Azure.NotificationHubs NuGet-paketet i projektet. Det här NuGet-paketet används för att komma åt din meddelandehubb, inkapslad i en tjänst.
I Visual Studio lägger du till en ny mapp med namnet Models i projektet och lägger sedan till en ny klass med namnet
PushTemplates
i mappen Models och ersätter koden med följande kod:namespace PushNotificationsAPI.Models; public class PushTemplates { public class Generic { public const string Android = "{ \"message\" : { \"notification\" : { \"title\" : \"PushDemo\", \"body\" : \"$(alertMessage)\"}, \"data\" : { \"action\" : \"$(alertAction)\" } } }"; public const string iOS = "{ \"aps\" : {\"alert\" : \"$(alertMessage)\"}, \"action\" : \"$(alertAction)\" }"; } public class Silent { public const string Android = "{ \"message\" : { \"data\" : {\"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\"} } }"; public const string iOS = "{ \"aps\" : {\"content-available\" : 1, \"apns-priority\": 5, \"sound\" : \"\", \"badge\" : 0}, \"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\" }"; } }
Klassen
PushTemplates
innehåller tokeniserade meddelandenyttolaster för allmänna och tysta push-meddelanden. Dessa nyttolaster definieras utanför installation för att tillåta experimentering utan att behöva uppdatera befintliga installationer via tjänsten. Att hantera ändringar av installationer på det här sättet ligger utanför omfånget för den här artikeln. I produktscenarier bör du överväga att använda anpassade mallar.I Visual Studio lägger du till en ny klass med namnet
DeviceInstallation
i mappen Models och ersätter koden med följande kod:using System.ComponentModel.DataAnnotations; namespace PushNotificationsAPI.Models; public class DeviceInstallation { [Required] public string InstallationId { get; set; } [Required] public string Platform { get; set; } [Required] public string PushChannel { get; set; } public IList<string> Tags { get; set; } = Array.Empty<string>(); }
I Visual Studio lägger du till en ny klass med namnet
NotificationRequest
i mappen Models och ersätter koden med följande kod:namespace PushNotificationsAPI.Models; public class NotificationRequest { public string Text { get; set; } public string Action { get; set; } public string[] Tags { get; set; } = Array.Empty<string>(); public bool Silent { get; set; } }
I Visual Studio lägger du till en ny klass med namnet
NotificationHubOptions
i mappen Models och ersätter koden med följande kod:using System.ComponentModel.DataAnnotations; namespace PushNotificationsAPI.Models; public class NotificationHubOptions { [Required] public string Name { get; set; } [Required] public string ConnectionString { get; set; } }
I Visual Studio lägger du till en ny mapp med namnet Services i projektet och lägger sedan till ett nytt gränssnitt med namnet
INotificationService
i mappen Services och ersätter koden med följande kod:using PushNotificationsAPI.Models; namespace PushNotificationsAPI.Services; public interface INotificationService { Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token); Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token); Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token); }
I Visual Studio lägger du till en ny klass med namnet
NotificationHubService
i mappen Services och ersätter koden med följande kod:using Microsoft.Extensions.Options; using Microsoft.Azure.NotificationHubs; using PushNotificationsAPI.Models; namespace PushNotificationsAPI.Services; public class NotificationHubService : INotificationService { readonly NotificationHubClient _hub; readonly Dictionary<string, NotificationPlatform> _installationPlatform; readonly ILogger<NotificationHubService> _logger; public NotificationHubService(IOptions<NotificationHubOptions> options, ILogger<NotificationHubService> logger) { _logger = logger; _hub = NotificationHubClient.CreateClientFromConnectionString(options.Value.ConnectionString, options.Value.Name); _installationPlatform = new Dictionary<string, NotificationPlatform> { { nameof(NotificationPlatform.Apns).ToLower(), NotificationPlatform.Apns }, { nameof(NotificationPlatform.FcmV1).ToLower(), NotificationPlatform.FcmV1 } }; } public async Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token) { if (string.IsNullOrWhiteSpace(deviceInstallation?.InstallationId) || string.IsNullOrWhiteSpace(deviceInstallation?.Platform) || string.IsNullOrWhiteSpace(deviceInstallation?.PushChannel)) return false; var installation = new Installation() { InstallationId = deviceInstallation.InstallationId, PushChannel = deviceInstallation.PushChannel, Tags = deviceInstallation.Tags }; if (_installationPlatform.TryGetValue(deviceInstallation.Platform, out var platform)) installation.Platform = platform; else return false; try { await _hub.CreateOrUpdateInstallationAsync(installation, token); } catch { return false; } return true; } public async Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token) { if (string.IsNullOrWhiteSpace(installationId)) return false; try { await _hub.DeleteInstallationAsync(installationId, token); } catch { return false; } return true; } public async Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token) { if ((notificationRequest.Silent && string.IsNullOrWhiteSpace(notificationRequest?.Action)) || (!notificationRequest.Silent && (string.IsNullOrWhiteSpace(notificationRequest?.Text)) || string.IsNullOrWhiteSpace(notificationRequest?.Action))) return false; var androidPushTemplate = notificationRequest.Silent ? PushTemplates.Silent.Android : PushTemplates.Generic.Android; var iOSPushTemplate = notificationRequest.Silent ? PushTemplates.Silent.iOS : PushTemplates.Generic.iOS; var androidPayload = PrepareNotificationPayload( androidPushTemplate, notificationRequest.Text, notificationRequest.Action); var iOSPayload = PrepareNotificationPayload( iOSPushTemplate, notificationRequest.Text, notificationRequest.Action); try { if (notificationRequest.Tags.Length == 0) { // This will broadcast to all users registered in the notification hub await SendPlatformNotificationsAsync(androidPayload, iOSPayload, token); } else if (notificationRequest.Tags.Length <= 20) { await SendPlatformNotificationsAsync(androidPayload, iOSPayload, notificationRequest.Tags, token); } else { var notificationTasks = notificationRequest.Tags .Select((value, index) => (value, index)) .GroupBy(g => g.index / 20, i => i.value) .Select(tags => SendPlatformNotificationsAsync(androidPayload, iOSPayload, tags, token)); await Task.WhenAll(notificationTasks); } return true; } catch (Exception e) { _logger.LogError(e, "Unexpected error sending notification"); return false; } } string PrepareNotificationPayload(string template, string text, string action) => template .Replace("$(alertMessage)", text, StringComparison.InvariantCulture) .Replace("$(alertAction)", action, StringComparison.InvariantCulture); Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, CancellationToken token) { var sendTasks = new Task[] { _hub.SendFcmV1NativeNotificationAsync(androidPayload, token), _hub.SendAppleNativeNotificationAsync(iOSPayload, token) }; return Task.WhenAll(sendTasks); } Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, IEnumerable<string> tags, CancellationToken token) { var sendTasks = new Task[] { _hub.SendFcmV1NativeNotificationAsync(androidPayload, tags, token), _hub.SendAppleNativeNotificationAsync(iOSPayload, tags, token) }; return Task.WhenAll(sendTasks); } }
Tagguttrycket som tillhandahålls till metoden
SendTemplateNotificationsAsync
är begränsat till 20 taggar om de bara innehåller ORs. Annars är de begränsade till 6 taggar. Mer information finns i Rutthantering och tagguttryck.I Visual Studio öppnar du Program.cs och uppdaterar koden för att lägga till
NotificationHubService
som en singleton-implementering avINotificationService
under anropet till metodenbuilder.Services.AddAuthentication
:using PushNotificationsAPI.Authentication; using PushNotificationsAPI.Services; using PushNotificationsAPI.Models; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = ApiKeyAuthOptions.DefaultScheme; options.DefaultChallengeScheme = ApiKeyAuthOptions.DefaultScheme; }).AddApiKeyAuth(builder.Configuration.GetSection("Authentication").Bind); builder.Services.AddSingleton<INotificationService, NotificationHubService>(); builder.Services.AddOptions<NotificationHubOptions>() .Configure(builder.Configuration.GetSection("NotificationHub").Bind) .ValidateDataAnnotations(); var app = builder.Build();
Skapa REST-API:t för notifikationer
Så här skapar du REST-API:et för meddelanden:
I Visual Studio lägger du till en ny Controller med namnet
NotificationsController
i mappen Controllers.Tips
Välj API-styrenheten med mall för läs-/skrivåtgärder.
I filen NotificationsController.cs lägger du till följande
using
-instruktioner överst i filen:using System.ComponentModel.DataAnnotations; using System.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PushNotificationsAPI.Models; using PushNotificationsAPI.Services;
I filen NotificationsController.cs lägger du till attributet
Authorize
i klassenNotificationsController
:[Authorize] [ApiController] [Route("api/[controller]")] public class NotificationsController : ControllerBase
I filen NotificationsController.cs uppdaterar du
NotificationsContoller
konstruktorn för att acceptera den registrerade instansen avINotificationService
som ett argument och tilldelar den till en readonly-medlem:readonly INotificationService _notificationService; public NotificationsController(INotificationService notificationService) { _notificationService = notificationService; }
I filen NotificationsContoller.cs ersätter du alla metoder med följande kod:
[HttpPut] [Route("installations")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)] public async Task<IActionResult> UpdateInstallation( [Required] DeviceInstallation deviceInstallation) { var success = await _notificationService .CreateOrUpdateInstallationAsync(deviceInstallation, HttpContext.RequestAborted); if (!success) return new UnprocessableEntityResult(); return new OkResult(); } [HttpDelete()] [Route("installations/{installationId}")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)] public async Task<ActionResult> DeleteInstallation( [Required][FromRoute] string installationId) { // Probably want to ensure deletion even if the connection is broken var success = await _notificationService .DeleteInstallationByIdAsync(installationId, CancellationToken.None); if (!success) return new UnprocessableEntityResult(); return new OkResult(); } [HttpPost] [Route("requests")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)] public async Task<IActionResult> RequestPush( [Required] NotificationRequest notificationRequest) { if ((notificationRequest.Silent && string.IsNullOrWhiteSpace(notificationRequest?.Action)) || (!notificationRequest.Silent && string.IsNullOrWhiteSpace(notificationRequest?.Text))) return new BadRequestResult(); var success = await _notificationService .RequestNotificationAsync(notificationRequest, HttpContext.RequestAborted); if (!success) return new UnprocessableEntityResult(); return new OkResult(); }
I filen Egenskaper/launchSettings.json ändrar du egenskapen
launchUrl
för varje profil frånweatherforecast
tillapi/notifications
.
Skapa en API-app
Nu ska du skapa en API-app i Azure App Service- som värd för serverdelstjänsten. Detta kan göras direkt från Visual Studio eller Visual Studio Code, med Azure CLI, Azure PowerShell, Azure Developer CLI och via Azure Portal. Mer information finns i Publicera din webbapp.
Så här skapar du en API-app i Azure-portalen:
Logga in på Azure-portalen i en webbläsare.
I Azure-portalen klickar du på knappen Skapa en resurs och söker sedan efter och väljer API App innan du väljer knappen Skapa.
På sidan Skapa API App uppdaterar du följande fält innan du väljer knappen Skapa:
Fält Handling Abonnemang Välj samma målprenumeration som du skapade meddelandehubben i. Resursgrupp Välj samma resursgrupp som du skapade meddelandehubben i. Namn Ange ett globalt unikt namn. Körningsstack Kontrollera att den senaste versionen av .NET är markerad. När API App har etablerats, navigerar du till resursen.
Anteckna standarddomänvärdet på sidan Översikt. Den här URL:en är din serverdelsslutpunkt som ska användas från .NET MAUI-appen. URL:en använder det API-appnamn som du angav, med formatet
https://<app_name>.azurewebsites.net
.I Azure-portalen bläddrar du till bladet Inställningar > Miljö och kontrollerar sedan att fliken Appinställningar är markerad. Använd sedan knappen Lägg till för att lägga till följande inställningar:
Namn Värde Autentisering:ApiKey <api_key_value> NotificationHub:Name <hub_name_value> NotificationHub:ConnectionString <hub_connection_string_value> Viktig
Den
Authentication:ApiKey
programinställningen har lagts till för enkelhetens skull. För produktionsscenarier bör du överväga en tjänst som Azure KeyVault för att lagra anslutningssträngen på ett säkert sätt.När alla dessa inställningar har angetts väljer du knappen Använd och sedan knappen Bekräfta.
Publicera serverdelstjänsten
Så här publicerar du serverdelstjänsten till Azure App Service:
- Högerklicka på projektet i Visual Studio och välj Publicera.
- I Publicera-guiden väljer du Azure och trycker sedan på knappen Nästa.
- I guiden Publicera väljer du Azure App Service (Windows) och sedan knappen Nästa.
- I guiden Publicera följer du autentiseringsflödet för att ansluta Visual Studio till din Azure-prenumeration och publicera appen.
Visual Studio skapar, paketerar och publicerar appen till Azure och startar sedan appen i din standardwebbläsare. Mer information finns i Publicera en ASP.NET-webbapp.
Tips
Du kan ladda ned en publiceringsprofil för din app från bladet Översikt api-appen i Azure-portalen och sedan använda profilen i Visual Studio för att publicera din app.
Verifiera det publicerade API:et
Om du vill kontrollera att API-appen har publicerats korrekt bör du använda valfri REST-verktyg för att skicka en POST
begäran till följande adress:
https://<app_name>.azurewebsites.net/api/notifications/requests
Not
Basadressen är https://<app_name>.azurewebsites.net
.
Se till att du konfigurerar begäranderubrikerna så att de innehåller nyckeln apikey
och dess värde, ange brödtexten till rå och använd följande platshållar-JSON-innehåll:
{}
Du bör få ett 400 Bad Request
-svar från tjänsten.
Not
Det går ännu inte att testa API:et med giltiga begärandedata eftersom detta kräver plattformsspecifik information från .NET MAUI-appen.
Mer information om hur du anropar REST-API:er finns i Använda .http-filer i Visual Studio och Testa webb-API:er med Http Repl-. I Visual Studio Code kan REST-klient användas för att testa REST-API:er.
Skapa en .NET MAUI-app
I det här avsnittet skapar du en .NET Multi-Platform App UI-app (.NET MAUI) som gör att du kan registrera dig för att ta emot push-meddelanden från en meddelandehubb via serverdelstjänsten och avregistrera dig.
Så här skapar du .NET MAUI-appen:
I Visual Studio skapar du en ny .NET MAUI-app med namnet PushNotificationsDemomed hjälp av projektmallen .NET MAUI App.
I Visual Studio lägger du till en ny mapp med namnet Models i .NET MAUI-projektet och lägger sedan till en ny klass med namnet
DeviceInstallation
i mappen Models och ersätter koden med följande kod:using System.Text.Json.Serialization; namespace PushNotificationsDemo.Models; public class DeviceInstallation { [JsonPropertyName("installationId")] public string InstallationId { get; set; } [JsonPropertyName("platform")] public string Platform { get; set; } [JsonPropertyName("pushChannel")] public string PushChannel { get; set; } [JsonPropertyName("tags")] public List<string> Tags { get; set; } = new List<string>(); }
I Visual Studio lägger du till en uppräkning med namnet
PushDemoAction
i mappen Models och ersätter koden med följande kod:namespace PushNotificationsDemo.Models; public enum PushDemoAction { ActionA, ActionB }
I Visual Studio lägger du till en ny mapp med namnet Services till .NET MAUI-projektet och lägger sedan till ett nytt gränssnitt med namnet
IDeviceInstallationService
till mappen Services och ersätter koden med följande kod:using PushNotificationsDemo.Models; namespace PushNotificationsDemo.Services; public interface IDeviceInstallationService { string Token { get; set; } bool NotificationsSupported { get; } string GetDeviceId(); DeviceInstallation GetDeviceInstallation(params string[] tags); }
Det här gränssnittet kommer att implementeras på varje plattform vid ett senare tillfälle för att förse den
DeviceInstallation
-information som krävs av backendtjänsten.I Visual Studio lägger du till ett gränssnitt med namnet
INotificationRegistrationService
i mappen Services och ersätter koden med följande kod:namespace PushNotificationsDemo.Services; public interface INotificationRegistrationService { Task DeregisterDeviceAsync(); Task RegisterDeviceAsync(params string[] tags); Task RefreshRegistrationAsync(); }
Det här gränssnittet hanterar interaktionen mellan klienten och serverdelstjänsten.
I Visual Studio lägger du till ett gränssnitt med namnet
INotificationActionService
i mappen Services och ersätter koden med följande kod:namespace PushNotificationsDemo.Services; public interface INotificationActionService { void TriggerAction(string action); }
Det här gränssnittet används som en enkel mekanism för att centralisera hanteringen av meddelandeåtgärder.
I Visual Studio lägger du till ett gränssnitt med namnet
IPushDemoNotificationActionService
i mappen Services och ersätter koden med följande kod:using PushNotificationsDemo.Models; namespace PushNotificationsDemo.Services; public interface IPushDemoNotificationActionService : INotificationActionService { event EventHandler<PushDemoAction> ActionTriggered; }
Den
IPushDemoNotificationActionService
typen är specifik för den här appen och använderPushDemoAction
uppräkning för att identifiera den åtgärd som utlöses med hjälp av en starkt typad metod.I Visual Studio lägger du till en klass med namnet
NotificationRegistrationService
i mappen Services och ersätter koden med följande kod:using System.Text; using System.Text.Json; using PushNotificationsDemo.Models; namespace PushNotificationsDemo.Services; public class NotificationRegistrationService : INotificationRegistrationService { const string RequestUrl = "api/notifications/installations"; const string CachedDeviceTokenKey = "cached_device_token"; const string CachedTagsKey = "cached_tags"; string _baseApiUrl; HttpClient _client; IDeviceInstallationService _deviceInstallationService; IDeviceInstallationService DeviceInstallationService => _deviceInstallationService ?? (_deviceInstallationService = Application.Current.Windows[0].Page.Handler.MauiContext.Services.GetService<IDeviceInstallationService>()); public NotificationRegistrationService(string baseApiUri, string apiKey) { _client = new HttpClient(); _client.DefaultRequestHeaders.Add("Accept", "application/json"); _client.DefaultRequestHeaders.Add("apikey", apiKey); _baseApiUrl = baseApiUri; } public async Task DeregisterDeviceAsync() { var cachedToken = await SecureStorage.GetAsync(CachedDeviceTokenKey) .ConfigureAwait(false); if (cachedToken == null) return; var deviceId = DeviceInstallationService?.GetDeviceId(); if (string.IsNullOrWhiteSpace(deviceId)) throw new Exception("Unable to resolve an ID for the device."); await SendAsync(HttpMethod.Delete, $"{RequestUrl}/{deviceId}") .ConfigureAwait(false); SecureStorage.Remove(CachedDeviceTokenKey); SecureStorage.Remove(CachedTagsKey); } public async Task RegisterDeviceAsync(params string[] tags) { var deviceInstallation = DeviceInstallationService?.GetDeviceInstallation(tags); await SendAsync<DeviceInstallation>(HttpMethod.Put, RequestUrl, deviceInstallation) .ConfigureAwait(false); await SecureStorage.SetAsync(CachedDeviceTokenKey, deviceInstallation.PushChannel) .ConfigureAwait(false); await SecureStorage.SetAsync(CachedTagsKey, JsonSerializer.Serialize(tags)); } public async Task RefreshRegistrationAsync() { var cachedToken = await SecureStorage.GetAsync(CachedDeviceTokenKey) .ConfigureAwait(false); var serializedTags = await SecureStorage.GetAsync(CachedTagsKey) .ConfigureAwait(false); if (string.IsNullOrWhiteSpace(cachedToken) || string.IsNullOrWhiteSpace(serializedTags) || string.IsNullOrWhiteSpace(_deviceInstallationService.Token) || cachedToken == DeviceInstallationService.Token) return; var tags = JsonSerializer.Deserialize<string[]>(serializedTags); await RegisterDeviceAsync(tags); } async Task SendAsync<T>(HttpMethod requestType, string requestUri, T obj) { string serializedContent = null; await Task.Run(() => serializedContent = JsonSerializer.Serialize(obj)) .ConfigureAwait(false); await SendAsync(requestType, requestUri, serializedContent); } async Task SendAsync(HttpMethod requestType, string requestUri, string jsonRequest = null) { var request = new HttpRequestMessage(requestType, new Uri($"{_baseApiUrl}{requestUri}")); if (jsonRequest != null) request.Content = new StringContent(jsonRequest, Encoding.UTF8, "application/json"); var response = await _client.SendAsync(request).ConfigureAwait(false); response.EnsureSuccessStatusCode(); } }
I Visual Studio lägger du till en klass med namnet
PushDemoNotificationActionService
i mappen Services och ersätter koden med följande kod:using PushNotificationsDemo.Models; namespace PushNotificationsDemo.Services; public class PushDemoNotificationActionService : IPushDemoNotificationActionService { readonly Dictionary<string, PushDemoAction> _actionMappings = new Dictionary<string, PushDemoAction> { { "action_a", PushDemoAction.ActionA }, { "action_b", PushDemoAction.ActionB } }; public event EventHandler<PushDemoAction> ActionTriggered = delegate { }; public void TriggerAction(string action) { if (!_actionMappings.TryGetValue(action, out var pushDemoAction)) return; List<Exception> exceptions = new List<Exception>(); foreach (var handler in ActionTriggered?.GetInvocationList()) { try { handler.DynamicInvoke(this, pushDemoAction); } catch (Exception ex) { exceptions.Add(ex); } } if (exceptions.Any()) throw new AggregateException(exceptions); } }
I Visual Studio lägger du till en klass med namnet
Config
i projektets rot och ersätter koden med följande kod:namespace PushNotificationsDemo; public static partial class Config { public static string ApiKey = "API_KEY"; public static string BackendServiceEndpoint = "BACKEND_SERVICE_ENDPOINT"; }
Klassen
Config
används som ett enkelt sätt att hålla hemligheterna borta från källkontrollen. Du kan ersätta dessa värden som en del av en automatiserad version eller åsidosätta dem med hjälp av en lokal partiell klass.Viktig
När du anger basadressen i .NET MAUI-appen ska du se till att den slutar med en
/
.I Visual Studio lägger du till en klass med namnet
Config.local_secrets
i projektets rot. Ersätt sedan koden i filen Config.local_secrets.cs med följande kod:namespace PushNotificationsDemo; public static partial class Config { static Config() { ApiKey = "<your_api_key>"; BackendServiceEndpoint = "<your_api_app_url>"; } }
Ersätt platshållarvärdena med de värden du valde när du skapade serverdelstjänsten. URL:en för
BackendServiceEndpoint
ska använda formatethttps://<api_app_name>.azurewebsites.net/
.Tips
Kom ihåg att lägga till
*.local_secrets.*
i din.gitignore
-fil för att undvika att infoga filen i versionshantering.
Skapa användargränssnittet
Så här skapar du appens användargränssnitt:
Öppna MainPage.xaml i Visual Studio och ersätt
VerticalStackLayout
och dess underordnade med följande XAML:<VerticalStackLayout Margin="20" Spacing="6"> <Button x:Name="registerButton" Text="Register" Clicked="OnRegisterButtonClicked" /> <Button x:Name="deregisterButton" Text="Deregister" Clicked="OnDeregisterButtonClicked" /> </VerticalStackLayout>
Öppna MainPage.xaml.cs i Visual Studio och lägg till en
using
-instruktion förPushNotificationsDemo.Services
-namnområdet:using PushNotificationsDemo.Services;
I MainPage.xaml.cslägger du till ett
readonly
bakgrundsfält för att lagra en referens till implementeringen avINotificationRegistrationService
:readonly INotificationRegistrationService _notificationRegistrationService;
I
MainPage
konstruktorn löser du implementeringen avINotificationRegistrationService
och tilldelar den till_notificationRegistrationService
-bakgrundsfältet:public MainPage(INotificationRegistrationService service) { InitializeComponent(); _notificationRegistrationService = service; }
I klassen
MainPage
implementerar du händelsehanterarnaOnRegisterButtonClicked
ochOnDeregisterButtonClicked
och anropar motsvarande register- och avregistermetoder förINotificationRegistrationService
-objektet:void OnRegisterButtonClicked(object sender, EventArgs e) { _notificationRegistrationService.RegisterDeviceAsync() .ContinueWith((task) => { ShowAlert(task.IsFaulted ? task.Exception.Message : $"Device registered"); }); } void OnDeregisterButtonClicked(object sender, EventArgs e) { _notificationRegistrationService.DeregisterDeviceAsync() .ContinueWith((task) => { ShowAlert(task.IsFaulted ? task.Exception.Message : $"Device deregistered"); }); } void ShowAlert(string message) { MainThread.BeginInvokeOnMainThread(() => { DisplayAlert("Push notifications demo", message, "OK") .ContinueWith((task) => { if (task.IsFaulted) throw task.Exception; }); }); }
Viktig
I appen utförs registrering och avregistrering som svar på användarindata så att den här funktionen kan utforskas och testas enklare. I en produktionsapp utför du vanligtvis registrerings- och avregistreringsåtgärderna under lämplig punkt i appens livscykel, utan att uttryckligen behöva användarindata.
Öppna App.xaml.cs i Visual Studio och lägg till följande
using
-instruktioner:using PushNotificationsDemo.Models; using PushNotificationsDemo.Services;
I App.xaml.cslägger du till ett
readonly
-bakgrundsfält för att lagra en referens tillIPushDemoNotificationActionService
-implementeringen:readonly IPushDemoNotificationActionService _actionService;
I konstruktor för
App
ska du lösa implementeringen avIPushDemoNotificationActionService
, tilldela den till det bakomliggande fältet_actionService
och prenumerera på händelsenIPushDemoNotificationActionService.ActionTriggered
.public App(IPushDemoNotificationActionService service) { InitializeComponent(); _actionService = service; _actionService.ActionTriggered += NotificationActionTriggered; MainPage = new AppShell(); }
I
App
konstruktorn löser du implementeringen avIPushDemoNotificationActionService
och tilldelar den till_actionService
-säkerhetskopieringsfältet och prenumererar på händelsenIPushDemoNotificationActionService.ActionTriggered
:public App(IPushDemoNotificationActionService service) { InitializeComponent(); _actionService = service; _actionService.ActionTriggered += NotificationActionTriggered; }
I klassen
App
implementerar du händelsehanteraren för denIPushDemoNotificationActionService.ActionTriggered
händelsen:void NotificationActionTriggered(object sender, PushDemoAction e) { ShowActionAlert(e); } void ShowActionAlert(PushDemoAction action) { MainThread.BeginInvokeOnMainThread(() => { Windows[0].Page?.DisplayAlert("Push notifications demo", $"{action} action received.", "OK") .ContinueWith((task) => { if (task.IsFaulted) throw task.Exception; }); }); }
Händelsehanteraren för den
ActionTriggered
händelsen visar mottagandet och spridningen av push-meddelandeåtgärder. Dessa skulle vanligtvis hanteras tyst, till exempel navigera till en viss vy eller uppdatera vissa data i stället för att visa en avisering.
Konfigurera Android-appen
Så här konfigurerar du .NET MAUI-appen på Android för att ta emot och bearbeta push-meddelanden:
I Visual Studio lägger du till Xamarin.Firebase.Messaging- NuGet-paketet i ditt .NET MAUI-appprojekt.
I Visual Studio lägger du till din google-services.json-fil i mappen Platforms/Android i ditt .NET MAUI-appprojekt. När filen har lagts till i projektet bör den ha lagts till med en byggåtgärd av
GoogleServicesJson
:<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-android'"> <GoogleServicesJson Include="Platforms\Android\google-services.json" /> </ItemGroup>
Tips
Kom ihåg att lägga till
google-services.json
i din.gitignore
-fil för att undvika att spara filen i versionskontroll.I Visual Studio redigerar du projektfilen (*.csproj) och anger
SupportedOSPlatformVersion
för Android till 26.0:<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">26.0</SupportedOSPlatformVersion>
Google har gjort ändringar i Android-meddelandekanaler i API 26. Mer information finns i Meddelandekanaler på developer.android.com.
I mappen Platforms/Android i projektet lägger du till en ny klass med namnet
DeviceInstallationService
och ersätter koden med följande kod:using Android.Gms.Common; using PushNotificationsDemo.Models; using PushNotificationsDemo.Services; using static Android.Provider.Settings; namespace PushNotificationsDemo.Platforms.Android; public class DeviceInstallationService : IDeviceInstallationService { public string Token { get; set; } public bool NotificationsSupported => GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(Platform.AppContext) == ConnectionResult.Success; public string GetDeviceId() => Secure.GetString(Platform.AppContext.ContentResolver, Secure.AndroidId); public DeviceInstallation GetDeviceInstallation(params string[] tags) { if (!NotificationsSupported) throw new Exception(GetPlayServicesError()); if (string.IsNullOrWhiteSpace(Token)) throw new Exception("Unable to resolve token for FCMv1."); var installation = new DeviceInstallation { InstallationId = GetDeviceId(), Platform = "fcmv1", PushChannel = Token }; installation.Tags.AddRange(tags); return installation; } string GetPlayServicesError() { int resultCode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(Platform.AppContext); if (resultCode != ConnectionResult.Success) return GoogleApiAvailability.Instance.IsUserResolvableError(resultCode) ? GoogleApiAvailability.Instance.GetErrorString(resultCode) : "This device isn't supported."; return "An error occurred preventing the use of push notifications."; } }
Den här klassen tilldelar ett unikt ID med värdet
Secure.AndroidId
och hanterar nyttolasten för registreringen av meddelandehubben.I mappen Platforms/Android i projektet lägger du till en ny klass med namnet
PushNotificationFirebaseMessagingService
och ersätter koden med följande kod:using Android.App; using Firebase.Messaging; using PushNotificationsDemo.Services; namespace PushNotificationsDemo.Platforms.Android; [Service(Exported = false)] [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })] public class PushNotificationFirebaseMessagingService : FirebaseMessagingService { IPushDemoNotificationActionService _notificationActionService; INotificationRegistrationService _notificationRegistrationService; IDeviceInstallationService _deviceInstallationService; int _messageId; IPushDemoNotificationActionService NotificationActionService => _notificationActionService ?? (_notificationActionService = IPlatformApplication.Current.Services.GetService<IPushDemoNotificationActionService>()); INotificationRegistrationService NotificationRegistrationService => _notificationRegistrationService ?? (_notificationRegistrationService = IPlatformApplication.Current.Services.GetService<INotificationRegistrationService>()); IDeviceInstallationService DeviceInstallationService => _deviceInstallationService ?? (_deviceInstallationService = IPlatformApplication.Current.Services.GetService<IDeviceInstallationService>()); public override void OnNewToken(string token) { DeviceInstallationService.Token = token; NotificationRegistrationService.RefreshRegistrationAsync() .ContinueWith((task) => { if (task.IsFaulted) throw task.Exception; }); } public override void OnMessageReceived(RemoteMessage message) { base.OnMessageReceived(message); if (message.Data.TryGetValue("action", out var messageAction)) NotificationActionService.TriggerAction(messageAction); } }
Den här klassen har ett
IntentFilter
attribut som innehållercom.google.firebase.MESSAGING_EVENT
-filtret. Det här filtret gör att Android kan skicka inkommande meddelanden till den här klassen för bearbetning.Information om meddelandeformatet Firebase Cloud Messaging finns i Om FCM-meddelanden på developer.android.com.
I Visual Studio öppnar du filen MainActivity.cs i mappen Platforms/Android och lägger till följande
using
-instruktioner:using Android.App; using Android.Content; using Android.Content.PM; using Android.OS; using PushNotificationsDemo.Services; using Firebase.Messaging;
I klassen
MainActivity
anger duLaunchMode
tillSingleTop
så attMainActivity
inte skapas igen när den öppnas:[Activity( Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
I klassen
MainActivity
lägger du till bakgrundsfält för att lagra referenser till implementeringarnaIPushDemoNotificationActionService
ochIDeviceInstallationService
:IPushDemoNotificationActionService _notificationActionService; IDeviceInstallationService _deviceInstallationService;
I klassen
MainActivity
lägger du tillNotificationActionService
ochDeviceInstallationService
privata egenskaper som hämtar deras konkreta implementeringar från appens container för beroendeinmatning:IPushDemoNotificationActionService NotificationActionService => _notificationActionService ?? (_notificationActionService = IPlatformApplication.Current.Services.GetService<IPushDemoNotificationActionService>()); IDeviceInstallationService DeviceInstallationService => _deviceInstallationService ?? (_deviceInstallationService = IPlatformApplication.Current.Services.GetService<IDeviceInstallationService>());
I klassen
MainActivity
implementerar duAndroid.Gms.Tasks.IOnSuccessListener
-gränssnittet för att hämta och lagra Firebase-token:public class MainActivity : MauiAppCompatActivity, Android.Gms.Tasks.IOnSuccessListener { public void OnSuccess(Java.Lang.Object result) { DeviceInstallationService.Token = result.ToString(); } }
I klassen
MainActivity
lägger du till metodenProcessNotificationActions
som kontrollerar om en vissIntent
har ett extra värde med namnetaction
och sedan villkorligt utlöser denaction
med hjälp avIPushDemoNotificationActionService
implementering:void ProcessNotificationsAction(Intent intent) { try { if (intent?.HasExtra("action") == true) { var action = intent.GetStringExtra("action"); if (!string.IsNullOrEmpty(action)) NotificationActionService.TriggerAction(action); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } }
I klassen
MainActivity
åsidosätter du metodenOnNewIntent
för att anropa metodenProcessNotificationActions
:protected override void OnNewIntent(Intent? intent) { base.OnNewIntent(intent); ProcessNotificationsAction(intent); }
Eftersom
LaunchMode
förActivity
är inställd påSingleTop
skickas enIntent
till den befintligaActivity
-instansen viaOnNewIntent
överskridande, snarare än metodenOnCreate
. Därför måste du hantera en inkommande intent i bådeOnNewIntent
ochOnCreate
.I klassen
MainActivity
åsidosätter du metodenOnCreate
för att anropa metodenProcessNotificationActions
och för att hämta token från Firebase lägger du tillMainActivity
somIOnSuccessListener
:protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); if (DeviceInstallationService.NotificationsSupported) FirebaseMessaging.Instance.GetToken().AddOnSuccessListener(this); ProcessNotificationsAction(Intent); }
Not
Appen måste registreras igen varje gång du kör den och stoppa den från en felsökningssession för att fortsätta ta emot push-meddelanden.
I Visual Studio lägger du till behörigheten
POST_NOTIFICATIONS
i filen AndroidManifest.xml i mappen Platforms/Android:<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Mer information om den här behörigheten finns i Meddelandekörningsbehörighet på developer.android.com.
Öppna MainPage.xaml.cs i Visual Studio och lägg till följande kod i klassen
MainPage
:#if ANDROID protected override async void OnAppearing() { base.OnAppearing(); PermissionStatus status = await Permissions.RequestAsync<Permissions.PostNotifications>(); } #endif
Den här koden körs på Android när
MainPage
visas och begär att användaren beviljarPOST_NOTIFICATIONS
behörighet. Mer information om .NET MAUI-behörigheter finns i Behörigheter.
Konfigurera iOS-appen
iOS-simulatorn stöder fjärrmeddelanden i iOS 16+ när den körs i macOS 13+ på Mac-datorer med Apple-kisel- eller T2-processorer. Varje simulator genererar registreringstoken som är unika för kombinationen av simulatorn och den Mac-maskinvara som den körs på.
Viktig
Simulatorn stöder sandbox-miljön för Apple Push Notification Service.
Följande instruktioner förutsätter att du använder maskinvara som har stöd för att ta emot fjärrmeddelanden i en iOS-simulator. Om så inte är fallet måste du köra iOS-appen på en fysisk enhet, vilket kräver att du skapar en etableringsprofil för din app som innehåller funktionen Push-meddelanden. Sedan måste du se till att din app byggs med hjälp av ditt certifikat och din provisioningprofil. Mer information om hur du gör detta finns i Konfigurera din iOS-app så att den fungerar med Azure Notification Hubsoch följ sedan anvisningarna nedan.
Så här konfigurerar du .NET MAUI-appen på iOS för att ta emot och bearbeta push-meddelanden:
I Visual Studio redigerar du projektfilen (*.csproj) och anger
SupportedOSPlatformVersion
för iOS till 13.0:<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">13.0</SupportedOSPlatformVersion>
Apple gjorde ändringar i sin push-tjänst i iOS 13. Mer information finns i Azure Notification Hubs-uppdateringar för iOS 13.
I Visual Studio lägger du till en Entitlements.plist--fil i mappen Platforms/iOS i projektet och lägger till följande XML i filen:
<?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>
Detta anger berättigande för APS-miljön och anger att du ska använda utvecklingsmiljön för Apple Push Notification Service. I produktionsappar ska det här berättigandevärdet anges till
production
. Mer information om den här behörigheten finns i APS-miljörättigheter på developer.apple.com.Mer information om hur du lägger till en rättighetsfil finns i iOS-berättiganden.
I Visual Studio lägger du till en ny klass med namnet
DeviceInstallationService
i mappen Platforms/iOS i projektet och lägger till följande kod i filen:using PushNotificationsDemo.Services; using PushNotificationsDemo.Models; using UIKit; namespace PushNotificationsDemo.Platforms.iOS; public class DeviceInstallationService : IDeviceInstallationService { const int SupportedVersionMajor = 13; const int SupportedVersionMinor = 0; public string Token { get; set; } public bool NotificationsSupported => UIDevice.CurrentDevice.CheckSystemVersion(SupportedVersionMajor, SupportedVersionMinor); public string GetDeviceId() => UIDevice.CurrentDevice.IdentifierForVendor.ToString(); public DeviceInstallation GetDeviceInstallation(params string[] tags) { if (!NotificationsSupported) throw new Exception(GetNotificationsSupportError()); if (string.IsNullOrWhiteSpace(Token)) throw new Exception("Unable to resolve token for APNS"); var installation = new DeviceInstallation { InstallationId = GetDeviceId(), Platform = "apns", PushChannel = Token }; installation.Tags.AddRange(tags); return installation; } string GetNotificationsSupportError() { if (!NotificationsSupported) return $"This app only supports notifications on iOS {SupportedVersionMajor}.{SupportedVersionMinor} and above. You are running {UIDevice.CurrentDevice.SystemVersion}."; if (Token == null) return $"This app can support notifications but you must enable this in your settings."; return "An error occurred preventing the use of push notifications"; } }
Den här klassen tillhandahåller ett unikt ID med värdet
UIDevice.IdentifierForVendor
och nyttolast för registreringen av meddelandehubben.I Visual Studio lägger du till en ny klass med namnet
NSDataExtensions
i mappen Platforms/iOS i projektet och lägger till följande kod i filen:using Foundation; using System.Text; namespace PushNotificationsDemo.Platforms.iOS; internal static class NSDataExtensions { internal static string ToHexString(this NSData data) { var bytes = data.ToArray(); if (bytes == null) return null; StringBuilder sb = new StringBuilder(bytes.Length * 2); foreach (byte b in bytes) sb.AppendFormat("{0:x2}", b); return sb.ToString().ToUpperInvariant(); } }
Utökningsmetoden
ToHexString
kommer att användas av den kod som du lägger till för att parsa den hämtade enhetstoken.I Visual Studio öppnar du filen AppDelegate.cs i mappen Platforms/iOS och lägger till följande
using
-instruktioner:using System.Diagnostics; using Foundation; using PushNotificationsDemo.Platforms.iOS; using PushNotificationsDemo.Services; using UIKit; using UserNotifications;
I klassen
AppDelegate
lägger du till bakgrundsfält för att lagra referenser till implementeringarnaIPushDemoNotificationActionService
,INotificationRegistrationService
ochIDeviceInstallationService
:IPushDemoNotificationActionService _notificationActionService; INotificationRegistrationService _notificationRegistrationService; IDeviceInstallationService _deviceInstallationService;
I klassen
AppDelegate
lägger du tillNotificationActionService
,NotificationRegistrationService
ochDeviceInstallationService
privata egenskaper som hämtar sina konkreta implementeringar från appens container för beroendeinmatning:IPushDemoNotificationActionService NotificationActionService => _notificationActionService ?? (_notificationActionService = IPlatformApplication.Current.Services.GetService<IPushDemoNotificationActionService>()); INotificationRegistrationService NotificationRegistrationService => _notificationRegistrationService ?? (_notificationRegistrationService = IPlatformApplication.Current.Services.GetService<INotificationRegistrationService>()); IDeviceInstallationService DeviceInstallationService => _deviceInstallationService ?? (_deviceInstallationService = IPlatformApplication.Current.Services.GetService<IDeviceInstallationService>());
I klassen
AppDelegate
lägger du till metodenCompleteRegistrationAsync
för att ange egenskapsvärdetIDeviceInstallationService.Token
:Task CompleteRegistrationAsync(NSData deviceToken) { DeviceInstallationService.Token = deviceToken.ToHexString(); return NotificationRegistrationService.RefreshRegistrationAsync(); }
Den här metoden uppdaterar också registreringen och cachelagrar enhetstoken om den har uppdaterats sedan den senast lagrades.
I klassen
AppDelegate
lägger du tillProcessNotificationActions
-metoden för bearbetning avNSDictionary
-meddelandedata och anropar villkorligtNotificationActionService.TriggerAction
:void ProcessNotificationActions(NSDictionary userInfo) { if (userInfo == null) return; try { // If your app isn't in the foreground, the notification goes to Notification Center. // If your app is in the foreground, the notification goes directly to your app and you // need to process the notification payload yourself. var actionValue = userInfo.ObjectForKey(new NSString("action")) as NSString; if (!string.IsNullOrWhiteSpace(actionValue?.Description)) NotificationActionService.TriggerAction(actionValue.Description); } catch (Exception ex) { Debug.WriteLine(ex.Message); } }
I klassen
AppDelegate
lägger du till metodenRegisteredForRemoteNotifications
som skickar argumentetdeviceToken
till metodenCompleteRegistrationAsync
:[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")] public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken) { CompleteRegistrationAsync(deviceToken) .ContinueWith((task) => { if (task.IsFaulted) throw task.Exception; }); }
Den här metoden anropas när appen är registrerad för att ta emot fjärrmeddelanden och används för att begära den unika enhetstoken, som i praktiken är adressen till din app på enheten.
I klassen
AppDelegate
lägger du till metodenReceivedRemoteNotification
som skickar argumentetuserInfo
till metodenProcessNotificationActions
:[Export("application:didReceiveRemoteNotification:")] public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo) { ProcessNotificationActions(userInfo); }
Den här metoden anropas när appen har tagit emot ett fjärrmeddelande och används för att bearbeta meddelandet.
I klassen
AppDelegate
lägger du till metodenFailedToRegisterForRemoteNotifications
för att logga eventuella fel:[Export("application:didFailToRegisterForRemoteNotificationsWithError:")] public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error) { Debug.WriteLine(error.Description); }
Den här metoden anropas när appen inte har registrerats för att ta emot fjärrmeddelanden. Registreringen kan misslyckas om enheten inte är ansluten till nätverket, om APNS-servern inte kan nås eller om appen är felaktigt konfigurerad.
Obs
För produktionsscenarier vill du implementera korrekt loggning och felhantering i metoden
FailedToRegisterForRemoteNotifications
.I klassen
AppDelegate
lägger du till metodenFinishedLaunching
för att villkorligt begära behörighet att använda meddelanden och registrera dig för fjärrmeddelanden:[Export("application:didFinishLaunchingWithOptions:")] public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { if (DeviceInstallationService.NotificationsSupported) { UNUserNotificationCenter.Current.RequestAuthorization( UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound, (approvalGranted, error) => { if (approvalGranted && error == null) { MainThread.BeginInvokeOnMainThread(() => { UIApplication.SharedApplication.RegisterForRemoteNotifications(); }); } }); } using (var userInfo = launchOptions?.ObjectForKey(UIApplication.LaunchOptionsRemoteNotificationKey) as NSDictionary) { ProcessNotificationActions(userInfo); } return base.FinishedLaunching(application, launchOptions); }
Information om hur du ber om behörighet att använda meddelanden finns i Fråga om behörighet att använda meddelanden på developer.apple.com.
Information om meddelanden i iOS finns i användaraviseringar på developer.apple.com.
Registrera typer med appens beroendeinjektionscontainer
Öppna MauiProgram.cs i Visual Studio och lägg till en
using
-instruktion förPushNotificationsDemo.Services
-namnområdet:using PushNotificationsDemo.Services;
I klassen
MauiProgram
lägger du till kod förRegisterServices
-tilläggsmetoden som registrerarDeviceInstallationService
på varje plattform samt plattformsoberoendePushDemoNotificationActionService
- ochNotificationRegistrationService
-tjänster och som returnerar ettMauiAppBuilder
-objekt:public static MauiAppBuilder RegisterServices(this MauiAppBuilder builder) { #if IOS builder.Services.AddSingleton<IDeviceInstallationService, PushNotificationsDemo.Platforms.iOS.DeviceInstallationService>(); #elif ANDROID builder.Services.AddSingleton<IDeviceInstallationService, PushNotificationsDemo.Platforms.Android.DeviceInstallationService>(); #endif builder.Services.AddSingleton<IPushDemoNotificationActionService, PushDemoNotificationActionService>(); builder.Services.AddSingleton<INotificationRegistrationService>(new NotificationRegistrationService(Config.BackendServiceEndpoint, Config.ApiKey)); return builder; }
I klassen
MauiProgram
lägger du till kod förRegisterViews
-tilläggsmetoden som registrerarMainPage
typ som en singleton och som returnerar ettMauiAppBuilder
-objekt:public static MauiAppBuilder RegisterViews(this MauiAppBuilder builder) { builder.Services.AddSingleton<MainPage>(); return builder; }
Den
MainPage
typen registreras eftersom den kräver ettINotificationRegistrationService
beroende och alla typer som kräver ett beroende måste registreras med containern för beroendeinmatning.I klassen
MauiProgram
ändrar du metodenCreateMauiApp
så att den anroparRegisterServices
- ochRegisterViews
-tilläggsmetoderna:public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }) .RegisterServices() .RegisterViews(); #if DEBUG builder.Logging.AddDebug(); #endif return builder.Build(); }
Mer information om beroendeinmatning i .NET MAUI finns i Beroendeinmatning.
Testa appen
Du kan testa din app genom att skicka push-meddelanden till appen med hjälp av serverdelstjänsten eller via Azure-portalen.
iOS-simulatorn stöder fjärrmeddelanden i iOS 16+ när den körs i macOS 13+ på Mac-datorer med Apple-kisel- eller T2-processorer. Om du inte uppfyller dessa maskinvarukrav måste du testa din iOS-app på en fysisk enhet. På Android kan du testa din app på en utvecklarupplåst fysisk enhet eller en emulator.
Android och iOS visar push-meddelanden för appens räkning när den körs i bakgrunden. Om appen körs i förgrunden när meddelandet tas emot avgör appens kod beteendet. Du kan till exempel uppdatera appens gränssnitt så att det återspeglar ny information som finns i meddelandet.
Testa med backend-tjänsten
Så här skickar du ett push-testmeddelande till din app via serverdelstjänsten som publiceras till Azure App Service:
I Visual Studio kör du appen PushNotificationsDemo på Android eller iOS och väljer knappen Registrera.
Not
Om du testar på Android kontrollerar du att du inte kör med hjälp av felsökningskonfigurationen. Om appen tidigare har distribuerats kan du också se till att den har tvingats stängas och sedan starta den igen från startprogrammet.
I valfri REST-verktyg skickar du en
POST
begäran till följande adress:https://<app_name>.azurewebsites.net/api/notifications/requests
Se till att du konfigurerar begärandehuvudena så att de innehåller nyckeln
apikey
och dess värde, ange brödtexten till rå och använd följande JSON-innehåll:{ "text": "Message from REST tooling!", "action": "action_a" }
Den övergripande begäran bör likna följande exempel:
POST /api/notifications/requests HTTP/1.1 Host: https://<app_name>.azurewebsites.net apikey: <your_api_key> Content-Type: application/json { "text": "Message from REST tooling!", "action": "action_a" }
I det REST-verktyg du väljer, kontrollera att du får ett 200 OK svar.
I appen på Android eller iOS ska en avisering visas som säger att åtgärden ActionA har tagits emot.
Mer information om hur du anropar REST-API:er finns i Använda .http-filer i Visual Studio och Testa webb-API:er med Http Repl-. I Visual Studio Code kan REST-klient användas för att testa REST-API:er.
Testa genom hjälp av Azure-portalen
Med Azure Notification Hubs kan du kontrollera att din app kan ta emot push-meddelanden.
Så här skickar du ett push-testmeddelande till din app via Azure-portalen:
I Visual Studio kör du appen PushNotificationsDemo på Android eller iOS och väljer knappen Registrera.
Obs
Om du testar på Android kontrollerar du att du inte kör med hjälp av felsökningskonfigurationen. Alternativt, om appen tidigare har distribuerats, se till att den har tvingats stängas och starta sedan den igen från startskärmen.
I Azure-portalenbläddrar du till meddelandehubben och väljer knappen Testa skicka på bladet Översikt.
I panelen Test Send väljer du din Plattform och ändrar nyttolasten.
För Apple använder du följande nyttolast:
{ "aps": { "alert": "Message from Notification Hub!" }, "action": "action_a" }
För Android använder du följande nyttolast:
{ "message": { "notification": { "title": "PushDemo", "body": "Message from Notification Hub!" }, "data": { "action": "action_a" } } }
Azure-portalen bör ange att meddelandet har skickats.
Information om meddelandeformatet Firebase Cloud Messaging finns i Om FCM-meddelanden på developer.android.com.
I appen på Android eller iOS ska en avisering visas med ActionA-åtgärdsom tagits emot.
Felsökning
I följande avsnitt beskrivs de vanliga problem som uppstår vid försök att använda push-meddelanden i en klientapp.
Inget svar från bakgrundstjänsten
När du testar lokalt kontrollerar du att serverdelstjänsten körs och använder rätt port.
Om du testar mot Azure API-appen kontrollerar du att tjänsten körs och har distribuerats och har startats utan fel.
Kontrollera att du har angett basadressen korrekt i REST-verktygen eller i konfigurationen av .NET MAUI-appen. Basadressen ska vara https://<api_name>.azurewebsites.net
eller https://localhost:7020
när du testar lokalt.
Ta emot en 401-statuskod från serverdelstjänsten
Kontrollera att du ställer in apikey
begärandehuvudet korrekt och att det här värdet matchar det som du har konfigurerat för serverdelstjänsten.
Om du får det här felet när du testar lokalt kontrollerar du att nyckelvärdet som du definierade i .NET MAUI-appen matchar värdet Authentication:ApiKey
användarhemligheter som används av serverdelstjänsten.
Om du testar med en Azure API-app kontrollerar du att nyckelvärdet som definierats i .NET MAUI-appen matchar det Authentication:ApiKey
appinställningsvärde som definierats i Azure-portalen. Om du har skapat eller ändrat den här appinställningen efter att du har distribuerat serverdelstjänsten måste du starta om tjänsten för att värdet ska börja gälla.
Ta emot en 404-statuskod från serverdelstjänsten
Kontrollera att metoden för slutpunkts- och HTTP-begäran är korrekt:
- PUT -
https://<api_name>.azurewebsites.net/api/notifications/installations
- TA BORT –
https://<api_name>.azurewebsites.net/api/notifications/installations/<installation_id>
- POST -
https://<api_name>.azurewebsites.net/api/notifications/requests
Eller när du testar lokalt:
- PUT -
https://localhost:7020/api/notifications/installations
- TA BORT –
https://localhost:7020/api/notifications/installations/<installation_id>
- POST -
https://localhost:7020/api/notifications/requests
Viktig
När du anger basadressen i .NET MAUI-appen ska du se till att den slutar med en /
. Basadressen ska vara https://<api_name>.azurewebsites.net
eller https://localhost:7020/
när du testar lokalt.
Får inte meddelanden på Android efter att ha startat eller stoppat en felsökningssession
Kontrollera att du registrerar dig varje gång du startar en felsökningssession. Felsökningsprogrammet gör att en ny Firebase-token genereras och därför måste installationen av meddelandehubben uppdateras.
Det går inte att registrera och felmeddelandet för meddelandehubben visas
Kontrollera att testenheten har nätverksanslutning. Bestäm sedan statuskoden för HTTP-svar genom att ange en brytpunkt för att inspektera egenskapen StatusCode
i HttpResponse
.
Granska de tidigare felsökningsförslagen, i förekommande fall, baserat på statuskoden.
Ange en brytpunkt på de rader som returnerar specifika statuskoder för respektive API. Försök sedan att anropa serverdelstjänsten när du felsöker lokalt.
Verifiera att serverdelstjänsten fungerar som förväntat med valfritt REST-verktyg och använd den nyttolast som skapats av .NET MAUI-appen för den valda plattformen.
Granska de plattformsspecifika konfigurationsavsnitten för att se till att inga steg har missats. Kontrollera att lämpliga värden löses för InstallationId
och Token
variabler för den valda plattformen.
Det går inte att identifiera ett ID för enhetsfelmeddelandet.
Granska de plattformsspecifika konfigurationsavsnitten för att se till att inga steg har missats.