Руководство. Отправка 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:
На странице приложения 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 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:ConnectinString
См. раздел 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
в папку Models и замените его код следующим кодом: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:
- В 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
и его значения, задайте текст необработанным и используйте следующее содержимое JSON заполнителя:
{}
Вы должны получить 400 Bad Request
ответ от службы.
Примечание.
Пока не удается протестировать API с использованием допустимых данных запроса, так как для этого потребуются сведения, относящиеся к платформе, из приложения .NET MAUI.
Дополнительные сведения о вызове REST API см. в статье "Использование HTTP-файлов в Visual Studio и тестирование веб-API с помощью Http Repl". В Visual Studio Code клиент REST можно использовать для тестирования ИНТЕРФЕЙСов REST API.
Создание приложения .NET MAUI
В этом разделе описано, как создать приложение пользовательского интерфейса многоплатформенных приложений .NET (.NET MAUI), которое позволяет зарегистрировать push-уведомления из концентратора уведомлений через серверную службу и отмените регистрацию.
Чтобы создать приложение .NET MAUI, выполните приведенные далее действия.
В Visual Studio создайте новое приложение .NET MAUI с именем PushNotificationsDemo с помощью шаблона проекта приложения .NET MAUI.
В Visual Studio добавьте новую папку с именем Models в проект .NET MAUI, а затем добавьте новый класс с именем
DeviceInstallation
в папку Models и замените его код следующим кодом: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); }
LaunchMode
Так как для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 добавьте разрешение на AndroidManifest.xml-файл в папке Platform/Android:
POST_NOTIFICATIONS
<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" на 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
тип в качестве одноэлементного и возвращает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 перейдите к центру уведомлений и нажмите кнопку "Отправить тест" в колонке "Обзор".
В колонке "Отправка тестов" выберите необходимую платформу и измените полезные данные.
Для 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-запроса верны:
- КЛАСТЬ-
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
переменные.
Не удается разрешить идентификатор устройства, сообщение об ошибке устройства
Проверьте разделы конфигурации, относящиеся к этой платформе, и убедитесь, что ни один шаг не упущен.