Поделиться через


Руководство. Отправка 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, выполните приведенные действия.

  1. В веб-браузере войдите в консоль Firebase.

  2. В консоли Firebase нажмите кнопку "Добавить проект " и создайте новый проект Firebase, введя PushDemo в качестве имени проекта.

    Примечание.

    Для вас будет автоматически сгенерировано уникальное название. По умолчанию это представляет собой название в нижнем регистре, которое вы указали, плюс сгенерированное число, разделенное дефисом. Это можно изменить, если вы хотите, при условии, что изменения по-прежнему глобально уникальны.

  3. После создания проекта выберите логотип Android, чтобы добавить Firebase в приложение Android:

    Снимок экрана: добавление Firebase в приложение Android в консоли Firebase Cloud Messaging.

  4. На странице "Добавить Firebase в ваше приложение для Android" введите имя для вашего пакета, при необходимости псевдоним приложения, и нажмите кнопку Регистрация приложения:

    Снимок экрана: регистрация приложения Android в Firebase.

  5. На странице "Добавить Firebase" на страницу приложения Android нажмите кнопку "Скачать google-services.json" и сохраните файл в локальную папку перед нажатием кнопки "Далее":

    Снимок экрана: скачивание JSON-файла служб Google.

  6. На странице Добавление Firebase в приложение Android нажмите кнопку Далее.

  7. На странице "Добавить Firebase в ваше Android-приложение" выберите кнопку "Перейти в консоль".

  8. В консоли Firebase щелкните значок "Обзор проекта", а затем выберите параметры проекта:

    Снимок экрана: выбор параметров проекта в консоли Firebase Cloud Messaging.

  9. В параметрах проекта выберите вкладку Cloud Messaging. Вы увидите, что API Cloud Messaging Firebase (V1) включен:

    Снимок экрана: подтверждение включения Firebase Cloud Messaging версии 1.

  10. В параметрах проекта перейдите на вкладку "Учетные записи службы" и нажмите кнопку "Создать новый закрытый ключ".

  11. В диалоговом окне "Создание нового закрытого ключа" нажмите кнопку "Создать ключ":

    Снимок экрана: создание нового закрытого ключа в консоли Firebase Cloud Messaging.

    Файл JSON будет скачан, который будет содержать значения, которые вы введете в Центр уведомлений Azure.

Регистрация приложения iOS для работы с push-уведомлениями

Чтобы отправить push-уведомления в приложение iOS, необходимо зарегистрировать приложение в Apple и зарегистрировать push-уведомления. Это можно сделать, выполнив действия, описанные в следующей документации по Центру уведомлений Azure:

Если вы хотите получать push-уведомления на физическом устройстве, вам также потребуется создать профиль подготовки.

Внимание

Чтобы получать фоновые уведомления в iOS, необходимо добавить в приложение режим фона удаленных уведомлений. Дополнительные сведения см. в разделе "Включение возможности удаленных уведомлений" на developer.apple.com.

Создание Центра уведомлений Azure

Чтобы создать центр уведомлений в портал Azure, выполните следующие действия.

  1. В веб-браузере войдите в портал Azure.
  2. В портал Azure нажмите кнопку "Создать ресурс", а затем найдите и выберите Центр уведомлений перед нажатием кнопки "Создать".
  3. На странице Центра уведомлений выполните следующие действия.
    1. В поле "Подписка" выберите имя подписки Azure, которую вы хотите использовать, а затем выберите существующую группу ресурсов или создайте новую.

    2. В поле "Сведения о пространстве имен" введите уникальное имя для нового пространства имен.

    3. В поле "Сведения о центре уведомлений" введите имя концентратора уведомлений. Это необходимо, так как пространство имен содержит один или несколько центров уведомлений.

    4. В раскрывающемся списке "Расположение" выберите значение, указывающее расположение, в котором нужно создать концентратор уведомлений.

    5. Просмотрите параметр Зоны доступности. Если вы выбрали регион, в котором есть зоны доступности, флажок установлен по умолчанию.

      Примечание.

      Зоны доступности — это платная функция, поэтому дополнительная плата добавляется на ваш уровень.

    6. Выберите вариант аварийного восстановления: нет, парный регион восстановления или гибкий регион восстановления. Если вы выбираете сопряжённый регион восстановления, отображается регион аварийного переключения. Если выбрать гибкий регион восстановления, используйте раскрывающийся список регионов восстановления.

    7. Выберите кнопку Создать. Центр уведомлений будет создан.

  4. В портале Azure перейдите к созданному концентратору уведомлений, а затем к панели "Управление политиками доступа".
  5. В разделе Политики доступа запишите строку подключения для политики DefaultFullSharedAccessSignature. Это потребуется позже при создании серверной службы, которая взаимодействует с центром уведомлений.

Дополнительные сведения о том, как создать концентратор уведомлений, см. в статье Создание концентратора уведомлений Azure в портале Azure.

Настройка Firebase Cloud Messaging в центре уведомлений

Чтобы настроить центр уведомлений для взаимодействия с Firebase Cloud Messaging:

  1. В портале Azure перейдите к центру уведомлений и выберите панель "Параметры > Google" (FCM версии 1).

  2. В колонке 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
  3. В колонке Google (FCM версии 1) нажмите кнопку Сохранить.

Настройка службы push-уведомлений Apple в центре уведомлений

В портале Azure перейдите к вашему центру уведомлений и выберите вкладку Настройки > Apple (APNS). Затем выполните соответствующие действия на основе выбранного ранее подхода при создании сертификата для концентратора уведомлений.

Внимание

При настройке режима приложения выберите только рабочую среду, если вы хотите отправлять push-уведомления пользователям, которые приобрели свое приложение из магазина.

Вариант 1. Использование push-сертификата P12

  1. В колонке Apple (APNS) выберите режим проверки подлинности сертификата .
  2. В колонке Apple (APNS) выберите значок файла рядом с полем "Отправить сертификат ". Затем выберите P12-файл, экспортируемый ранее, и отправьте его.
  3. В панели Apple (APNS) введите пароль сертификата в поле "Пароль", если требуется.
  4. На панели Apple (APNS) выберите режим песочницы приложения.
  5. На панели Apple (APNS) нажмите кнопку «Сохранить».

Вариант 2. Использование аутентификации на основе токенов

  1. В панели Apple (APNS) выберите аутентификацию по токену.
  2. В разделе Apple (APNS) введите значения, которые вы ранее получили для полей Идентификатор ключа, Идентификатор пакета, Идентификатор команды, и Маркер.
  3. В колонке Apple (APNS) выберите режим приложения песочницы .
  4. В колонке Apple (APNS) нажмите кнопку «Сохранить».

Создание серверного приложения ASP.NET Core Web API

В этом разделе вы создадите серверную часть веб-API ASP.NET Core для обработки установки устройства и отправки уведомлений в приложение .NET MAUI.

Создание проекта веб-API

Чтобы создать проект веб-API, выполните следующие действия.

  1. В Visual Studio создайте проект веб-API ASP.NET Core:

    Снимок экрана: создание нового проекта веб-API ASP.NET Core в Visual Studio.

  2. В диалоговом окне "Настройка нового проекта" назовите проект PushNotificationsAPI.

  3. В диалоговом окне "Дополнительные сведения" установите флажки "Настройка для HTTPS" и "Использование контроллеров":

    Снимок экрана: настройка проекта веб-API ASP.NET Core в Visual Studio.

  4. После создания проекта нажмите клавишу F5 , чтобы запустить проект.

    В данный момент приложение настроено на использование WeatherForecastController в качестве launchUrl, которое задано в файле Properties\launchSettings.json. Приложение запустится в веб-браузере и отобразит некоторые данные JSON.

    Внимание

    При запуске проекта ASP.NET Core, использующего ПРОТОКОЛ HTTPS, Visual Studio обнаружит, установлен ли сертификат разработки ASP.NET Core HTTPS в локальном хранилище сертификатов пользователей и предложит установить его и доверять ему, если он отсутствует.

  5. Закройте веб-браузер.

  6. В Обозреватель решений разверните папку "Контроллеры" и удалите WeatherForecastController.cs.

  7. В Обозреватель решений в корне проекта удалите WeatherForecast.cs.

  8. Откройте командное окно и перейдите в каталог, содержащий файл проекта. Затем выполните следующие команды:

    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, выполните следующие действия.

  1. Откройте командное окно и перейдите в каталог, содержащий файл проекта. Затем выполните следующие команды:

    dotnet user-secrets set "Authentication:ApiKey" <value>
    

    Замените значение заполнителя ключом API, который может быть любым значением.

  2. В 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; }
    }
    
  3. В 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.

  4. В 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.

  5. В 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);
    
  6. В 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, выполните следующие действия.

  1. В Visual Studio добавьте в проект пакет NuGet Microsoft.Azure.NotificationHubs . Этот пакет NuGet используется для доступа к центру уведомлений, инкапсулированному в службе.

  2. В 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-уведомлений. Эти пакеты определяются вне установки, чтобы позволить экспериментирование без необходимости обновлять существующие установки посредством службы. Обработка изменений в установках таким образом выходит за рамки этой статьи. В сценариях продукта рекомендуется использовать пользовательские шаблоны.

  3. В 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>();
    }
    
  4. В 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; }
    }
    
  5. В 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; }
    }
    
  6. В 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);
    }
    
  7. В 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 тегами. Дополнительные сведения см. в статье Маршрутизация и выражения тегов.

  8. В 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 уведомлений, выполните следующие действия.

  1. В Visual Studio добавьте новый контроллер с именем в папку NotificationsController".

    Совет

    Выберите шаблон контроллера API с действиями чтения и записи.

  2. В файле NotificationsController.cs добавьте следующие using инструкции в верхней части файла:

    using System.ComponentModel.DataAnnotations;
    using System.Net;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using PushNotificationsAPI.Models;
    using PushNotificationsAPI.Services;
    
  3. В файле NotificationsController.cs добавьте Authorize атрибут в NotificationsController класс:

    [Authorize]
    [ApiController]
    [Route("api/[controller]")]
    public class NotificationsController : ControllerBase
    
  4. В файле NotificationsController.cs обновите конструктор NotificationsContoller, чтобы он принимал зарегистрированный экземпляр INotificationService в качестве аргумента и назначал его полю, доступному только для чтения.

    readonly INotificationService _notificationService;
    
    public NotificationsController(INotificationService notificationService)
    {
        _notificationService = notificationService;
    }
    
  5. В файле 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();
    }
    
  6. В файле 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, выполните следующие действия.

  1. В веб-браузере войдите в портал Azure.

  2. В портал Azure нажмите кнопку "Создать ресурс", а затем найдите и выберите приложение API перед нажатием кнопки "Создать".

  3. На странице "Создание приложения API" обновите следующие поля перед нажатием кнопки "Создать".

    Поле Действие
    Подписка Выберите ту же целевую подписку, в которую вы создали концентратор уведомлений.
    Группа ресурсов Выберите ту же группу ресурсов, в которую вы создали концентратор уведомлений.
    Имя. Введите глобально уникальное имя.
    Стек среды выполнения Убедитесь, что выбрана последняя версия .NET.
  4. Как только приложение API будет подготовлено, перейдите к ресурсу.

  5. На странице обзора запишите значение домена по умолчанию. Этот URL-адрес — это серверная конечная точка, которая будет использована приложением .NET MAUI. URL-адрес будет использовать указанное вами имя приложения API с форматом https://<app_name>.azurewebsites.net.

  6. В портале Azure перейдите к панели Переменные среды, и затем убедитесь, что выбрана вкладка Параметры приложения. Затем нажмите кнопку "Добавить", чтобы добавить следующие параметры:

    Имя. Значение
    Authentication:ApiKey <api_key_value>
    NotificationHub:Name <hub_name_value>
    NotificationHub:ConnectionString <hub_connection_string_value>

    Внимание

    Параметр Authentication:ApiKey приложения добавлен для простоты. Для рабочих сценариев рассмотрите службу Azure KeyVault, чтобы безопасно хранить строку подключения.

    После ввода всех этих параметров нажмите кнопку "Применить " и нажмите кнопку "Подтвердить ".

Опубликовать бэкэнд-сервис

Чтобы опубликовать серверную службу на Azure App Service:

  1. В Visual Studio щелкните проект правой кнопкой мыши и выберите " Опубликовать".
  2. В мастере публикации выберите Azure и нажмите кнопку "Далее".
  3. В мастере Публикация выберите службу приложений Azure (Windows), а затем нажмите кнопку Далее.
  4. В мастере публикации следуйте потоку проверки подлинности, чтобы подключить 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, выполните приведенные далее действия.

  1. В Visual Studio создайте новое приложение .NET MAUI с именем PushNotificationsDemo с помощью шаблона проекта приложения .NET MAUI.

  2. В 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>();
    }
    
  3. В Visual Studio добавьте перечисление с именем PushDemoAction в папку Models и замените его код следующим кодом:

    namespace PushNotificationsDemo.Models;
    
    public enum PushDemoAction
    {
        ActionA,
        ActionB
    }
    
  4. В 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 сведения, необходимые серверной службе.

  5. В Visual Studio добавьте интерфейс с именем INotificationRegistrationService в папку "Службы" и замените его код следующим кодом:

    namespace PushNotificationsDemo.Services;
    
    public interface INotificationRegistrationService
    {
        Task DeregisterDeviceAsync();
        Task RegisterDeviceAsync(params string[] tags);
        Task RefreshRegistrationAsync();
    }
    

    Этот интерфейс будет обрабатывать взаимодействие между клиентом и серверной службой.

  6. В Visual Studio добавьте интерфейс с именем INotificationActionService в папку "Службы" и замените его код следующим кодом:

    namespace PushNotificationsDemo.Services;
    
    public interface INotificationActionService
    {
        void TriggerAction(string action);
    }
    

    Этот интерфейс будет использоваться в качестве простого механизма для централизованной обработки действий уведомлений.

  7. В Visual Studio добавьте интерфейс с именем IPushDemoNotificationActionService в папку "Службы" и замените его код следующим кодом:

    using PushNotificationsDemo.Models;
    
    namespace PushNotificationsDemo.Services;
    
    public interface IPushDemoNotificationActionService : INotificationActionService
    {
        event EventHandler<PushDemoAction> ActionTriggered;
    }
    

    Тип IPushDemoNotificationActionService предназначен для этого приложения и использует PushDemoAction перечисление для определения действия, активируемого с помощью строго типизированного подхода.

  8. В 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();
        }
    }
    
  9. В 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);
        }
    }
    
  10. В 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 убедитесь, что он заканчивается /.

  11. В 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, чтобы избежать закоммитивания этого файла в систему контроля версий.

Создание пользовательского интерфейса

Чтобы создать пользовательский интерфейс приложения, выполните следующие действия.

  1. В 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>
    
  2. В Visual Studio откройте MainPage.xaml.cs и добавьте директиву using для пространства имен PushNotificationsDemo.Services:

    using PushNotificationsDemo.Services;
    
  3. В MainPage.xaml.cs добавьте резервное readonly поле для хранения ссылки на INotificationRegistrationService реализацию:

    readonly INotificationRegistrationService _notificationRegistrationService;
    
  4. В конструкторе MainPage устраните INotificationRegistrationService реализацию и назначьте ее резервному _notificationRegistrationService полю:

    public MainPage(INotificationRegistrationService service)
    {
        InitializeComponent();
    
        _notificationRegistrationService = service;
    }
    
  5. Реализуйте в классе 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;
                });
        });
    }
    

    Внимание

    В приложении регистрация и отмена регистрации выполняются в ответ на входные данные пользователей, чтобы упростить изучение и тестирование этой функции. В рабочем приложении обычно выполняются действия регистрации и отмены регистрации во время соответствующего момента жизненного цикла приложения, не требуя явного ввода пользователем.

  6. В Visual Studio откройте App.xaml.cs и добавьте следующие using инструкции:

    using PushNotificationsDemo.Models;
    using PushNotificationsDemo.Services;
    
  7. В App.xaml.cs добавьте резервное readonly поле для хранения ссылки на IPushDemoNotificationActionService реализацию:

    readonly IPushDemoNotificationActionService _actionService;
    
  1. В конструкторе App получите реализацию IPushDemoNotificationActionService и назначьте ее резервному полю _actionService, после чего подпишитесь на событие IPushDemoNotificationActionService.ActionTriggered.

    public App(IPushDemoNotificationActionService service)
    {
        InitializeComponent();
    
        _actionService = service;
        _actionService.ActionTriggered += NotificationActionTriggered;
    
        MainPage = new AppShell();
    }
    
  1. В конструкторе App устраните IPushDemoNotificationActionService реализацию и назначьте ее _actionService резервному IPushDemoNotificationActionService.ActionTriggered полю и подпишитесь на событие:

    public App(IPushDemoNotificationActionService service)
    {
        InitializeComponent();
    
        _actionService = service;
        _actionService.ActionTriggered += NotificationActionTriggered;
    }
    
  1. 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-уведомлений, выполните приведенные ниже действия.

  1. В Visual Studio добавьте пакет NuGet Xamarin.Firebase.Messaging в проект приложения .NET MAUI.

  2. В 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, чтобы избежать отправки этого файла в систему управления версиями.

  3. В Visual Studio измените файл проекта (*.csproj) и установите SupportedOSPlatformVersion для Android значение 26.0:

    <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">26.0</SupportedOSPlatformVersion>
    

    Google внесли изменения в каналы уведомлений Android в API 26. Дополнительные сведения см. в разделе "Каналы уведомлений" на developer.android.com.

  4. В папке "Платформы/ 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 значение и полезные данные регистрации концентратора уведомлений.

  5. В папке "Платформы/ 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.

  6. В 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;
    
  7. 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)]
    
  8. В классе MainActivity добавьте вспомогательные поля для хранения ссылок на реализации IPushDemoNotificationActionService и IDeviceInstallationService.

    IPushDemoNotificationActionService _notificationActionService;
    IDeviceInstallationService _deviceInstallationService;
    
  9. MainActivity В классе добавьте NotificationActionService и DeviceInstallationService частные свойства, которые извлекают конкретные реализации из контейнера внедрения зависимостей приложения:

    IPushDemoNotificationActionService NotificationActionService =>
        _notificationActionService ?? (_notificationActionService = IPlatformApplication.Current.Services.GetService<IPushDemoNotificationActionService>());
    
    IDeviceInstallationService DeviceInstallationService =>
        _deviceInstallationService ?? (_deviceInstallationService = IPlatformApplication.Current.Services.GetService<IDeviceInstallationService>());
    
  10. В классе 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();
        }
    }
    
  11. В классе 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);
        }
    }
    
  12. В классе MainActivity переопределите метод OnNewIntent, чтобы вызвать метод ProcessNotificationActions:

    protected override void OnNewIntent(Intent? intent)
    {
        base.OnNewIntent(intent);
        ProcessNotificationsAction(intent);
    }
    

    Поскольку для Activity установлено значение SingleTop, Intent будет отправлено существующему экземпляру Activity через переопределение OnNewIntent, а не метод OnCreate. Таким образом, необходимо обработать входящее намерение в обоих случаях OnNewIntent и OnCreate.

  13. 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-уведомлений.

  14. В Visual Studio добавьте разрешение на POST_NOTIFICATIONS в папке Platform/Android:

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    

    Дополнительные сведения об этом разрешении см. в разделе "Разрешение среды выполнения уведомлений" на developer.android.com.

  15. В 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-уведомлений:

  1. В Visual Studio измените файл проекта (*.csproj) и задайте SupportedOSPlatformVersion для iOS значение 13.0:

    <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">13.0</SupportedOSPlatformVersion>
    

    Apple внесла изменения в службу push-уведомлений в iOS 13. Дополнительные сведения см. в статье об обновлениях Центров уведомлений Azure для iOS 13.

  2. В 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".

  3. В 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, и данные полезной нагрузки регистрации концентратора уведомлений.

  4. В 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 будет использован кодом, который будет добавлен для анализа полученного токена устройства.

  5. В Visual Studio откройте файл AppDelegate.cs в папке Platform/iOS и добавьте следующие using инструкции:

    using System.Diagnostics;
    using Foundation;
    using PushNotificationsDemo.Platforms.iOS;
    using PushNotificationsDemo.Services;
    using UIKit;
    using UserNotifications;
    
  6. В классе AppDelegate добавьте служебные поля для хранения ссылок на реализации IPushDemoNotificationActionService, INotificationRegistrationService и IDeviceInstallationService.

    IPushDemoNotificationActionService _notificationActionService;
    INotificationRegistrationService _notificationRegistrationService;
    IDeviceInstallationService _deviceInstallationService;
    
  7. В классе 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>());
    
  8. В классе AppDelegate добавьте метод CompleteRegistrationAsync, чтобы задать значение свойства IDeviceInstallationService.Token.

    Task CompleteRegistrationAsync(NSData deviceToken)
    {
        DeviceInstallationService.Token = deviceToken.ToHexString();
        return NotificationRegistrationService.RefreshRegistrationAsync();
    }
    

    Этот метод также обновляет регистрацию и кэширует маркер устройства, если он был обновлен с момента последнего хранения.

  9. В классе 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);
        }
    }
    
  10. В классе AppDelegate добавьте метод RegisteredForRemoteNotifications, передав аргумент deviceToken в метод CompleteRegistrationAsync.

    [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
    public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
    {
        CompleteRegistrationAsync(deviceToken)
            .ContinueWith((task) =>
            {
                if (task.IsFaulted)
                    throw task.Exception;
            });
    }
    

    Этот метод будет вызываться при регистрации приложения для получения удаленного уведомления и используется для запроса уникального маркера устройства, который фактически является адресом приложения на устройстве.

  11. В классе AppDelegate добавьте метод ReceivedRemoteNotification, передав аргумент userInfo в метод ProcessNotificationActions.

    [Export("application:didReceiveRemoteNotification:")]
    public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
    {
        ProcessNotificationActions(userInfo);
    }
    

    Этот метод будет вызываться, когда приложение получило удаленное уведомление и используется для обработки уведомления.

  12. В классе AppDelegate добавьте метод FailedToRegisterForRemoteNotifications для логирования любых ошибок.

    [Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
    public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
    {
        Debug.WriteLine(error.Description);
    }
    

    Этот метод будет вызван, когда приложению не удалось зарегистрироваться для получения удаленных уведомлений. Регистрация может завершиться ошибкой, если устройство не подключено к сети, если сервер APNS недоступен или если приложение настроено неправильно.

    Примечание.

    В рабочих сценариях необходимо реализовать надлежащее ведение журнала и обработку ошибок в методе FailedToRegisterForRemoteNotifications .

  13. 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.

Зарегистрируйте типы в контейнере внедрения зависимостей приложения

  1. В Visual Studio откройте MauiProgram.cs и добавьте инструкцию usingPushNotificationsDemo.Services для пространства имен:

    using PushNotificationsDemo.Services;
    
  2. В классе 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;
    }
    
  3. Добавьте код в класс MauiProgram для метода расширения RegisterViews, который регистрирует тип MainPage как singleton и возвращает объект MauiAppBuilder:

    public static MauiAppBuilder RegisterViews(this MauiAppBuilder builder)
    {
        builder.Services.AddSingleton<MainPage>();
        return builder;
    }
    

    Тип MainPage регистрируется, так как он требует INotificationRegistrationService зависимости, и все типы, требующие зависимости, должны быть зарегистрированы в контейнере внедрения зависимостей.

  4. В классе 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:

  1. В Visual Studio запустите приложение PushNotificationsDemo в Android или iOS и нажмите кнопку "Зарегистрировать ".

    Примечание.

    Если вы тестируете на Android, убедитесь, что вы не используете конфигурацию отладки. Кроме того, если приложение было развернуто ранее, убедитесь, что оно было принудительно закрыто, а затем снова запустите его с средства запуска.

  2. В выбранном средстве 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"
    }
    
  3. В выбранном средстве REST убедитесь, что вы получите ответ 200 OK .

  4. В приложении на Android или iOS появится оповещение, показывающее действие ActionA, полученное.

Дополнительные сведения о вызове REST API см. в статье "Использование HTTP-файлов в Visual Studio и тестирование веб-API с помощью Http Repl". В Visual Studio Code можно использовать REST-клиент для тестирования REST API.

Тестирование с помощью портал Azure

Центры уведомлений Azure позволяют проверить, может ли приложение получать push-уведомления.

Чтобы отправить тестовое push-уведомление в приложение с помощью портал Azure:

  1. В Visual Studio запустите приложение PushNotificationsDemo в Android или iOS и нажмите кнопку "Зарегистрировать ".

    Примечание.

    Если вы тестируете на Android, убедитесь, что вы не используете конфигурацию отладки. Кроме того, если приложение было развернуто ранее, убедитесь, что оно принудительно закрыто, а затем запустите его снова из лаунчера.

  2. В портале Azure перейдите к вашему центру уведомлений и нажмите кнопку "Тестовая отправка" на вкладке "Обзор".

  3. На панели Тестовая отправка выберите необходимую платформу и измените 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.

  4. В приложении на 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 на выбранной вами платформе.

Не удается определить идентификатор устройства, ошибка сообщения для устройства

Проверьте разделы конфигурации, относящиеся к этой платформе, и убедитесь, что ни один шаг не упущен.