다음을 통해 공유


자습서: 백 엔드 서비스를 통해 Azure Notification Hubs를 사용하여 .NET MAUI 앱에 푸시 알림 보내기

샘플을 찾아봅니다. 샘플 찾아보기

푸시 알림은 백 엔드 시스템에서 클라이언트 앱으로 정보를 전달합니다. Apple, Google 및 기타 플랫폼에는 각각 자체 푸시 알림 서비스(PNS)가 있습니다. Azure Notification Hubs를 사용하면 백 엔드 앱이 각 PNS에 알림 배포를 처리하는 단일 허브와 통신할 수 있도록 플랫폼 간에 알림을 중앙 집중화할 수 있습니다.

Azure Notification Hubs를 사용하려면 앱이 허브에 등록하고 필요에 따라 템플릿을 정의하고 태그를 구독해야 합니다.

  • 디바이스 설치를 수행하면 PNS 핸들이 Azure Notification Hub의 식별자에 연결됩니다. 등록에 대한 자세한 내용은 등록 관리를 참조하세요.
  • 템플릿을 사용하여 디바이스에서 매개 변수화된 메시지 템플릿을 지정할 수 있습니다. 들어오는 메시지는 디바이스별로 사용자 지정할 수 있습니다. 자세한 내용은 Notification Hubs 템플릿을 참조 하세요.
  • 태그를 사용하여 뉴스, 스포츠, 날씨 등의 메시지 범주를 구독할 수 있습니다. 자세한 내용은 라우팅 및 태그 식을 참조 하세요.

이 자습서에서는 Azure Notification Hubs를 사용하여 Android 및 iOS를 대상으로 하는 .NET 다중 플랫폼 앱 UI(.NET MAUI) 앱에 푸시 알림을 보냅니다. ASP.NET Core Web API 백 엔드는 클라이언트에 대한 디바이스 등록을 처리하고 푸시 알림을 시작하는 데 사용됩니다. 이러한 작업은 Microsoft.Azure.NotificationHubs NuGet 패키지를 사용하여 처리됩니다. 전반적인 접근 방식에 대한 자세한 내용은 백 엔드에서 등록 관리를 참조 하세요.

이 자습서에서는 다음을 수행합니다.

  • 푸시 알림 서비스 및 Azure Notification Hub를 설정합니다.
  • ASP.NET Core WebAPI 백 엔드 앱을 만듭니다.
  • .NET MAUI 앱을 만듭니다.
  • 푸시 알림에 대한 Android 앱을 구성합니다.
  • 푸시 알림에 대한 iOS 앱을 구성합니다.
  • 앱을 테스트합니다.
  • 설정 및 구성 문제를 해결합니다.

필수 조건

이 자습서를 완료하려면 다음이 필요합니다.

  • 활성 구독이 있는 Azure 계정
  • .NET 다중 플랫폼 앱 UI 개발 워크로드 및 ASP.NET 및 웹 개발 워크로드가 설치된 최신 버전의 Visual Studio/Visual Studio Code를 실행하는 PC 또는 Mac입니다.

Android의 경우 다음이 필요합니다.

  • 개발자가 Google Play 서비스가 설치된 API 26 이상을 실행하는 물리적 디바이스 또는 에뮬레이터의 잠금을 해제했습니다.

iOS의 경우 다음이 있어야 합니다.

  • 활성 Apple 개발자 계정입니다.
  • 키 집합에 설치된 유효한 개발자 인증서와 함께 Xcode를 실행하는 Mac

그런 다음 iOS에서 다음 중 하나가 있어야 합니다.

  • Apple 실리콘 또는 T2 프로세서가 있는 Mac 컴퓨터의 macOS 13 이상에서 실행되는 iOS 16+ 시뮬레이터입니다.

    또는

  • 개발자 계정에 등록된 물리적 iOS 디바이스입니다(iOS 13.0 이상 실행).

  • Apple 개발자 계정에 등록되고 인증서와 연결된 물리적 디바이스입니다.

Important

iOS 시뮬레이터는 Apple 실리콘 또는 T2 프로세서가 있는 Mac 컴퓨터의 macOS 13 이상에서 실행되는 경우 iOS 16 이상에서 원격 알림을 지원합니다. 이러한 하드웨어 요구 사항을 충족하지 않는 경우 활성 Apple 개발자 계정 및 물리적 디바이스가 필요합니다.

이 자습서를 수행하려면 다음에 대해 잘 알고 있어야 합니다.

이 자습서는 Visual Studio를 대상으로 하지만 PC 또는 Mac에서 Visual Studio Code를 사용하여 이를 따를 수 있습니다. 그러나 조정이 필요한 몇 가지 차이점이 있을 것입니다. 예를 들어 사용자 인터페이스 및 워크플로, 템플릿 이름 및 환경 구성에 대한 설명입니다.

푸시 알림 서비스 및 Azure Notification Hub 설정

이 섹션에서는 Firebase Cloud Messaging 및 APNS(Apple Push Notification Services)를 설정합니다. 그런 다음, 이러한 서비스를 사용하도록 Azure Notification Hub를 만들고 구성합니다.

Firebase 프로젝트 만들기

Firebase 프로젝트를 만들려면 다음을 수행합니다.

  1. 웹 브라우저에서 Firebase 콘솔에 로그인합니다.

  2. Firebase 콘솔에서 프로젝트 추가 단추를 선택하고 새 Firebase 프로젝트를 만들고 프로젝트 이름으로 PushDemo입력합니다.

    참고 항목

    고유한 이름이 생성됩니다. 기본적으로 이 값은 제공한 이름의 소문자 변형과 대시로 구분된 생성된 숫자로 구성됩니다. 편집 내용이 여전히 전역적으로 고유하다면 원하는 경우 이를 변경할 수 있습니다.

  3. 프로젝트를 만든 후 Android 로고를 선택하여 Android 앱에 Firebase를 추가합니다.

    Firebase Cloud Messaging 콘솔에서 Android 앱에 Firebase를 추가하는 스크린샷

  4. Android 앱에 Firebase 추가 페이지에서 패키지 이름, 선택적으로 앱 애칭을 입력하고 앱 등록 단추를 선택합니다.

    Firebase에 Android 앱을 등록하는 스크린샷

  5. Android 앱에 Firebase 추가 페이지에서 다운로드 google-services.json 단추를 선택하고 다음 단추를 선택하기 전에 파일을 로컬 폴더에 저장합니다.

    Google 서비스 JSON 파일을 다운로드하는 스크린샷

  6. Android 앱에 Firebase 추가 페이지에서 다음 단추를 선택합니다.

  7. Android 앱Firebase 추가 페이지에서 콘솔계속 단추를 선택합니다.

  8. Firebase 콘솔에서 프로젝트 개요 아이콘을 선택한 다음 프로젝트 설정을 선택합니다.

    Firebase Cloud Messaging 콘솔에서 프로젝트 설정을 선택하는 스크린샷

  9. 프로젝트 설정에서 클라우드 메시징 탭을 선택합니다. Firebase Cloud Messaging API(V1)가 사용하도록 설정된 것을 볼 수 있습니다.

    Firebase Cloud Messaging V1이 사용하도록 설정되어 있는지 확인하는 스크린샷

  10. 프로젝트 설정에서 서비스 계정 탭을 선택한 다음 새 프라이빗 키 생성 단추를 선택합니다.

  11. 새 프라이빗 키 생성 대화 상자에서 키 생성 단추를 선택합니다.

    Firebase Cloud Messaging 콘솔에서 새 프라이빗 키를 생성하는 스크린샷

    JSON 파일이 다운로드되며 Azure Notification Hub에 입력할 값이 포함됩니다.

푸시 알림에 대한 iOS 앱 등록

iOS 앱에 푸시 알림을 보내려면 Apple에 앱을 등록하고 푸시 알림을 등록해야 합니다. 이 작업은 다음 Azure Notification Hub 설명서의 단계를 수행하여 수행할 수 있습니다.

물리적 디바이스에서 푸시 알림을 받으려면 프로비저닝 프로필도 만들어야 합니다.

Important

iOS에서 백그라운드 알림을 받으려면 앱에 원격 알림 백그라운드 모드를 추가해야 합니다. 자세한 내용은 developer.apple.com 원격 알림 기능 사용을 참조하세요.

Azure Notification Hub 만들기

Azure Portal에서 알림 허브를 만들려면 다음을 수행합니다.

  1. 웹 브라우저에서 Azure Portal에 로그인합니다.
  2. Azure Portal에서 리소스 만들기 단추를 클릭한 다음 만들기 단추를 선택하기 전에 알림 허브를 검색하여 선택합니다.
  3. 알림 허브 페이지에서 다음 단계를 수행합니다.
    1. 구독 필드에서 사용하려는 Azure 구독의 이름을 선택한 다음, 기존 리소스 그룹을 선택하거나 새 리소스 그룹을 만듭니다.

    2. 네임스페이 스 세부 정보 필드에 새 네임스페이스의 고유한 이름을 입력합니다.

    3. 알림 허브 세부 정보 필드에 알림 허브의 이름을 입력합니다. 네임스페이스에 하나 이상의 알림 허브가 포함되어 있기 때문에 이 작업이 필요합니다.

    4. 위치 드롭다운에서 알림 허브를 만들 위치를 지정하는 값을 선택합니다.

    5. 가용성 영역 옵션을 검토합니다. 가용성 영역이 있는 지역을 선택한 경우 기본적으로 확인란이 선택됩니다.

      참고 항목

      가용성 영역은 유료 기능이므로 계층에 추가 요금이 추가됩니다.

    6. 재해 복구 옵션(없음, 쌍을 이루는 복구 지역 또는 유연한 복구 지역)을 선택합니다. 쌍을 이루는 복구 지역을 선택하면 장애 조치(failover) 지역이 표시됩니다. 유연한 복구 지역을 선택한 경우 드롭다운을 사용하여 복구 지역 목록에서 선택합니다.

    7. 생성 단추를 선택합니다. 알림 허브가 만들어집니다.

  4. Azure Portal에서 새로 만든 알림 허브로 이동한 다음 액세스 정책 관리 > 블레이드로 이동합니다.
  5. 액세스 정책 블레이드에서 정책에 대한 DefaultFullSharedAccessSignature 연결 문자열 기록해 둡다. 나중에 알림 허브와 통신하는 백 엔드 서비스를 빌드할 때 필요합니다.

알림 허브를 만드는 방법에 대한 자세한 내용은 Azure Portal에서 Azure 알림 허브 만들기를 참조 하세요.

알림 허브에서 Firebase Cloud Messaging 구성

Firebase Cloud Messaging과 통신하도록 알림 허브를 구성하려면 다음을 수행합니다.

  1. Azure Portal에서 알림 허브로 이동하여 설정 > Google(FCM v1) 블레이드를 선택합니다.

  2. Google(FCM v1) 블레이드에서 프라이빗 키, 클라이언트 전자 메일프로젝트 ID 필드의 값을 입력합니다. 이러한 값은 Firebase Cloud Messaging에서 다운로드한 프라이빗 키 JSON 파일에서 찾을 수 있습니다.

    Azure 필드 JSON 키 JSON 값 예제
    프라이빗 키 private_key 이 값은 .로 -----BEGIN PRIVATE KEY-----\n 시작하고 끝나 -----END PRIVATE KEY-----\n야 합니다.
    클라이언트 전자 메일 client_email firebase-adminsdk-55sfg@pushdemo-d6ab2.iam.gserviceaccount.com
    프로젝트 ID project_id pushdemo-d6ab2
  3. Google(FCM v1) 블레이드에서 저장 단추를 선택합니다.

알림 허브에서 Apple 푸시 알림 서비스 구성

Azure Portal에서 알림 허브로 이동하여 APNS(설정 > Apple) 블레이드를 선택합니다. 그런 다음 알림 허브에 대한 인증서를 만들 때 이전에 선택한 접근 방식에 따라 적절한 단계를 수행합니다.

Important

애플리케이션 모드를 설정할 때 스토어에서 앱을 구매한 사용자에게 푸시 알림을 보내려는 경우에만 프로덕션을 선택합니다.

옵션 1 - .p12 푸시 인증서 사용

  1. Apple(APNS) 블레이드에서 인증서 인증 모드를 선택합니다.
  2. Apple(APNS) 블레이드에서 인증서 업로드 필드 옆에 있는 파일 아이콘을 선택합니다. 그런 다음 이전에 내보낸 .p12 파일을 선택하고 업로드합니다.
  3. 필요한 경우 Apple(APNS) 블레이드에서 암호 필드에 인증서 암호를 입력합니다.
  4. Apple(APNS) 블레이드에서 샌드박스 애플리케이션 모드를 선택합니다.
  5. Apple(APNS) 블레이드에서 저장 단추를 선택합니다.

옵션 2 - 토큰 기반 인증 사용

  1. Apple(APNS) 블레이드에서 토큰 인증 모드를 선택합니다.
  2. Apple(APNS) 블레이드에서 키 ID, 번들 ID, 팀 ID 토큰 필드에 대해 이전에 획득한 값을 입력합니다.
  3. Apple(APNS) 블레이드에서 샌드박스 애플리케이션 모드를 선택합니다.
  4. Apple(APNS) 블레이드에서 저장 단추를 선택합니다.

ASP.NET Core Web API 백 엔드 앱 만들기

이 섹션에서는 디바이스 설치를 처리하고 .NET MAUI 앱에 알림을 보내는 ASP.NET Core Web API 백 엔드를 만듭니다.

웹 API 프로젝트 만들기

웹 API 프로젝트를 만들려면 다음을 수행합니다.

  1. Visual Studio에서 ASP.NET Core Web API 프로젝트를 만듭니다.

    Visual Studio에서 새 ASP.NET Core Web API 프로젝트를 만드는 스크린샷

  2. 새 프로젝트 구성 대화 상자에서 프로젝트 이름을 PushNotificationsAPI로 지정합니다.

  3. 추가 정보 대화 상자에서 HTTPS컨트롤러 사용 구성 확인란이 사용하도록 설정되어 있는지 확인합니다.

    Visual Studio에서 ASP.NET Core Web API 프로젝트를 구성하는 스크린샷

  4. 프로젝트가 만들어지면 F5 키를 눌러 프로젝트를 실행합니다.

    앱은 현재 Properties\launchSettings.json 파일에 설정된 대로 launchUrl사용하도록 WeatherForecastController 구성되어 있습니다. 앱이 웹 브라우저에서 시작되고 일부 JSON 데이터가 표시됩니다.

    Important

    HTTPS를 사용하는 ASP.NET Core 프로젝트를 실행하면 Visual Studio에서 ASP.NET Core HTTPS 개발 인증서가 로컬 사용자 인증서 저장소에 설치되어 있는지 감지하고, 누락된 경우 이를 설치하고 신뢰하도록 제안합니다.

  5. 웹 브라우저를 닫습니다.

  6. 솔루션 탐색기 Controllers 폴더를 확장하고 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 Notification Hub 이름 및 연결 문자열 값으로 바꿉니다. 이러한 위치는 Azure Notification Hub의 다음 위치에서 찾을 수 있습니다.

    구성 값 위치
    NotificationHub:Name 개요 페이지의 맨 위에 있는 Essentials 요약의 이름을 참조하세요.
    NotificationHub:ConnectinString 액세스 정책 페이지에서 DefaultFullSharedAccessSignature*를 참조하세요.

    그러면 Secret Manager 도구를 사용하여 로컬 구성 값이 설정됩니다. 이렇게 하면 Azure Notification Hub 비밀을 Visual Studio 솔루션에서 분리하여 소스 제어가 종료되지 않도록 합니다.

    프로덕션 시나리오의 경우 Azure KeyVault와 같은 서비스를 사용하여 연결 문자열 안전하게 저장합니다.

API 키를 사용하여 클라이언트 인증

API 키를 사용하여 클라이언트를 인증하려면 다음을 수행합니다.

  1. 명령 창을 열고 프로젝트 파일이 포함된 디렉터리로 이동합니다. 그러고 나서 다음 명령을 실행합니다.

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

    자리 표시자 값을 모든 값이 될 수 있는 API 키로 바꿉다.

  2. Visual Studio에서 프로젝트에 Authentication이라는 새 폴더를 추가한 다음 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에서 Authentication 폴더에 명명된 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에서 Authentication 폴더에 명명된 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 코드를 업데이트하여 < UseAuthenticationa0/> 및 MapControllers 확장 메서드를 UseRouting호출합니다.

    // 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에서 프로젝트에 Microsoft.Azure.NotificationHubs NuGet 패키지를 추가합니다. 이 NuGet 패키지는 서비스 내에 캡슐화된 알림 허브에 액세스하는 데 사용됩니다.

  2. Visual Studio에서 프로젝트에 Models라는 새 폴더를 추가한 다음 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 일반 및 자동 푸시 알림에 대한 토큰화된 알림 페이로드가 포함되어 있습니다. 이러한 페이로드는 서비스를 통해 기존 설치를 업데이트하지 않고도 실험을 허용하도록 설치 외부에서 정의됩니다. 이러한 방식으로 설치에 대한 변경 내용을 처리하는 것은 이 문서의 범위를 벗어납니다. 제품 시나리오에서는 사용자 지정 템플릿을 사용하는 것이 좋습니다.

  3. Visual Studio에서 Models 폴더에 명명된 DeviceInstallation 새 클래스를 추가하고 해당 코드를 다음 코드로 바꿉다.

    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에서 Models 폴더에 명명된 NotificationRequest 새 클래스를 추가하고 해당 코드를 다음 코드로 바꿉다.

    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에서 Models 폴더에 명명된 NotificationHubOptions 새 클래스를 추가하고 해당 코드를 다음 코드로 바꿉다.

    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라는 새 폴더를 추가한 다음 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에서 Services 폴더에 명명된 NotificationHubService 새 클래스를 추가하고 해당 코드를 다음 코드로 바꿉니다.

    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 제공된 태그 식은 OU만 포함하는 경우 20개의 태그로 제한됩니다. 그렇지 않으면 6개의 태그로 제한됩니다. 자세한 내용은 라우팅 및 태그 식을 참조하세요.

  8. Visual Studio에서 Program.cs 열고 코드를 업데이트하여 메서드 호출 아래 builder.Services.AddAuthenticationINotificationService 싱글톤 구현으로 추가 NotificationHubService 합니다.

    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에서 Controllers 폴더에 명명된 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 파일에서 등록된 인스턴스 INotificationService 를 인수로 수락하도록 생성자를 업데이트 NotificationsContoller 하고 읽기 전용 멤버에 할당합니다.

    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. 속성/launchSettings.json 파일에서 weatherforecast api/notifications각 프로필의 launchUrl 속성을 .로 변경합니다.

API 앱 만들기

이제 Azure 앱 Service에서 백 엔드 서비스를 호스트하는 API 앱을 만듭니다. 이 작업은 Azure CLI, Azure PowerShell, Azure 개발자 CLI 및 Azure Portal을 통해 Visual Studio 또는 Visual Studio Code에서 직접 수행할 수 있습니다. 자세한 내용은 웹앱 게시를 참조하세요.

Azure Portal에서 API 앱을 만들려면 다음을 수행합니다.

  1. 웹 브라우저에서 Azure Portal에 로그인합니다.

  2. Azure Portal에서 리소스 만들기 단추를 클릭한 다음 만들기 단추를 선택하기 전에 API 앱을 검색하여 선택합니다.

  3. 만들기 API 앱 페이지에서 만들기 단추를 선택하기 전에 다음 필드를 업데이트합니다.

    필드 작업
    구독 알림 허브를 만든 것과 동일한 대상 구독을 선택합니다.
    리소스 그룹 알림 허브를 만든 것과 동일한 리소스 그룹을 선택합니다.
    속성 글로벌로 고유한 이름을 입력합니다.
    런타임 스택 최신 버전의 .NET이 선택되어 있는지 확인합니다.
  4. API 앱이 프로비전되면 리소스로 이동합니다.

  5. 개요 페이지에서 기본 도메인 값을 기록해 둡다. 이 URL은 .NET MAUI 앱에서 사용되는 백 엔드 엔드포인트입니다. URL은 지정한 API 앱 이름을 형식 https://<app_name>.azurewebsites.net으로 사용합니다.

  6. Azure Portal에서 설정 환경 변수 블레이드로 이동한 다음 앱 설정 탭이 선택되어 있는지 확인합니다.> 그런 다음 추가 단추를 사용하여 다음 설정을 추가합니다.

    속성
    인증:ApiKey <api_key_value>
    NotificationHub:Name <hub_name_value>
    NotificationHub:ConnectionString <hub_connection_string_value>

    Important

    Authentication:ApiKey 간단히 하기 위해 애플리케이션 설정이 추가되었습니다. 프로덕션 시나리오의 경우 Azure KeyVault와 같은 서비스를 사용하여 연결 문자열 안전하게 저장합니다.

    이러한 설정이 모두 입력되면 적용 단추를 선택한 다음 확인 단추를 선택합니다.

백 엔드 서비스 게시

백 엔드 서비스를 Azure 앱 서비스에 게시하려면 다음을 수행합니다.

  1. Visual Studio에서 프로젝트를 마우스 오른쪽 단추로 클릭하고 게시를 선택합니다.
  2. 게시 마법사에서 Azure를 선택한 다음, 다음 단추를 선택합니다.
  3. 게시 마법사에서 Azure 앱 서비스(Windows)를 선택한 다음, 다음 단추를 선택합니다.
  4. 게시 마법사에서 인증 흐름에 따라 Visual Studio를 Azure 구독에 연결하고 앱을 게시합니다.

Visual Studio는 앱을 빌드, 패키지 및 Azure에 게시한 다음 기본 브라우저에서 앱을 시작합니다. 자세한 내용은 ASP.NET 웹앱 게시를 참조 하세요.

Azure Portal의 API 앱 개요 블레이드에서 앱에 대한 게시 프로필을 다운로드한 다음 Visual Studio의 프로필을 사용하여 앱을 게시할 수 있습니다.

게시된 API 유효성 검사

API 앱이 올바르게 게시되었는지 확인하려면 선택한 REST 도구를 사용하여 다음 주소로 POST 요청을 보내야 합니다.

https://<app_name>.azurewebsites.net/api/notifications/requests

참고 항목

기본 주소는 .입니다 https://<app_name>.azurewebsites.net.

apikey 와 해당 값을 포함하도록 요청 헤더를 구성하고, 본문을 원시로 설정하고, 다음 자리 표시자 JSON 콘텐츠를 사용해야 합니다.

{}

서비스에서 응답을 받아야 400 Bad Request 합니다.

참고 항목

.NET MAUI 앱의 플랫폼별 정보가 필요하므로 유효한 요청 데이터를 사용하여 API를 테스트할 수 없습니다.

REST API를 호출하는 방법에 대한 자세한 내용은 Visual Studio에서 .http 파일 및 Http Repl사용하여 웹 API 테스트 사용을 참조하세요. Visual Studio Code 에서 REST 클라이언트 를 사용하여 REST API를 테스트할 수 있습니다.

.NET MAUI 앱 만들기

이 섹션에서는 백 엔드 서비스를 통해 알림 허브에서 푸시 알림을 수신하도록 등록하고 등록을 해제할 수 있는 .NET 다중 플랫폼 앱 UI(.NET MAUI) 앱을 빌드합니다.

.NET MAUI 앱을 만들려면 다음을 수행합니다.

  1. Visual Studio에서 .NET MAUI 앱 프로젝트 템플릿을 사용하여 PushNotificationsDemo라는 새 .NET MAUI 앱을 만듭니다.

  2. Visual Studio에서 .NET MAUI 프로젝트에 Models라는 새 폴더를 추가한 다음 Models 폴더에 명명된 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에서 Models 폴더에 명명된 PushDemoAction 열거형을 추가하고 해당 코드를 다음 코드로 바꿉다.

    namespace PushNotificationsDemo.Models;
    
    public enum PushDemoAction
    {
        ActionA,
        ActionB
    }
    
  4. Visual Studio에서 .NET MAUI 프로젝트에 Services라는 새 폴더를 추가한 다음 Services 폴더에 명명된 IDeviceInstallationService 새 인터페이스를 추가하고 해당 코드를 다음 코드로 바꿉니다.

    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에서 Services 폴더에 명명된 INotificationRegistrationService 인터페이스를 추가하고 해당 코드를 다음 코드로 바꿉니다.

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

    이 인터페이스는 클라이언트와 백 엔드 서비스 간의 상호 작용을 처리합니다.

  6. Visual Studio에서 Services 폴더에 명명된 INotificationActionService 인터페이스를 추가하고 해당 코드를 다음 코드로 바꿉니다.

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

    이 인터페이스는 알림 작업의 처리를 중앙 집중화하는 간단한 메커니즘으로 사용됩니다.

  7. Visual Studio에서 Services 폴더에 명명된 IPushDemoNotificationActionService 인터페이스를 추가하고 해당 코드를 다음 코드로 바꿉니다.

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

    IPushDemoNotificationActionService 형식은 이 앱과 관련이 있으며 열거형을 사용하여 PushDemoAction 강력한 형식의 접근 방식을 사용하여 트리거되는 작업을 식별합니다.

  8. Visual Studio에서 Services 폴더에 명명된 NotificationRegistrationService 클래스를 추가하고 해당 코드를 다음 코드로 바꿉니다.

    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에서 Services 폴더에 명명된 PushDemoNotificationActionService 클래스를 추가하고 해당 코드를 다음 코드로 바꿉니다.

    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 는 비밀이 소스 제어를 벗어나지 않도록 하는 간단한 방법으로 사용됩니다. 이러한 값을 자동화된 빌드의 일부로 바꾸거나 로컬 부분 클래스를 사용하여 재정의할 수 있습니다.

    Important

    .NET MAUI 앱에서 기본 주소를 지정할 때는 .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>";
        }
    }
    

    자리 표시자 값을 백 엔드 서비스를 만들 때 선택한 값으로 바꿉니다. URL은 BackendServiceEndpoint 형식 https://<api_app_name>.azurewebsites.net/을 사용해야 합니다.

    소스 제어에 .gitignore 이 파일을 커밋하지 않도록 파일에 추가 *.local_secrets.* 해야 합니다.

UI 만들기

앱의 UI를 만들려면 다음을 수행합니다.

  1. Visual Studio에서 MainPage.xaml을 열고 해당 자식과 해당 자식을 다음 XAML로 바꿉 VerticalStackLayout 니다.

    <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 구현에 대한 참조 INotificationRegistrationServicereadonly 저장할 지원 필드를 추가합니다.

    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;
                });
        });
    }
    

    Important

    앱에서 등록 및 등록 취소는 사용자 입력에 대한 응답으로 수행되므로 이 기능을 보다 쉽게 탐색하고 테스트할 수 있습니다. 프로덕션 앱에서는 일반적으로 명시적 사용자 입력 없이 앱 수명 주기의 적절한 시점 동안 등록 및 등록 취소 작업을 수행합니다.

  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 이벤트 처리기는 푸시 알림 작업의 수신 및 전파를 보여 줍니다. 일반적으로 경고를 표시하지 않고 특정 보기로 이동하거나 일부 데이터를 새로 고치는 등 자동으로 처리됩니다.

Android 앱 구성

Android에서 푸시 알림을 수신하고 처리하도록 .NET MAUI 앱을 구성하려면 다음을 수행합니다.

  1. Visual Studio에서 .NET MAUI 앱 프로젝트에 Xamarin.Firebase.Messaging NuGet 패키지를 추가합니다.

  2. Visual Studio에서 .NET MAUI 앱 프로젝트의 Platforms/Android 폴더에 google-services.json 파일을 추가합니다. 파일이 프로젝트에 추가되면 다음의 빌드 작업 GoogleServicesJson과 함께 추가되어야 합니다.

    <ItemGroup Condition="'$(TargetFramework)' == 'net8.0-android'">
      <GoogleServicesJson Include="Platforms\Android\google-services.json" />
    </ItemGroup>
    

    소스 제어에 .gitignore 이 파일을 커밋하지 않도록 파일에 추가 google-services.json 해야 합니다.

  3. Visual Studio에서 프로젝트 파일(*.csproj)을 편집하고 Android용을 26.0으로 설정합니다 SupportedOSPlatformVersion .

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

    Google은 API 26에서 Android 알림 채널을 변경했습니다. 자세한 내용은 developer.android.com 알림 채널을 참조하세요.

  4. 프로젝트의 Platforms/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 사용하여 고유한 ID를 제공합니다.

  5. 프로젝트의 Platforms/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 메시지 형식에 대한 자세한 내용은 developer.android.com FCM 메시지 정보를 참조하세요.

  6. Visual Studio에서 Platforms/Android 폴더에서 MainActivity.cs 파일을 열고 다음 using 문을 추가합니다.

    using Android.App;
    using Android.Content;
    using Android.Content.PM;
    using Android.OS;
    using PushNotificationsDemo.Services;
    using Firebase.Messaging;
    
  7. MainActivity 클래스에서 열 때 다시 생성되지 않도록 MainActivity 하려면 다음을 SingleTop 설정합니다LaunchMode.

    [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 지원 필드를 추가하여 참조 및 IDeviceInstallationService 구현을 IPushDemoNotificationActionService 저장합니다.

    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 Firebase 토큰을 Android.Gms.Tasks.IOnSuccessListener 검색하고 저장하는 인터페이스를 구현합니다.

    public class MainActivity : MauiAppCompatActivity, Android.Gms.Tasks.IOnSuccessListener
    {
        public void OnSuccess(Java.Lang.Object result)
        {
            DeviceInstallationService.Token = result.ToString();
        }
    }
    
  11. MainActivity 클래스에서 지정된 Intent 값에 추가 action값이 있는지 여부를 확인하는 메서드를 추가 ProcessNotificationActions 한 다음, 구현을 IPushDemoNotificationActionService 사용하여 조건부로 트리거 action 합니다.

    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);
    }
    

    for는 LaunchMode Intent/>ActivitySingleTop설정되므로 메서드가 아닌 재정의 OnNewIntent 를 통해 기존 Activity 인스턴스로 OnCreate 전송됩니다. 따라서 들어오는 의도를 둘 다 OnNewIntent 에서 OnCreate처리해야 합니다.

  13. 클래스에서 메서드를 MainActivity 호출 ProcessNotificationActions 하고 Firebase에서 토큰을 검색하여 다음으로 추가하는 MainActivity 메서드를 재정 OnCreateIOnSuccessListener합니다.

    protected override void OnCreate(Bundle? savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
    
        if (DeviceInstallationService.NotificationsSupported)
            FirebaseMessaging.Instance.GetToken().AddOnSuccessListener(this);
    
        ProcessNotificationsAction(Intent);
    }
    

    참고 항목

    푸시 알림을 계속 받으려면 실행할 때마다 앱을 다시 등록하고 디버그 세션에서 중지해야 합니다.

  14. Visual Studio에서 Platforms/Android 폴더의 AndroidManifest.xml 파일에 권한을 추가 POST_NOTIFICATIONS 합니다.

    <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
    

    이 코드는 표시되면 MainPage Android에서 실행되며 사용자에게 사용 권한을 부여 POST_NOTIFICATIONS 하도록 요청합니다. .NET MAUI 권한에 대한 자세한 내용은 사용 권한을 참조 하세요.

iOS 앱 구성

iOS 시뮬레이터는 Apple 실리콘 또는 T2 프로세서가 있는 Mac 컴퓨터의 macOS 13 이상에서 실행되는 경우 iOS 16 이상에서 원격 알림을 지원합니다. 각 시뮬레이터는 해당 시뮬레이터와 실행 중인 Mac 하드웨어의 조합에 고유한 등록 토큰을 생성합니다.

Important

시뮬레이터는 Apple Push Notification Service 샌드박스 환경을 지원합니다.

다음 지침에서는 iOS 시뮬레이터에서 원격 알림 수신을 지원하는 하드웨어를 사용하고 있다고 가정합니다. 그렇지 않은 경우 물리적 디바이스에서 iOS 앱을 실행해야 합니다. 이 경우 푸시 알림 기능을 포함하는 앱에 대한 프로비저닝 프로필을 만들어야 합니다. 그런 다음 인증서 및 프로비저닝 프로필을 사용하여 앱이 빌드되었는지 확인해야 합니다. 이 작업을 수행하는 방법에 대한 자세한 내용은 Azure Notification Hubs에서 작동하도록 iOS 앱 설정을 참조하고 아래 지침을 따르세요.

푸시 알림을 수신하고 처리하도록 iOS에서 .NET MAUI 앱을 구성하려면 다음을 수행합니다.

  1. Visual Studio에서 프로젝트 파일(*.csproj)을 편집하고 iOS용을 13.0으로 설정합니다 SupportedOSPlatformVersion .

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

    Apple은 iOS 13에서 푸시 서비스를 변경했습니다. 자세한 내용은 iOS 13용 Azure Notification Hubs 업데이트를 참조하세요.

  2. Visual Studio에서 프로젝트의 Platforms/iOS 폴더에 Entitlements.plist 파일을 추가하고 파일에 다음 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 환경 자격이 설정되고 개발 Apple Push Notification 서비스 환경을 사용하도록 지정됩니다. 프로덕션 앱에서 이 권한 값은 .로 production설정해야 합니다. 이 자격에 대한 자세한 내용은 developer.apple.com APS 환경 자격을 참조하세요.

    자격 파일을 추가하는 방법에 대한 자세한 내용은 iOS 자격을 참조 하세요.

  3. Visual Studio에서 프로젝트의 Platforms/iOS 폴더에 명명된 DeviceInstallationService 새 클래스를 추가하고 파일에 다음 코드를 추가합니다.

    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 사용하여 고유한 ID를 제공합니다.

  4. Visual Studio에서 프로젝트의 Platforms/iOS 폴더에 명명된 NSDataExtensions 새 클래스를 추가하고 파일에 다음 코드를 추가합니다.

    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에서 Platforms/iOS 폴더에서 AppDelegate.cs 파일을 열고 다음 using 문을 추가합니다.

    using System.Diagnostics;
    using Foundation;
    using PushNotificationsDemo.Platforms.iOS;
    using PushNotificationsDemo.Services;
    using UIKit;
    using UserNotifications;
    
  6. AppDelegate 클래스에서 지원 필드를 추가하여 , INotificationRegistrationServiceIDeviceInstallationService 구현에 IPushDemoNotificationActionService대한 참조를 저장합니다.

    IPushDemoNotificationActionService _notificationActionService;
    INotificationRegistrationService _notificationRegistrationService;
    IDeviceInstallationService _deviceInstallationService;
    
  7. 클래스에서 앱의 AppDelegate 종속성 주입 컨테이너에서 구체적인 구현을 검색하는 프라이빗 속성을 추가 NotificationActionServiceNotificationRegistrationServiceDeviceInstallationService 합니다.

    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 속성 값을 설정하는 메서드를 IDeviceInstallationService.Token 추가 CompleteRegistrationAsync 합니다.

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

    또한 이 메서드는 등록을 새로 고치고 마지막으로 저장된 이후 업데이트된 경우 디바이스 토큰을 캐시합니다.

  9. AppDelegate 클래스에서 알림 데이터를 처리하고 조건부로 호출하는 NSDictionary NotificationActionService.TriggerAction메서드를 추가 ProcessNotificationActions 합니다.

    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 열고 네임스페이 using PushNotificationsDemo.Services 스에 대한 문을 추가합니다.

    using PushNotificationsDemo.Services;
    
  2. MauiProgram 클래스에서 각 플랫폼 및 플랫폼 NotificationRegistrationService PushDemoNotificationActionService 간 및 서비스를 등록 DeviceInstallationService 하고 개체를 반환 MauiAppBuilder 하는 확장 메서드에 대한 RegisterServices 코드를 추가합니다.

    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 클래스에서 형식을 싱글톤으로 등록 MainPage 하고 개체를 반환 MauiAppBuilder 하는 확장 메서드에 대한 RegisterViews 코드를 추가합니다.

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

    MainPage 종속성이 필요하고 종속성이 필요한 INotificationRegistrationService 모든 형식을 종속성 주입 컨테이너에 등록해야 하므로 형식이 등록됩니다.

  4. MauiProgram 클래스에서 메서드를 CreateMauiApp 수정하여 메서드를 호출하고 RegisterViews 확장 메서드를 RegisterServices 호출합니다.

    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의 종속성 주입에 대한 자세한 내용은 종속성 주입을 참조하세요.

앱 테스트

백 엔드 서비스를 사용하여 앱에 푸시 알림을 보내거나 Azure Portal을 통해 앱을 테스트할 수 있습니다.

iOS 시뮬레이터는 Apple 실리콘 또는 T2 프로세서가 있는 Mac 컴퓨터의 macOS 13 이상에서 실행되는 경우 iOS 16 이상에서 원격 알림을 지원합니다. 이러한 하드웨어 요구 사항을 충족하지 않는 경우 물리적 디바이스에서 iOS 앱을 테스트해야 합니다. Android에서는 개발자 잠금 해제된 물리적 디바이스 또는 에뮬레이터에서 앱을 테스트할 수 있습니다.

Android 및 iOS는 백그라운드에서 실행될 때 앱을 대신하여 푸시 알림을 표시합니다. 알림이 수신될 때 앱이 포그라운드에서 실행되는 경우 앱의 코드에 따라 동작이 결정됩니다. 예를 들어 알림에 포함된 새 정보를 반영하도록 앱의 인터페이스를 업데이트할 수 있습니다.

백 엔드 서비스를 사용하여 테스트

Azure 앱 서비스에 게시된 백 엔드 서비스를 통해 앱에 테스트 푸시 알림을 보내려면 다음을 수행합니다.

  1. Visual Studio에서 Android 또는 iOS에서 PushNotificationsDemo 앱을 실행하고 등록 단추를 선택합니다.

    참고 항목

    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를 호출하는 방법에 대한 자세한 내용은 Visual Studio에서 .http 파일 및 Http Repl사용하여 웹 API 테스트 사용을 참조하세요. Visual Studio Code 에서 REST 클라이언트 를 사용하여 REST API를 테스트할 수 있습니다.

Azure Portal을 사용하여 테스트

Azure Notification Hubs를 사용하면 앱이 푸시 알림을 받을 수 있는지 확인할 수 있습니다.

Azure Portal을 통해 앱에 테스트 푸시 알림을 보내려면 다음을 수행합니다.

  1. Visual Studio에서 Android 또는 iOS에서 PushNotificationsDemo 앱을 실행하고 등록 단추를 선택합니다.

    참고 항목

    Android에서 테스트하는 경우 디버그 구성을 사용하여 실행되고 있지 않은지 확인합니다. 또는 앱이 이전에 배포된 경우 강제로 닫혔는지 확인하고 시작 관리자에서 다시 시작합니다.

  2. Azure Portal에서 알림 허브로 이동하고 개요 블레이드에서 보내기 테스트 단추를 선택합니다.

  3. 보내기 테스트 블레이드에서 필요한 플랫폼을 선택하고 페이로드를 수정합니다.

    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 Portal은 알림이 성공적으로 전송되었음을 나타내야 합니다.

    Firebase Cloud Messaging 메시지 형식에 대한 자세한 내용은 developer.android.com FCM 메시지 정보를 참조하세요.

  4. Android 또는 iOS의 앱에서 받은 ActionA 작업을 보여 주는 경고가 표시됩니다.

문제 해결

다음 섹션에서는 클라이언트 앱에서 푸시 알림을 사용하려고 할 때 발생하는 일반적인 문제에 대해 설명합니다.

백 엔드 서비스의 응답 없음

로컬로 테스트하는 경우 백 엔드 서비스가 실행 중이고 올바른 포트를 사용하고 있는지 확인합니다.

Azure API 앱에 대해 테스트하는 경우 서비스가 실행 중이고 배포되었으며 오류 없이 시작되었는지 확인합니다.

REST 도구 또는 .NET MAUI 앱 구성에서 기본 주소를 올바르게 지정했는지 확인합니다. 기본 주소는 로컬로 테스트할 https://<api_name>.azurewebsites.net https://localhost:7020 때나 있어야 합니다.

백 엔드 서비스에서 401 상태 코드 수신

요청 헤더를 apikey 올바르게 설정하고 있으며 이 값이 백 엔드 서비스에 대해 구성한 값과 일치하는지 확인합니다.

로컬로 테스트할 때 이 오류가 표시되는 경우 .NET MAUI 앱에서 정의한 키 값이 백 엔드 서비스에서 사용하는 사용자 비밀 값과 일치하는 Authentication:ApiKey 지 확인합니다.

Azure API 앱을 사용하여 테스트하는 경우 .NET MAUI 앱에 정의된 키 값이 Azure Portal에 정의된 앱 설정 값과 일치하는 Authentication:ApiKey 지 확인합니다. 백 엔드 서비스를 배포한 후 이 앱 설정을 만들거나 변경한 경우 값을 적용하려면 서비스를 다시 시작해야 합니다.

백 엔드 서비스에서 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

Important

.NET MAUI 앱에서 기본 주소를 지정할 때는 .NET MAUI 앱으로 /끝나는지 확인합니다. 기본 주소는 로컬로 테스트할 https://<api_name>.azurewebsites.net https://localhost:7020/ 때나 있어야 합니다.

디버그 세션을 시작하거나 중지한 후 Android에서 알림을 수신하지 않음

디버그 세션을 시작할 때마다 등록해야 합니다. 디버거를 사용하면 새 Firebase 토큰이 생성되므로 알림 허브 설치를 업데이트해야 합니다.

등록할 수 없고 알림 허브 오류 메시지가 표시됩니다.

테스트 디바이스에 네트워크 연결이 있는지 확인합니다. 그런 다음 중단점을 설정하여 HTTP 응답 상태 코드를 확인하여 .에서 HttpResponse속성을 검사 StatusCode 합니다.

상태 코드에 따라 해당하는 경우 이전 문제 해결 제안을 검토합니다.

해당 API에 대한 특정 상태 코드를 반환하는 줄에 중단점을 설정합니다. 그런 다음, 로컬로 디버그할 때 백 엔드 서비스를 호출해 봅니다.

선택한 REST 도구에서 백 엔드 서비스가 예상대로 작동하는지 확인하고 선택한 플랫폼에 대해 .NET MAUI 앱에서 만든 페이로드를 사용합니다.

플랫폼별 구성 섹션을 검토하여 누락된 단계가 없는지 확인합니다. 선택한 플랫폼에 InstallationId 적합한 값과 Token 변수가 확인되고 있는지 확인합니다.

디바이스에 대한 ID를 확인할 수 없음 디바이스 오류 메시지

플랫폼별 구성 섹션을 검토하여 누락된 단계가 없는지 확인합니다.