Руководство. Отправка push-уведомлений в приложения MAUI .NET с помощью Центров уведомлений Azure через серверную службу
Push-уведомления предоставляют информацию из серверной системы клиентскому приложению. Платформы Apple, Google и других поставщиков имеют свои собственные службы push-уведомлений (Push Notification Services, PNS). Центры уведомлений Azure позволяют централизировать уведомления на разных платформах, чтобы серверное приложение могли взаимодействовать с одним концентратором, который отвечает за распространение уведомлений по каждому PNS.
Центры уведомлений Azure требуют, чтобы приложения регистрируются в центре и при необходимости определяют шаблоны и (или) подписываются на теги:
- Установка устройства связывает дескриптор PNS с идентификатором в Центре уведомлений Azure. Дополнительные сведения о регистрации см. в разделе "Управление регистрацией".
- Шаблоны позволяют устройствам задавать параметризованные шаблоны сообщений. Входящие сообщения можно настроить на каждое устройство. Дополнительные сведения см. в разделе "Шаблоны центров уведомлений".
- Теги можно использовать для подписки на определенные категории сообщений, например на новости, спорт или погоду. Дополнительные сведения см. в статье Маршрутизация и выражения тегов.
В этом руководстве вы будете использовать Центры уведомлений Azure для отправки push-уведомлений в приложение .NET Multi-platform App UI (.NET MAUI), предназначенное для Android и iOS. Серверная часть веб-API ASP.NET Core используется для обработки регистрации устройств для клиента и запуска push-уведомлений. Эти операции обрабатываются с помощью пакета NuGet Microsoft.Azure.NotificationHubs . Дополнительные сведения об общем подходе см. в разделе "Управление регистрацией" из серверной части.
Изучив это руководство, вы:
- Настройка служб push-уведомлений и Центра уведомлений Azure.
- Создайте серверное приложение ASP.NET Core WebAPI.
- Создайте приложение .NET MAUI.
- Настройте приложение Android для push-уведомлений.
- Настройте приложение iOS для push-уведомлений.
- Тестирование приложения.
- Устранение неполадок при установке и конфигурации.
Предварительные условия
Чтобы завершить работу с этим руководством, вам потребуется:
- Учетная запись Azure с активной подпиской.
- Компьютер или Mac с последней версией Visual Studio/Visual Studio Code с рабочей нагрузкой разработки многоплатформенного пользовательского интерфейса приложений .NET и установленными рабочими нагрузками ASP.NET и веб-разработки.
Для Android необходимо иметь следующее:
- Разработчик разблокировал физическое устройство или эмулятор под управлением API 26+ с установленными службами Google Play.
Для iOS требуется следующее:
- Активная учетная запись разработчика Apple.
- компьютер Mac с Xcode, а также действительный сертификат разработчика, установленный в цепочку ключей;
Затем у вас в iOS должно быть одно из следующих:
Симулятор iOS 16+, работающий в macOS 13+ на компьютерах Mac с процессорами Apple silicon или T2.
ИЛИ
Физическое устройство iOS, зарегистрированное в учетной записи разработчика (под управлением iOS 13.0+).
Физическое устройство, зарегистрированное в учетной записи разработчика Apple, и связанное с вашим сертификатом.
Внимание
Симулятор iOS поддерживает удаленные уведомления в iOS 16+ при запуске в macOS 13+ на компьютерах Mac с процессорами Apple silicon или T2. Если вы не соответствуете этим требованиям к оборудованию, вам потребуется активная учетная запись разработчика Apple и физическое устройство.
Чтобы следовать этому руководству, необходимо ознакомиться со следующими сведениями:
Хотя это руководство предназначено для Visual Studio, его можно следовать с помощью Visual Studio Code на пк или Mac. Однако существуют некоторые различия, которые нуждаются в согласовании. Например, описания пользовательского интерфейса и рабочих процессов, имен шаблонов и конфигурации среды.
Настройка служб push-уведомлений и Центра уведомлений Azure
В этом разделе описана настройка Firebase Cloud Messaging и служб push-уведомлений Apple (APNS). Затем вы создадите и настроите Центр уведомлений Azure для работы с этими службами.
Создание проекта Firebase
Чтобы создать проект Firebase, выполните приведенные действия.
В веб-браузере войдите в консоль Firebase.
В консоли Firebase нажмите кнопку "Добавить проект " и создайте новый проект Firebase, введя PushDemo в качестве имени проекта.
Примечание.
Для вас будет автоматически сгенерировано уникальное название. По умолчанию это представляет собой название в нижнем регистре, которое вы указали, плюс сгенерированное число, разделенное дефисом. Это можно изменить, если вы хотите, при условии, что изменения по-прежнему глобально уникальны.
После создания проекта выберите логотип Android, чтобы добавить Firebase в приложение Android:
На странице "Добавить Firebase в ваше приложение для Android" введите имя для вашего пакета, при необходимости псевдоним приложения, и нажмите кнопку Регистрация приложения:
На странице "Добавить Firebase" на страницу приложения Android нажмите кнопку "Скачать google-services.json" и сохраните файл в локальную папку перед нажатием кнопки "Далее":
На странице Добавление Firebase в приложение Android нажмите кнопку Далее.
На странице "Добавить Firebase в ваше Android-приложение" выберите кнопку "Перейти в консоль".
В консоли Firebase щелкните значок "Обзор проекта", а затем выберите параметры проекта:
В параметрах проекта выберите вкладку Cloud Messaging. Вы увидите, что API Cloud Messaging Firebase (V1) включен:
В параметрах проекта перейдите на вкладку "Учетные записи службы" и нажмите кнопку "Создать новый закрытый ключ".
В диалоговом окне "Создание нового закрытого ключа" нажмите кнопку "Создать ключ":
Файл JSON будет скачан, который будет содержать значения, которые вы введете в Центр уведомлений Azure.
Регистрация приложения iOS для работы с push-уведомлениями
Чтобы отправить push-уведомления в приложение iOS, необходимо зарегистрировать приложение в Apple и зарегистрировать push-уведомления. Это можно сделать, выполнив действия, описанные в следующей документации по Центру уведомлений Azure:
- Создание файла запроса на подпись сертификата
- Регистрация приложения для получения push-уведомлений
- Создание сертификата для центра уведомлений
Если вы хотите получать push-уведомления на физическом устройстве, вам также потребуется создать профиль подготовки.
Внимание
Чтобы получать фоновые уведомления в iOS, необходимо добавить в приложение режим фона удаленных уведомлений. Дополнительные сведения см. в разделе "Включение возможности удаленных уведомлений" на developer.apple.com.
Создание Центра уведомлений Azure
Чтобы создать центр уведомлений в портал Azure, выполните следующие действия.
- В веб-браузере войдите в портал Azure.
- В портал Azure нажмите кнопку "Создать ресурс", а затем найдите и выберите Центр уведомлений перед нажатием кнопки "Создать".
-
На странице Центра уведомлений выполните следующие действия.
В поле "Подписка" выберите имя подписки Azure, которую вы хотите использовать, а затем выберите существующую группу ресурсов или создайте новую.
В поле "Сведения о пространстве имен" введите уникальное имя для нового пространства имен.
В поле "Сведения о центре уведомлений" введите имя концентратора уведомлений. Это необходимо, так как пространство имен содержит один или несколько центров уведомлений.
В раскрывающемся списке "Расположение" выберите значение, указывающее расположение, в котором нужно создать концентратор уведомлений.
Просмотрите параметр Зоны доступности. Если вы выбрали регион, в котором есть зоны доступности, флажок установлен по умолчанию.
Примечание.
Зоны доступности — это платная функция, поэтому дополнительная плата добавляется на ваш уровень.
Выберите вариант аварийного восстановления: нет, парный регион восстановления или гибкий регион восстановления. Если вы выбираете сопряжённый регион восстановления, отображается регион аварийного переключения. Если выбрать гибкий регион восстановления, используйте раскрывающийся список регионов восстановления.
Выберите кнопку Создать. Центр уведомлений будет создан.
- В портале Azure перейдите к созданному концентратору уведомлений, а затем к панели
"Управление политиками доступа". - В разделе Политики доступа запишите строку подключения для политики
DefaultFullSharedAccessSignature
. Это потребуется позже при создании серверной службы, которая взаимодействует с центром уведомлений.
Дополнительные сведения о том, как создать концентратор уведомлений, см. в статье Создание концентратора уведомлений Azure в портале Azure.
Настройка Firebase Cloud Messaging в центре уведомлений
Чтобы настроить центр уведомлений для взаимодействия с Firebase Cloud Messaging:
В портале Azure перейдите к центру уведомлений и выберите панель "Параметры > Google" (FCM версии 1).
В колонке Google (FCM версии 1) введите значения для полей "Закрытый ключ", "Электронная почта клиента" и "Идентификатор проекта". Эти значения можно найти в JSON-файле закрытого ключа, скачанном из Firebase Cloud Messaging:
Поле Azure Ключ JSON Пример значения JSON Приватный ключ private_key
Это значение должно начинаться с -----BEGIN PRIVATE KEY-----\n
и заканчиваться-----END PRIVATE KEY-----\n
.Электронная почта клиента client_email
firebase-adminsdk-55sfg@pushdemo-d6ab2.iam.gserviceaccount.com
Код проекта project_id
pushdemo-d6ab2
В колонке Google (FCM версии 1) нажмите кнопку Сохранить.
Настройка службы push-уведомлений Apple в центре уведомлений
В портале Azure перейдите к вашему центру уведомлений и выберите вкладку Настройки > Apple (APNS). Затем выполните соответствующие действия на основе выбранного ранее подхода при создании сертификата для концентратора уведомлений.
Внимание
При настройке режима приложения выберите только рабочую среду, если вы хотите отправлять push-уведомления пользователям, которые приобрели свое приложение из магазина.
Вариант 1. Использование push-сертификата P12
- В колонке Apple (APNS) выберите режим проверки подлинности сертификата .
- В колонке Apple (APNS) выберите значок файла рядом с полем "Отправить сертификат ". Затем выберите P12-файл, экспортируемый ранее, и отправьте его.
- В панели Apple (APNS) введите пароль сертификата в поле "Пароль", если требуется.
- На панели Apple (APNS) выберите режим песочницы приложения.
- На панели Apple (APNS) нажмите кнопку «Сохранить».
Вариант 2. Использование аутентификации на основе токенов
- В панели Apple (APNS) выберите аутентификацию по токену.
- В разделе Apple (APNS) введите значения, которые вы ранее получили для полей Идентификатор ключа, Идентификатор пакета, Идентификатор команды, и Маркер.
- В колонке Apple (APNS) выберите режим приложения песочницы .
- В колонке Apple (APNS) нажмите кнопку «Сохранить».
Создание серверного приложения ASP.NET Core Web API
В этом разделе вы создадите серверную часть веб-API ASP.NET Core для обработки установки устройства и отправки уведомлений в приложение .NET MAUI.
Создание проекта веб-API
Чтобы создать проект веб-API, выполните следующие действия.
В Visual Studio создайте проект веб-API ASP.NET Core:
В диалоговом окне "Настройка нового проекта" назовите проект PushNotificationsAPI.
В диалоговом окне "Дополнительные сведения" установите флажки "Настройка для HTTPS" и "Использование контроллеров":
После создания проекта нажмите клавишу F5 , чтобы запустить проект.
В данный момент приложение настроено на использование
WeatherForecastController
в качествеlaunchUrl
, которое задано в файле Properties\launchSettings.json. Приложение запустится в веб-браузере и отобразит некоторые данные JSON.Внимание
При запуске проекта ASP.NET Core, использующего ПРОТОКОЛ HTTPS, Visual Studio обнаружит, установлен ли сертификат разработки ASP.NET Core HTTPS в локальном хранилище сертификатов пользователей и предложит установить его и доверять ему, если он отсутствует.
Закройте веб-браузер.
В Обозреватель решений разверните папку "Контроллеры" и удалите WeatherForecastController.cs.
В Обозреватель решений в корне проекта удалите WeatherForecast.cs.
Откройте командное окно и перейдите в каталог, содержащий файл проекта. Затем выполните следующие команды:
dotnet user-secrets init dotnet user-secrets set "NotificationHub:Name" <value> dotnet user-secrets set "NotificationHub:ConnectionString" "<value>"
Замените значения заполнителей собственным именем Центра уведомлений Azure и собственными значениями строки подключения. Их можно найти в следующих расположениях в Центре уведомлений Azure:
Параметр конфигурации Расположение NotificationHub:Name
См. Имя в сводке 'Основы' в верхней части страницы 'Обзор'. NotificationHub:ConnectionString
См. раздел DefaultFullSharedAccessSignature* на странице "Политики доступа". Это настраивает локальные значения конфигурации с помощью средства Secret Manager. Это разделяет секреты Центра уведомлений Azure от решения Visual Studio, чтобы гарантировать, что они не попадут в систему управления версиями.
Совет
Для рабочих сценариев рассмотрите использование такой службы, как Azure KeyVault, чтобы безопасно хранить строки подключения.
Проверка подлинности клиентов с помощью ключа API
Чтобы выполнить проверку подлинности клиентов с помощью ключа API, выполните следующие действия.
Откройте командное окно и перейдите в каталог, содержащий файл проекта. Затем выполните следующие команды:
dotnet user-secrets set "Authentication:ApiKey" <value>
Замените значение заполнителя ключом API, который может быть любым значением.
В Visual Studio добавьте новую папку с именем Authentication в проект, а затем добавьте новый класс с именем
ApiKeyAuthOptions
в папку проверки подлинности и замените его код следующим кодом: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; } }
В Visual Studio добавьте новый класс с именем
ApiKeyAuthHandler
в папку проверки подлинности и замените его код следующим кодом: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)); } }
Обработчик проверки подлинности — это тип, реализующий поведение схемы, которая в данном случае является пользовательской схемой ключей API.
В Visual Studio добавьте новый класс с именем
AuthenticationBuilderExtensions
в папку проверки подлинности и замените его код следующим кодом: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); } }
Этот метод расширения будет использоваться для упрощения кода конфигурации ПО промежуточного слоя в Program.cs.
В Visual Studio откройте Program.cs и обновите код, чтобы настроить проверку подлинности ключа API под вызовом
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);
В Program.cs обновите код под
// Configure the HTTP request pipeline
комментарием, чтобы вызвать методы расширенияUseRouting
,UseAuthentication
иMapControllers
.// Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
Метод
UseAuthentication
расширения регистрирует ПО промежуточного слоя, которое использует ранее зарегистрированную схему проверки подлинности.UseAuthentication
необходимо вызывать перед любым ПО промежуточного слоя, которое зависит от проверки подлинности пользователей.Примечание.
Хотя ключ API не является таким безопасным, как токен, он подходит для этого руководства и его можно легко настроить с помощью промежуточного слоя ASP.NET.
Добавление и настройка служб
Чтобы добавить и настроить службы в серверном приложении веб-API, выполните следующие действия.
В Visual Studio добавьте в проект пакет NuGet Microsoft.Azure.NotificationHubs . Этот пакет NuGet используется для доступа к центру уведомлений, инкапсулированному в службе.
В Visual Studio добавьте в проект новую папку с именем Models, а затем добавьте новый класс с именем в
PushTemplates
и замените его код следующим кодом: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)\" }"; } }
Класс
PushTemplates
содержит разобранные на токены полезные данные уведомления для универсальных и тихих push-уведомлений. Эти пакеты определяются вне установки, чтобы позволить экспериментирование без необходимости обновлять существующие установки посредством службы. Обработка изменений в установках таким образом выходит за рамки этой статьи. В сценариях продукта рекомендуется использовать пользовательские шаблоны.В Visual Studio добавьте новый класс с именем
DeviceInstallation
в папку Models и замените его код следующим кодом: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>(); }
В Visual Studio добавьте новый класс с именем
NotificationRequest
в папку Models и замените его код следующим кодом: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; } }
В Visual Studio добавьте новый класс с именем
NotificationHubOptions
в папку Models и замените его код следующим кодом:using System.ComponentModel.DataAnnotations; namespace PushNotificationsAPI.Models; public class NotificationHubOptions { [Required] public string Name { get; set; } [Required] public string ConnectionString { get; set; } }
В Visual Studio добавьте в проект новую папку с именем Services , а затем добавьте новый интерфейс с именем
INotificationService
в папку "Службы" и замените его код следующим кодом: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); }
В Visual Studio добавьте новый класс с именем
NotificationHubService
в папку Services и замените его код следующим кодом: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); } }
Выражение тега, предоставленное методу
SendTemplateNotificationsAsync
, ограничено 20 тегами, если они содержат только OR. В противном случае они ограничены 6 тегами. Дополнительные сведения см. в статье Маршрутизация и выражения тегов.В Visual Studio откройте Program.cs и обновите код, чтобы добавить
NotificationHubService
в качестве однотонной реализацииINotificationService
ниже вызоваbuilder.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();
Создание REST API уведомлений
Чтобы создать REST API уведомлений, выполните следующие действия.
В Visual Studio добавьте новый контроллер с именем в папку
NotificationsController
".Совет
Выберите шаблон контроллера API с действиями чтения и записи.
В файле NotificationsController.cs добавьте следующие
using
инструкции в верхней части файла:using System.ComponentModel.DataAnnotations; using System.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PushNotificationsAPI.Models; using PushNotificationsAPI.Services;
В файле NotificationsController.cs добавьте
Authorize
атрибут вNotificationsController
класс:[Authorize] [ApiController] [Route("api/[controller]")] public class NotificationsController : ControllerBase
В файле NotificationsController.cs обновите конструктор
NotificationsContoller
, чтобы он принимал зарегистрированный экземплярINotificationService
в качестве аргумента и назначал его полю, доступному только для чтения.readonly INotificationService _notificationService; public NotificationsController(INotificationService notificationService) { _notificationService = notificationService; }
В файле NotificationsContoller.cs замените все методы следующим кодом:
[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(); }
В файле Properties/launchSettings.json измените
launchUrl
свойство для каждого профиля сweatherforecast
наapi/notifications
.
Создание приложения API
Теперь вы создадите приложение API в службе приложений Azure для размещения вашей серверной службы. Это можно сделать непосредственно из Visual Studio или Visual Studio Code с помощью Azure CLI, Azure PowerShell, Azure Developer CLI и портала Azure. Дополнительные сведения см. в статье "Публикация веб-приложения".
Чтобы создать приложение API в портал Azure, выполните следующие действия.
В веб-браузере войдите в портал Azure.
В портал Azure нажмите кнопку "Создать ресурс", а затем найдите и выберите приложение API перед нажатием кнопки "Создать".
На странице "Создание приложения API" обновите следующие поля перед нажатием кнопки "Создать".
Поле Действие Подписка Выберите ту же целевую подписку, в которую вы создали концентратор уведомлений. Группа ресурсов Выберите ту же группу ресурсов, в которую вы создали концентратор уведомлений. Имя. Введите глобально уникальное имя. Стек среды выполнения Убедитесь, что выбрана последняя версия .NET. Как только приложение API будет подготовлено, перейдите к ресурсу.
На странице обзора запишите значение домена по умолчанию. Этот URL-адрес — это серверная конечная точка, которая будет использована приложением .NET MAUI. URL-адрес будет использовать указанное вами имя приложения API с форматом
https://<app_name>.azurewebsites.net
.В портале Azure перейдите к панели Переменные среды, и затем убедитесь, что выбрана вкладка Параметры приложения. Затем нажмите кнопку "Добавить", чтобы добавить следующие параметры:
Имя. Значение Authentication:ApiKey <api_key_value> NotificationHub:Name <hub_name_value> NotificationHub:ConnectionString <hub_connection_string_value> Внимание
Параметр
Authentication:ApiKey
приложения добавлен для простоты. Для рабочих сценариев рассмотрите службу Azure KeyVault, чтобы безопасно хранить строку подключения.После ввода всех этих параметров нажмите кнопку "Применить " и нажмите кнопку "Подтвердить ".
Опубликовать бэкэнд-сервис
Чтобы опубликовать серверную службу на Azure App Service:
- В Visual Studio щелкните проект правой кнопкой мыши и выберите " Опубликовать".
- В мастере публикации выберите Azure и нажмите кнопку "Далее".
- В мастере Публикация выберите службу приложений Azure (Windows), а затем нажмите кнопку Далее.
- В мастере публикации следуйте потоку проверки подлинности, чтобы подключить Visual Studio к подписке Azure и опубликовать это приложение.
Visual Studio создает, упаковывает и публикует приложение в Azure, а затем запускает приложение в браузере по умолчанию. Дополнительные сведения см. в статье "Публикация веб-приложения ASP.NET".
Совет
Вы можете скачать профиль публикации для приложения из колонки "Обзор" приложения API в портал Azure, а затем использовать профиль в Visual Studio для публикации приложения.
Проверка опубликованного API
Чтобы проверить правильность публикации приложения API, следует использовать средства REST для отправки POST
запроса на следующий адрес:
https://<app_name>.azurewebsites.net/api/notifications/requests
Примечание.
Базовый адрес : https://<app_name>.azurewebsites.net
.
Убедитесь, что заголовки запроса настроены на включение ключа apikey
и его значения, установите тело запроса в формате raw и используйте следующее содержимое JSON-заполнителя:
{}
Вы должны получить 400 Bad Request
ответ от службы.
Примечание.
Пока не удается протестировать API с использованием допустимых данных запроса, так как для этого потребуются сведения, относящиеся к платформе, из приложения .NET MAUI.
Дополнительные сведения о вызове REST API см. в статье "Использование HTTP-файлов в Visual Studio и тестирование веб-API с помощью Http Repl". В Visual Studio Code можно использовать REST Client для тестирования REST API.
Создание приложения .NET MAUI
В этом разделе вы создадите приложение пользовательского интерфейса многоплатформенных приложений .NET (.NET MAUI), которое позволяет вам зарегистрироваться для получения push-уведомлений из концентратора уведомлений через серверную службу и сняться с регистрации.
Чтобы создать приложение .NET MAUI, выполните приведенные далее действия.
В Visual Studio создайте новое приложение .NET MAUI с именем PushNotificationsDemo с помощью шаблона проекта приложения .NET MAUI.
В Visual Studio добавьте новую папку с именем Models в проект .NET MAUI, а затем добавьте новый класс с именем в
DeviceInstallation
и замените его код следующим кодом: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>(); }
В Visual Studio добавьте перечисление с именем
PushDemoAction
в папку Models и замените его код следующим кодом:namespace PushNotificationsDemo.Models; public enum PushDemoAction { ActionA, ActionB }
В Visual Studio добавьте новую папку с именем Services в проект .NET MAUI, а затем добавьте новый интерфейс с именем
IDeviceInstallationService
в папку Services и замените его код следующим кодом:using PushNotificationsDemo.Models; namespace PushNotificationsDemo.Services; public interface IDeviceInstallationService { string Token { get; set; } bool NotificationsSupported { get; } string GetDeviceId(); DeviceInstallation GetDeviceInstallation(params string[] tags); }
Этот интерфейс будет реализован на каждой платформе позже, чтобы предоставить
DeviceInstallation
сведения, необходимые серверной службе.В Visual Studio добавьте интерфейс с именем
INotificationRegistrationService
в папку "Службы" и замените его код следующим кодом:namespace PushNotificationsDemo.Services; public interface INotificationRegistrationService { Task DeregisterDeviceAsync(); Task RegisterDeviceAsync(params string[] tags); Task RefreshRegistrationAsync(); }
Этот интерфейс будет обрабатывать взаимодействие между клиентом и серверной службой.
В Visual Studio добавьте интерфейс с именем
INotificationActionService
в папку "Службы" и замените его код следующим кодом:namespace PushNotificationsDemo.Services; public interface INotificationActionService { void TriggerAction(string action); }
Этот интерфейс будет использоваться в качестве простого механизма для централизованной обработки действий уведомлений.
В Visual Studio добавьте интерфейс с именем
IPushDemoNotificationActionService
в папку "Службы" и замените его код следующим кодом:using PushNotificationsDemo.Models; namespace PushNotificationsDemo.Services; public interface IPushDemoNotificationActionService : INotificationActionService { event EventHandler<PushDemoAction> ActionTriggered; }
Тип
IPushDemoNotificationActionService
предназначен для этого приложения и используетPushDemoAction
перечисление для определения действия, активируемого с помощью строго типизированного подхода.В Visual Studio добавьте класс с именем
NotificationRegistrationService
в папку Services и замените его код следующим кодом: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(); } }
В Visual Studio добавьте класс с именем
PushDemoNotificationActionService
в папку Services и замените его код следующим кодом: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); } }
В Visual Studio добавьте класс с именем
Config
в корень проекта и замените его содержимое на следующий код:namespace PushNotificationsDemo; public static partial class Config { public static string ApiKey = "API_KEY"; public static string BackendServiceEndpoint = "BACKEND_SERVICE_ENDPOINT"; }
Класс
Config
используется как простой способ сохранить секреты вне системы контроля версий. Эти значения можно заменять в ходе автоматизированной сборки или переопределять, используя локальный разделяемый класс.Внимание
При указании базового адреса в приложении .NET MAUI убедитесь, что он заканчивается
/
.В Visual Studio добавьте класс с именем
Config.local_secrets
в корневой каталог проекта. Затем замените код в файле Config.local_secrets.cs следующим кодом:namespace PushNotificationsDemo; public static partial class Config { static Config() { ApiKey = "<your_api_key>"; BackendServiceEndpoint = "<your_api_app_url>"; } }
Замените значения заполнителей значениями, выбранными при создании серверной службы.
BackendServiceEndpoint
URL-адрес должен использовать форматhttps://<api_app_name>.azurewebsites.net/
.Совет
Не забудьте добавить
*.local_secrets.*
в файл.gitignore
, чтобы избежать закоммитивания этого файла в систему контроля версий.
Создание пользовательского интерфейса
Чтобы создать пользовательский интерфейс приложения, выполните следующие действия.
В Visual Studio откройте MainPage.xaml и замените его дочерние
VerticalStackLayout
элементы следующим кодом XAML:<VerticalStackLayout Margin="20" Spacing="6"> <Button x:Name="registerButton" Text="Register" Clicked="OnRegisterButtonClicked" /> <Button x:Name="deregisterButton" Text="Deregister" Clicked="OnDeregisterButtonClicked" /> </VerticalStackLayout>
В Visual Studio откройте MainPage.xaml.cs и добавьте директиву
using
для пространства именPushNotificationsDemo.Services
:using PushNotificationsDemo.Services;
В MainPage.xaml.cs добавьте резервное
readonly
поле для хранения ссылки наINotificationRegistrationService
реализацию:readonly INotificationRegistrationService _notificationRegistrationService;
В конструкторе
MainPage
устранитеINotificationRegistrationService
реализацию и назначьте ее резервному_notificationRegistrationService
полю:public MainPage(INotificationRegistrationService service) { InitializeComponent(); _notificationRegistrationService = service; }
Реализуйте в классе
MainPage
обработчики событийOnRegisterButtonClicked
иOnDeregisterButtonClicked
, вызывая соответствующие методы регистрации и отмены регистрации на объектеINotificationRegistrationService
.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; }); }); }
Внимание
В приложении регистрация и отмена регистрации выполняются в ответ на входные данные пользователей, чтобы упростить изучение и тестирование этой функции. В рабочем приложении обычно выполняются действия регистрации и отмены регистрации во время соответствующего момента жизненного цикла приложения, не требуя явного ввода пользователем.
В Visual Studio откройте App.xaml.cs и добавьте следующие
using
инструкции:using PushNotificationsDemo.Models; using PushNotificationsDemo.Services;
В App.xaml.cs добавьте резервное
readonly
поле для хранения ссылки наIPushDemoNotificationActionService
реализацию:readonly IPushDemoNotificationActionService _actionService;
В конструкторе
App
получите реализациюIPushDemoNotificationActionService
и назначьте ее резервному полю_actionService
, после чего подпишитесь на событиеIPushDemoNotificationActionService.ActionTriggered
.public App(IPushDemoNotificationActionService service) { InitializeComponent(); _actionService = service; _actionService.ActionTriggered += NotificationActionTriggered; MainPage = new AppShell(); }
В конструкторе
App
устранитеIPushDemoNotificationActionService
реализацию и назначьте ее_actionService
резервномуIPushDemoNotificationActionService.ActionTriggered
полю и подпишитесь на событие:public App(IPushDemoNotificationActionService service) { InitializeComponent(); _actionService = service; _actionService.ActionTriggered += NotificationActionTriggered; }
App
В классе реализуйте обработчик событий дляIPushDemoNotificationActionService.ActionTriggered
события: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; }); }); }
Обработчик событий для
ActionTriggered
события демонстрирует получение и распространение действий push-уведомлений. Обычно они обрабатываются в фоновом режиме, например, чтобы перейти к определенному виду или обновить какие-то данные, а не показывать оповещение.
Настройка приложения Android
Чтобы настроить приложение .NET MAUI в Android для получения и обработки push-уведомлений, выполните приведенные ниже действия.
В Visual Studio добавьте пакет NuGet Xamarin.Firebase.Messaging в проект приложения .NET MAUI.
В Visual Studio добавьте файл google-services.json в папку Platform/Android проекта приложения .NET MAUI. После добавления файла в проект он должен иметь действие сборки:
GoogleServicesJson
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-android'"> <GoogleServicesJson Include="Platforms\Android\google-services.json" /> </ItemGroup>
Совет
Не забудьте добавить
google-services.json
в файл.gitignore
, чтобы избежать отправки этого файла в систему управления версиями.В Visual Studio измените файл проекта (*.csproj) и установите
SupportedOSPlatformVersion
для Android значение 26.0:<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">26.0</SupportedOSPlatformVersion>
Google внесли изменения в каналы уведомлений Android в API 26. Дополнительные сведения см. в разделе "Каналы уведомлений" на developer.android.com.
В папке "Платформы/ Android " проекта добавьте новый класс с именем
DeviceInstallationService
и замените его код следующим кодом: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."; } }
Этот класс предоставляет уникальный идентификатор, используя
Secure.AndroidId
значение и полезные данные регистрации концентратора уведомлений.В папке "Платформы/ Android " проекта добавьте новый класс с именем
PushNotificationFirebaseMessagingService
и замените его код следующим кодом: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); } }
Этот класс имеет
IntentFilter
атрибут, включающийcom.google.firebase.MESSAGING_EVENT
фильтр. Этот фильтр позволяет Android передавать входящие сообщения этому классу для обработки.Сведения о формате сообщений Firebase Cloud Messaging см. в разделе "Сведения о сообщениях FCM" на developer.android.com.
В Visual Studio откройте файл MainActivity.cs в папке Platform/Android и добавьте следующие
using
инструкции:using Android.App; using Android.Content; using Android.Content.PM; using Android.OS; using PushNotificationsDemo.Services; using Firebase.Messaging;
MainActivity
В классе установитеLaunchMode
вSingleTop
, чтобыMainActivity
не создавалось снова при открытии:[Activity( Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
В классе
MainActivity
добавьте вспомогательные поля для хранения ссылок на реализацииIPushDemoNotificationActionService
иIDeviceInstallationService
.IPushDemoNotificationActionService _notificationActionService; IDeviceInstallationService _deviceInstallationService;
MainActivity
В классе добавьтеNotificationActionService
иDeviceInstallationService
частные свойства, которые извлекают конкретные реализации из контейнера внедрения зависимостей приложения:IPushDemoNotificationActionService NotificationActionService => _notificationActionService ?? (_notificationActionService = IPlatformApplication.Current.Services.GetService<IPushDemoNotificationActionService>()); IDeviceInstallationService DeviceInstallationService => _deviceInstallationService ?? (_deviceInstallationService = IPlatformApplication.Current.Services.GetService<IDeviceInstallationService>());
В классе
MainActivity
реализуйте интерфейсAndroid.Gms.Tasks.IOnSuccessListener
для получения и хранения токена Firebase:public class MainActivity : MauiAppCompatActivity, Android.Gms.Tasks.IOnSuccessListener { public void OnSuccess(Java.Lang.Object result) { DeviceInstallationService.Token = result.ToString(); } }
В классе
MainActivity
добавьте методProcessNotificationActions
, который проверит, имеет ли данноеIntent
дополнительное значение с именемaction
, а затем при определенных условиях вызоветaction
, используя реализациюIPushDemoNotificationActionService
.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); } }
В классе
MainActivity
переопределите методOnNewIntent
, чтобы вызвать методProcessNotificationActions
:protected override void OnNewIntent(Intent? intent) { base.OnNewIntent(intent); ProcessNotificationsAction(intent); }
Поскольку для
Activity
установлено значениеSingleTop
,Intent
будет отправлено существующему экземпляруActivity
через переопределениеOnNewIntent
, а не методOnCreate
. Таким образом, необходимо обработать входящее намерение в обоих случаяхOnNewIntent
иOnCreate
.MainActivity
В классе переопределите методOnCreate
, чтобы вызвать методProcessNotificationActions
и получить токен из Firebase, добавивMainActivity
какIOnSuccessListener
:protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); if (DeviceInstallationService.NotificationsSupported) FirebaseMessaging.Instance.GetToken().AddOnSuccessListener(this); ProcessNotificationsAction(Intent); }
Примечание.
Приложение необходимо повторно зарегистрировать при каждом запуске и остановить его от сеанса отладки, чтобы продолжить получение push-уведомлений.
В Visual Studio добавьте разрешение на
POST_NOTIFICATIONS
в папке Platform/Android:<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Дополнительные сведения об этом разрешении см. в разделе "Разрешение среды выполнения уведомлений" на developer.android.com.
В Visual Studio откройте MainPage.xaml.cs и добавьте следующий код в
MainPage
класс:#if ANDROID protected override async void OnAppearing() { base.OnAppearing(); PermissionStatus status = await Permissions.RequestAsync<Permissions.PostNotifications>(); } #endif
Этот код выполняется на Android, когда появляется
MainPage
, и запрашивает у пользователя разрешениеPOST_NOTIFICATIONS
. Дополнительные сведения о разрешениях .NET MAUI см. в разделе "Разрешения".
Настройка приложения iOS
Симулятор iOS поддерживает удаленные уведомления в iOS 16+ при запуске в macOS 13+ на компьютерах Mac с процессорами Apple silicon или T2. Каждый симулятор создает маркеры регистрации, уникальные для сочетания этого симулятора и оборудования Mac, на котором он работает.
Внимание
Симулятор поддерживает среду песочницы службы push-уведомлений Apple.
В следующих инструкциях предполагается, что вы используете оборудование, которое поддерживает получение удаленных уведомлений в симуляторе iOS. Если это не так, вам потребуется запустить приложение iOS на физическом устройстве, которое потребует создания профиля подготовки для приложения, включающего возможность push-уведомлений. Затем необходимо убедиться, что приложение построено с использованием вашего сертификата и профиля распространения. Дополнительные сведения о том, как это сделать, см. в статье Настройка приложения iOS для работы с Центрами уведомлений Azure, а затем следуйте приведенным ниже инструкциям.
Чтобы настроить приложение .NET MAUI в iOS для получения и обработки push-уведомлений:
В Visual Studio измените файл проекта (*.csproj) и задайте
SupportedOSPlatformVersion
для iOS значение 13.0:<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">13.0</SupportedOSPlatformVersion>
Apple внесла изменения в службу push-уведомлений в iOS 13. Дополнительные сведения см. в статье об обновлениях Центров уведомлений Azure для iOS 13.
В Visual Studio добавьте файл Entitlements.plist в папку Platform/iOS проекта и добавьте следующий XML-файл в файл:
<?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>
Это задает право на использование среды APS и указывает использовать среду разработки службы push-уведомлений Apple. В производственных приложениях значение этого права должно быть задано
production
. Дополнительные сведения об этом праве доступа см. в разделе APS Environment Entitlement на developer.apple.com.Дополнительные сведения о добавлении файла прав см. в разделе "Права iOS".
В Visual Studio добавьте новый класс с именем
DeviceInstallationService
в папку Platform/iOS проекта и добавьте следующий код в файл: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"; } }
Этот класс предоставляет уникальный идентификатор, используя значение
UIDevice.IdentifierForVendor
, и данные полезной нагрузки регистрации концентратора уведомлений.В Visual Studio добавьте новый класс с именем
NSDataExtensions
в папку Platform/iOS проекта и добавьте следующий код в файл: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(); } }
Метод расширения
ToHexString
будет использован кодом, который будет добавлен для анализа полученного токена устройства.В Visual Studio откройте файл AppDelegate.cs в папке Platform/iOS и добавьте следующие
using
инструкции:using System.Diagnostics; using Foundation; using PushNotificationsDemo.Platforms.iOS; using PushNotificationsDemo.Services; using UIKit; using UserNotifications;
В классе
AppDelegate
добавьте служебные поля для хранения ссылок на реализацииIPushDemoNotificationActionService
,INotificationRegistrationService
иIDeviceInstallationService
.IPushDemoNotificationActionService _notificationActionService; INotificationRegistrationService _notificationRegistrationService; IDeviceInstallationService _deviceInstallationService;
В классе
AppDelegate
добавьте частные свойстваNotificationActionService
,NotificationRegistrationService
иDeviceInstallationService
, которые извлекают свои конкретные реализации из контейнера внедрения зависимостей приложения: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>());
В классе
AppDelegate
добавьте методCompleteRegistrationAsync
, чтобы задать значение свойстваIDeviceInstallationService.Token
.Task CompleteRegistrationAsync(NSData deviceToken) { DeviceInstallationService.Token = deviceToken.ToHexString(); return NotificationRegistrationService.RefreshRegistrationAsync(); }
Этот метод также обновляет регистрацию и кэширует маркер устройства, если он был обновлен с момента последнего хранения.
В классе
AppDelegate
добавьте методProcessNotificationActions
для обработки данных уведомленияNSDictionary
и вызоваNotificationActionService.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); } }
В классе
AppDelegate
добавьте методRegisteredForRemoteNotifications
, передав аргументdeviceToken
в методCompleteRegistrationAsync
.[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")] public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken) { CompleteRegistrationAsync(deviceToken) .ContinueWith((task) => { if (task.IsFaulted) throw task.Exception; }); }
Этот метод будет вызываться при регистрации приложения для получения удаленного уведомления и используется для запроса уникального маркера устройства, который фактически является адресом приложения на устройстве.
В классе
AppDelegate
добавьте методReceivedRemoteNotification
, передав аргументuserInfo
в методProcessNotificationActions
.[Export("application:didReceiveRemoteNotification:")] public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo) { ProcessNotificationActions(userInfo); }
Этот метод будет вызываться, когда приложение получило удаленное уведомление и используется для обработки уведомления.
В классе
AppDelegate
добавьте методFailedToRegisterForRemoteNotifications
для логирования любых ошибок.[Export("application:didFailToRegisterForRemoteNotificationsWithError:")] public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error) { Debug.WriteLine(error.Description); }
Этот метод будет вызван, когда приложению не удалось зарегистрироваться для получения удаленных уведомлений. Регистрация может завершиться ошибкой, если устройство не подключено к сети, если сервер APNS недоступен или если приложение настроено неправильно.
Примечание.
В рабочих сценариях необходимо реализовать надлежащее ведение журнала и обработку ошибок в методе
FailedToRegisterForRemoteNotifications
.AppDelegate
В классе добавьтеFinishedLaunching
метод для условного запроса разрешения на использование уведомлений и регистрации для удаленных уведомлений:[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); }
Сведения о запросе разрешения на использование уведомлений см. в статье "Запрос разрешения на использование уведомлений на developer.apple.com".
Сведения о уведомлениях в iOS см. в разделе "Уведомления пользователей" на developer.apple.com.
Зарегистрируйте типы в контейнере внедрения зависимостей приложения
В Visual Studio откройте MauiProgram.cs и добавьте инструкцию
using
PushNotificationsDemo.Services
для пространства имен:using PushNotificationsDemo.Services;
В классе
MauiProgram
добавьте код для метода расширенияRegisterServices
, который регистрируетDeviceInstallationService
на каждой платформе, а также кроссплатформенные службыPushDemoNotificationActionService
иNotificationRegistrationService
, и возвращает объектMauiAppBuilder
: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; }
Добавьте код в класс
MauiProgram
для метода расширенияRegisterViews
, который регистрирует типMainPage
как singleton и возвращает объектMauiAppBuilder
:public static MauiAppBuilder RegisterViews(this MauiAppBuilder builder) { builder.Services.AddSingleton<MainPage>(); return builder; }
Тип
MainPage
регистрируется, так как он требуетINotificationRegistrationService
зависимости, и все типы, требующие зависимости, должны быть зарегистрированы в контейнере внедрения зависимостей.В классе
MauiProgram
измените методCreateMauiApp
таким образом, чтобы он вызывал методы расширенияRegisterServices
иRegisterViews
.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(); }
Дополнительные сведения о внедрении зависимостей в .NET MAUI см. в разделе "Внедрение зависимостей".
Тестирование приложения
Вы можете протестировать приложение, отправив push-уведомления в приложение с помощью серверной службы или через портал Azure.
Симулятор iOS поддерживает удаленные уведомления в iOS 16+ при запуске в macOS 13+ на компьютерах Mac с процессорами Apple silicon или T2. Если вы не соответствуете этим требованиям к оборудованию, вам придется протестировать приложение iOS на физическом устройстве. В Android вы можете протестировать приложение на разблокируемом физическом устройстве разработчика или эмуляторе.
Android и iOS отображают push-уведомления от имени приложения при запуске в фоновом режиме. Если приложение выполняется на переднем плане при получении уведомления, код приложения определяет поведение. Например, можно обновить интерфейс приложения, чтобы отразить новые сведения, содержащиеся в уведомлении.
Тестирование с помощью серверной службы
Чтобы отправить тестовое push-уведомление приложению через серверную службу, опубликованную в службе приложение Azure:
В Visual Studio запустите приложение PushNotificationsDemo в Android или iOS и нажмите кнопку "Зарегистрировать ".
Примечание.
Если вы тестируете на Android, убедитесь, что вы не используете конфигурацию отладки. Кроме того, если приложение было развернуто ранее, убедитесь, что оно было принудительно закрыто, а затем снова запустите его с средства запуска.
В выбранном средстве REST отправьте
POST
запрос на следующий адрес:https://<app_name>.azurewebsites.net/api/notifications/requests
Убедитесь, что заголовки запроса настроены для включения ключа
apikey
и его значения, задайте текст необработанным и используйте следующее содержимое JSON:{ "text": "Message from REST tooling!", "action": "action_a" }
Общий запрос должен быть похож на следующий пример:
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" }
В выбранном средстве REST убедитесь, что вы получите ответ 200 OK .
В приложении на Android или iOS появится оповещение, показывающее действие ActionA, полученное.
Дополнительные сведения о вызове REST API см. в статье "Использование HTTP-файлов в Visual Studio и тестирование веб-API с помощью Http Repl". В Visual Studio Code можно использовать REST-клиент для тестирования REST API.
Тестирование с помощью портал Azure
Центры уведомлений Azure позволяют проверить, может ли приложение получать push-уведомления.
Чтобы отправить тестовое push-уведомление в приложение с помощью портал Azure:
В Visual Studio запустите приложение PushNotificationsDemo в Android или iOS и нажмите кнопку "Зарегистрировать ".
Примечание.
Если вы тестируете на Android, убедитесь, что вы не используете конфигурацию отладки. Кроме того, если приложение было развернуто ранее, убедитесь, что оно принудительно закрыто, а затем запустите его снова из лаунчера.
В портале Azure перейдите к вашему центру уведомлений и нажмите кнопку "Тестовая отправка" на вкладке "Обзор".
На панели Тестовая отправка выберите необходимую платформу и измените payload.
Для Apple используйте следующую полезную нагрузку:
{ "aps": { "alert": "Message from Notification Hub!" }, "action": "action_a" }
Для Android используйте следующую полезную нагрузку:
{ "message": { "notification": { "title": "PushDemo", "body": "Message from Notification Hub!" }, "data": { "action": "action_a" } } }
В портал Azure должно быть указано, что уведомление успешно отправлено.
Сведения о формате сообщений Firebase Cloud Messaging см. в разделе "Сведения о сообщениях FCM" на developer.android.com.
В приложении на Android или iOS появится оповещение, показывающее действие ActionA, полученное.
Устранение неполадок
В следующих разделах рассматриваются распространенные проблемы, возникающие при попытке использования push-уведомлений в клиентском приложении.
Нет ответа от серверной службы
При локальном тестировании убедитесь, что внутренняя служба запущена и использует правильный порт.
При тестировании приложения API Azure убедитесь, что служба запущена, развернута и началась без ошибок.
Убедитесь, что базовый адрес указан правильно в средстве REST или в конфигурации приложения .NET MAUI. Базовый адрес должен быть https://<api_name>.azurewebsites.net
или https://localhost:7020
при локальном тестировании.
Получение кода состояния 401 от внутренней службы
Убедитесь, что заголовок apikey
запроса задан правильно, и что это значение соответствует заданному для серверной службы.
Если при локальном тестировании эта ошибка возникает, убедитесь, что значение ключа, определенное в приложении .NET MAUI, соответствует Authentication:ApiKey
значению секретов пользователя, используемому серверной службой.
Если вы тестируете приложение API Azure, убедитесь, что ключевое значение, определенное в приложении .NET MAUI, соответствует Authentication:ApiKey
значению параметра приложения, указанному в портале Azure. Если вы создали или изменили этот параметр приложения после развертывания серверной службы, необходимо перезапустить службу, чтобы значение вступило в силу.
Получение кода состояния 404 от внутренней службы
Убедитесь, что конечная точка и метод HTTP-запроса верны:
- PUT -
https://<api_name>.azurewebsites.net/api/notifications/installations
- УДАЛИТЬ-
https://<api_name>.azurewebsites.net/api/notifications/installations/<installation_id>
- ПОСТ -
https://<api_name>.azurewebsites.net/api/notifications/requests
Или при локальном тестировании:
- КЛАСТЬ-
https://localhost:7020/api/notifications/installations
- УДАЛИТЬ -
https://localhost:7020/api/notifications/installations/<installation_id>
- ОТПРАВИТЬ
https://localhost:7020/api/notifications/requests
Внимание
При указании базового адреса в приложении .NET MAUI убедитесь, что он заканчивается /
. Базовый адрес должен быть https://<api_name>.azurewebsites.net
или https://localhost:7020/
при локальном тестировании.
Отсутствие уведомлений на Android после запуска или остановки сеанса отладки
Убедитесь, что каждый раз при запуске сеанса отладки вы регистрируетесь. Отладчик приведет к созданию нового токена Firebase, поэтому необходимо обновить установку концентратора уведомлений.
Не удается зарегистрироваться и отображается сообщение об ошибке центра уведомлений
Убедитесь, что устройство подключено к сети. Затем определите код состояния ответа HTTP, задав точку останова для проверки StatusCode
свойства в объекте HttpResponse
.
Ознакомьтесь с предыдущими предложениями по устранению неполадок в зависимости от кода состояния.
Задайте точку останова в строках, возвращающих определенные коды состояния для соответствующего API. Затем попробуйте вызвать серверную службу при локальной отладке.
Проверьте, что серверная служба работает должным образом, используя выбранные инструменты REST, и используйте полезные данные, созданные приложением .NET MAUI для выбранной платформы.
Проверьте разделы конфигурации, относящиеся к этой платформе, и убедитесь, что ни один шаг не упущен. Проверьте, вычисляются ли подходящие значения для переменной InstallationId
и переменной Token
на выбранной вами платформе.
Не удается определить идентификатор устройства, ошибка сообщения для устройства
Проверьте разделы конфигурации, относящиеся к этой платформе, и убедитесь, что ни один шаг не упущен.