빠른 시작 Windows App SDK의 푸시 알림

이 빠른 시작에서는 Windows 앱 SDK를 사용하여 푸시 알림을 보내고 받는 데스크톱 Windows 애플리케이션을 만듭니다.

필수 조건

샘플 앱

이 빠른 시작에서는 앱에 푸시 알림 지원을 추가하는 방법을 안내합니다. GitHub에 있는 샘플 앱의 컨텍스트에서 이 빠른 시작의 예제 코드를 참조하세요.

API 참조

푸시 알림에 대한 API 참조 설명서는 Microsoft.Windows.PushNotifications 네임스페이스를 참조하세요.

Azure Active Directory(AAD)에서 앱의 ID 구성

Windows 앱 SDK의 푸시 알림은 AAD(Azure Active Directory)의 ID를 사용합니다. Azure 자격 증명은 WNS 채널 URI를 요청할 때와 푸시 알림을 보내기 위해 액세스 토큰을 요청할 때 필요합니다. 참고: Microsoft Partner Center와 함께 Windows App SDK 푸시 알림을 사용하여 NOT를 지원합니다.

1단계: AAD 앱 등록 만들기

Azure 계정에 로그인하고 새 AAD 앱 등록 리소스를 만듭니다. 새 등록을 선택합니다.

2단계: 이름 입력 및 다중 테넌트 옵션 선택

  1. 앱 이름을 입력합니다.

  2. 푸시 알림은 다중 테넌트 옵션이 필요하므로 이를 선택합니다.

    1. 테넌트에 대한 자세한 내용은 누가 앱에 로그인할 수 있나요?를 참조하세요.
  3. 등록을 선택합니다.

  4. 활성화 등록 및 액세스 토큰 요청 시 사용할 Azure AppId이므로Application(클라이언트) ID에 주의하십시오.

  5. 액세스 토큰을 요청할 때 사용할 Azure TenantId이므로디렉터리(테넌트) ID를 기록해 둡니다.

    Important

    AAD App Registration Tenant애플리케이션(클라이언트) ID디렉토리(임차인) ID 를 기록합니다.

  6. 채널 요청을 요청할 때 사용할 Azure ObjectId이므로개체 ID를 기록해 둡니다. 이 ID는 Essentials 페이지에 나열된 개체 ID가 아닙니다. 대신 올바른 Object ID를 찾으려면 Essentials 페이지의 Managed Application in local directory 필드에서 앱 이름을 클릭합니다:

    Screenshot showing the Managed application in local directory option on the Essentials page

    Screenshot showing the Object ID field

    참고 항목

    Object ID를 얻으려면 서비스 주체가 필요합니다. App과 연결된 Object ID가 없는 경우 다음 문서 중 하나의 단계를 따라 Azure 포털에 Object ID를 만들거나 명령줄을 사용해야 합니다:

    포털을 사용하여 리소스에 액세스할 수 있는 Azure AD 애플리케이션 및 서비스 주체 만들기

    Azure PowerShell을 사용하여 인증서로 서비스 주체 만들기

3단계: 앱 등록을 위한 비밀 만들기

비밀은 푸시 알림을 보내기 위한 액세스 토큰을 요청할 때 Azure AppId/ClientId와 함께 사용됩니다.

AAD App Secret

인증서 & 비밀로 이동하고 새 클라이언트 암호를 선택합니다.

Important

비밀을 만든 후에는 이를 복사하여 Azure Key Vault와 같은 안전한 위치에 저장해야 합니다. 만든 직후 한 번만 볼 수 있습니다.

4단계: 앱의 패키지 패밀리 이름을 Azure AppId에 매핑합니다

Important

이제 WNS(Windows 푸시 알림 서비스)가 Azure Portal과 통합되었습니다. 새로운 등록 환경은 미리 보기에서 사용할 수 있습니다. 패키지된 앱(외부 위치가 포함된 패키지 포함)인 경우 이 흐름을 사용하여 앱의 PFN(패키지 패밀리 이름) 및 해당 Azure 앱Id를 매핑할 수 있습니다.

귀하의 앱이 패키지형 Win32 앱인 경우 제목 줄 "Windows App SDK 푸시 알림 요청" 및 본문 "Azure Subscription: [Azure Subscription ID]"를 이메일 Win_App_SDK_Push@microsoft.com 로 보내 새로운 Azure Portal 미리보기 환경에 대한 액세스를 요청하십시오. 요청은 매주 완료됩니다. 매핑 요청이 완료되면 알림이 받게 됩니다.

푸시 알림을 받도록 앱 구성

1단계: 네임스페이스 선언 추가

Windows 앱 SDK 푸시 알림에 대한 네임스페이스를 추가합니다 Microsoft.Windows.PushNotifications.

#include <winrt/Microsoft.Windows.PushNotifications.h>

using namespace winrt::Microsoft::Windows::PushNotifications;

2단계: COM 활성화기를 앱 매니페스트에 추가합니다

Important

앱이 언팩된 경우(즉, 실행 시 패키지 ID가 부족한 경우) 단계 3: 앱 시작 시 푸시 알림 등록 및 응답으로 이동합니다.

앱이 패키지된 경우(외부 위치로 패키지 포함): Package.appxmanifest를 엽니다. 이 <Application> 요소 안에 다음을 추가합니다. 각 Id, Executable, DisplayName 값을 앱 고유의 값으로 바꿉니다.

<!--Packaged apps only-->
<!--package.appxmanifest-->

<Package
  ...
  xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
  ...
  <Applications>
    <Application>
      ...
      <Extensions>

        <!--Register COM activator-->    
        <com:Extension Category="windows.comServer">
          <com:ComServer>
              <com:ExeServer Executable="SampleApp\SampleApp.exe" DisplayName="SampleApp" Arguments="----WindowsAppRuntimePushServer:">
                <com:Class Id="[Your app's Azure AppId]" DisplayName="Windows App SDK Push" />
            </com:ExeServer>
          </com:ComServer>
        </com:Extension>
    
      </Extensions>
    </Application>
  </Applications>
 </Package>    

3단계: 앱 시작 시 푸시 알림 등록 및 응답

앱의 main() 메서드를 업데이트하여 다음을 추가합니다.

  1. PushNotificationManager::D efault()를 호출 하여 푸시 알림을 수신하도록 앱을 등록합니다. Register().
  2. AppInstance::GetCurrent()를 호출 하여 활성화 요청의 원본을 확인합니다. GetActivatedEventArgs(). 푸시 알림에서 활성화가 트리거된 경우 알림의 페이로드에 따라 응답합니다.

Important

AppInstance.GetCurrent.GetActivatedEventArgs로 전화하기 전에 PushNotificationManager::Default(.Register )로 전화해야 합니다.

다음 샘플은 GitHub에 있는 패키지된 샘플 앱에서 가져옵니다.

// cpp-console.cpp
#include "pch.h"
#include <iostream>
#include <winrt/Microsoft.Windows.PushNotifications.h>
#include <winrt/Microsoft.Windows.AppLifecycle.h>
#include <winrt/Windows.Foundation.h>
#include <wil/result.h>
#include <wil/cppwinrt.h>


using namespace winrt;
using namespace Windows::Foundation;

using namespace winrt::Microsoft::Windows::PushNotifications;
using namespace winrt::Microsoft::Windows::AppLifecycle;

winrt::guid remoteId{ "7edfab6c-25ae-4678-b406-d1848f97919a" }; // Replace this with your own Azure ObjectId



void SubscribeForegroundEventHandler()
{
    winrt::event_token token{ PushNotificationManager::Default().PushReceived([](auto const&, PushNotificationReceivedEventArgs const& args)
    {
        auto payload{ args.Payload() };

        std::string payloadString(payload.begin(), payload.end());
        std::cout << "\nPush notification content received in the FOREGROUND: " << payloadString << std::endl;
    }) };
}

int main()
{
    // Setup an event handler, so we can receive notifications in the foreground while the app is running.
    SubscribeForegroundEventHandler();

    PushNotificationManager::Default().Register();

    auto args{ AppInstance::GetCurrent().GetActivatedEventArgs() };
    switch (args.Kind())
    {
        // When it is launched normally (by the users, or from the debugger), the sample requests a WNS Channel URI and
        // displays it, then waits for notifications. This user can take a copy of the WNS Channel URI and use it to send
        // notifications to the sample
        case ExtendedActivationKind::Launch:
        {
            // Checks to see if push notifications are supported. Certain self-contained apps may not support push notifications by design
            if (PushNotificationManager::IsSupported())
            {
                // Request a WNS Channel URI which can be passed off to an external app to send notifications to.
                // The WNS Channel URI uniquely identifies this app for this user and device.
                PushNotificationChannel channel{ RequestChannel() };
                if (!channel)
                {
                    std::cout << "\nThere was an error obtaining the WNS Channel URI" << std::endl;
    
                    if (remoteId == winrt::guid { "00000000-0000-0000-0000-000000000000" })
                    {
                        std::cout << "\nThe ObjectID has not been set. Refer to the readme file accompanying this sample\nfor the instructions on how to obtain and setup an ObjectID" << std::endl;
                    }
                }
    
                std::cout << "\nPress 'Enter' at any time to exit App." << std::endl;
                std::cin.ignore();
            }
            else
            {
                // App implements its own custom socket here to receive messages from the cloud since Push APIs are unsupported.
            }
        }
        break;

        // When it is activated from a push notification, the sample only displays the notification.
        // It doesn’t register for foreground activation of perform any other actions
        // because background activation is meant to let app perform only small tasks in order to preserve battery life.
        case ExtendedActivationKind::Push:
        {
            PushNotificationReceivedEventArgs pushArgs{ args.Data().as<PushNotificationReceivedEventArgs>() };

            // Call GetDeferral to ensure that code runs in low power
            auto deferral{ pushArgs.GetDeferral() };

            auto payload{ pushArgs.Payload() } ;

            // Do stuff to process the raw notification payload
            std::string payloadString(payload.begin(), payload.end());
            std::cout << "\nPush notification content received in the BACKGROUND: " << payloadString.c_str() << std::endl;
            std::cout << "\nPress 'Enter' to exit the App." << std::endl;

            // Call Complete on the deferral when finished processing the payload.
            // This removes the override that kept the app running even when the system was in a low power mode.
            deferral.Complete();
            std::cin.ignore();
        }
        break;

        default:
            std::cout << "\nUnexpected activation type" << std::endl;
            std::cout << "\nPress 'Enter' to exit the App." << std::endl;
            std::cin.ignore();
            break;
    }

    // We do not call PushNotificationManager::UnregisterActivator
    // because then we wouldn't be able to receive background activations, once the app has closed.
    // Call UnregisterActivator once you don't want to receive push notifications anymore.
}

4단계: WNS 채널 URI 요청 및 WNS 서버에 등록

WNS 채널 URI는 푸시 알림을 보내기 위한 HTTP 끝점입니다. 각 클라이언트는 푸시 알림을 받기 위해 채널 URI를 요청하고 WNS 서버에 등록해야 합니다.

참고 항목

WNS 채널 URI는 30일 후에 만료됩니다.

auto channelOperation{ PushNotificationManager::Default().CreateChannelAsync(winrt::guid("[Your app's Azure ObjectID]")) };

PushNotificationManager 채널 URI를 만들려고 시도하고 15분 이내에 자동으로 다시 시도합니다. 호출이 완료될 때까지 기다리는 이벤트 처리기를 만듭니다. 호출이 완료된 후 성공한 경우 WNS 서버에 URI를 등록합니다.

// cpp-console.cpp

winrt::Windows::Foundation::IAsyncOperation<PushNotificationChannel> RequestChannelAsync()
{
    // To obtain an AAD RemoteIdentifier for your app,
    // follow the instructions on https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app
    auto channelOperation = PushNotificationManager::Default().CreateChannelAsync(remoteId);

    // Setup the inprogress event handler
    channelOperation.Progress(
        [](auto&& sender, auto&& args)
        {
            if (args.status == PushNotificationChannelStatus::InProgress)
            {
                // This is basically a noop since it isn't really an error state
                std::cout << "Channel request is in progress." << std::endl << std::endl;
            }
            else if (args.status == PushNotificationChannelStatus::InProgressRetry)
            {
                LOG_HR_MSG(
                    args.extendedError,
                    "The channel request is in back-off retry mode because of a retryable error! Expect delays in acquiring it. RetryCount = %d",
                    args.retryCount);
            }
        });

    auto result = co_await channelOperation;

    if (result.Status() == PushNotificationChannelStatus::CompletedSuccess)
    {
        auto channelUri = result.Channel().Uri();

        std::cout << "channelUri: " << winrt::to_string(channelUri.ToString()) << std::endl << std::endl;

        auto channelExpiry = result.Channel().ExpirationTime();

        // Caller's responsibility to keep the channel alive
        co_return result.Channel();
    }
    else if (result.Status() == PushNotificationChannelStatus::CompletedFailure)
    {
        LOG_HR_MSG(result.ExtendedError(), "We hit a critical non-retryable error with channel request!");
        co_return nullptr;
    }
    else
    {
        LOG_HR_MSG(result.ExtendedError(), "Some other failure occurred.");
        co_return nullptr;
    }

};

PushNotificationChannel RequestChannel()
{
    auto task = RequestChannelAsync();
    if (task.wait_for(std::chrono::seconds(300)) != AsyncStatus::Completed)
    {
        task.Cancel();
        return nullptr;
    }

    auto result = task.GetResults();
    return result;
}

5단계: 앱 빌드 및 설치

Visual Studio를 사용하여 앱을 빌드하고 설치합니다. 솔루션 탐색기에서 솔루션 파일을 마우스 오른쪽 단추로 클릭하고 배포를 선택합니다. Visual Studio는 앱을 빌드하고 컴퓨터에 설치합니다. 시작 메뉴 또는 Visual Studio 디버거를 통해 앱을 실행할 수 있습니다.

앱에 푸시 알림 보내기

이 시점에서 모든 구성이 완료되고 WNS 서버가 클라이언트 앱에 푸시 알림을 보낼 수 있습니다. 다음 단계에서는 푸시 알림 서버 요청 및 응답 헤더를 참조 하여 자세한 내용을 확인하세요 .

1단계: 액세스 토큰 요청

푸시 알림을 보내려면 먼저 WNS 서버에서 액세스 토큰을 요청해야 합니다. Azure TenantId, Azure AppId 및 비밀을 사용하여 HTTP POST 요청을 보냅니다. Azure TenantId 및 Azure 앱Id를 검색하는 방법에 대한 자세한 내용은 로그인을 위한 테넌트 및 앱 ID 값 가져오기를 참조하세요.

HTTP 샘플 요청:

POST /{tenantID}/oauth2/v2.0/token Http/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 160

grant_type=client_credentials&client_id=<Azure_App_Registration_AppId_Here>&client_secret=<Azure_App_Registration_Secret_Here>&scope=https://wns.windows.com/.default/

C# 샘플 요청:

//Sample C# Access token request
var client = new RestClient("https://login.microsoftonline.com/{tenantID}/oauth2/v2.0");
var request = new RestRequest("/token", Method.Post);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", "[Your app's Azure AppId]");
request.AddParameter("client_secret", "[Your app's secret]");
request.AddParameter("scope", "https://wns.windows.com/.default");
RestResponse response = await client.ExecutePostAsync(request);
Console.WriteLine(response.Content);

요청이 성공하면 access_token 필드에 토큰이 포함된 응답을 받게 됩니다.

{
    "token_type":"Bearer",
    "expires_in":"86399",
    "ext_expires_in":"86399",
    "expires_on":"1653771789",
    "not_before":"1653685089",
    "access_token":"[your access token]"
}

2단계. 원시 알림 보내기

이전 단계에서 얻은 액세스 토큰과 보내려는 푸시 알림의 내용이 포함된 HTTP POST 요청을 만듭니다. 푸시 알림의 콘텐츠가 앱으로 전달됩니다.

POST /?token=[The token query string parameter from your channel URL. E.g. AwYAAABa5cJ3...] HTTP/1.1
Host: dm3p.notify.windows.com
Content-Type: application/octet-stream
X-WNS-Type: wns/raw
Authorization: Bearer [your access token]
Content-Length: 46

{ Sync: "Hello from the Contoso App Service" }
var client = new RestClient("[Your channel URL. E.g. https://wns2-by3p.notify.windows.com/?token=AwYAAABa5cJ3...]");
var request = new RestRequest();
request.Method = Method.Post; 
request.AddHeader("Content-Type", "application/octet-stream");
request.AddHeader("X-WNS-Type", "wns/raw");
request.AddHeader("Authorization", "Bearer [your access token]");
request.AddBody("Notification body");
RestResponse response = await client.ExecutePostAsync(request);");

3 단계: 클라우드 소스 앱 알림 보내기

원시 알림을 보내는 데만 관심이 있는 경우 이 단계를 무시합니다. 푸시 알림 알림이라고도 하는 클라우드 소스 앱 알림을 보내려면 먼저 Windows 앱 SDK 빠른 시작: 앱 알림을 따릅니다. 앱 알림은 푸시(클라우드에서 전송)하거나 로컬로 전송할 수 있습니다. 클라우드 소스 앱 알림을 보내는 것은 2단계 에서 원시 알림을 보내는 것과 유사하지만, X-WNS-Type 헤더가 toast이고, Content-Type이text/xml이고, 콘텐츠에 앱 알림 XML 페이로드가 포함된 것을 제외하고는 마찬가지입니다. XML 페이로드를 생성하는 방법에 대한 자세한 내용은 알림 XML 스키마를 참조하세요.

액세스 토큰과 보내려는 클라우드 원본 앱 알림의 내용을 포함하는 HTTP POST 요청을 만듭니다. 푸시 알림의 콘텐츠가 앱으로 전달됩니다.

POST /?token=AwYAAAB%2fQAhYEiAESPobjHzQcwGCTjHu%2f%2fP3CCNDcyfyvgbK5xD3kztniW%2bjba1b3aSSun58SA326GMxuzZooJYwtpgzL9AusPDES2alyQ8CHvW94cO5VuxxLDVzrSzdO1ZVgm%2bNSB9BAzOASvHqkMHQhsDy HTTP/1.1
Host: dm3p.notify.windows.com
Content-Type: text/xml
X-WNS-Type: wns/toast
Authorization: Bearer [your access token]
Content-Length: 180

<toast><visual><binding template="ToastGeneric"><text>Example cloud toast notification</text><text>This is an example cloud notification using XML</text></binding></visual></toast>
var client = new RestClient("https://dm3p.notify.windows.com/?token=AwYAAAB%2fQAhYEiAESPobjHzQcwGCTjHu%2f%2fP3CCNDcyfyvgbK5xD3kztniW%2bjba1b3aSSun58SA326GMxuzZooJYwtpgzL9AusPDES2alyQ8CHvW94cO5VuxxLDVzrSzdO1ZVgm%2bNSB9BAzOASvHqkMHQhsDy");
client.Timeout = -1;

var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "text/xml");
request.AddHeader("X-WNS-Type", "wns/toast");
request.AddHeader("Authorization", "Bearer <AccessToken>");
request.AddParameter("text/xml", "<toast><visual><binding template=\"ToastGeneric\"><text>Example cloud toast notification</text><text>This is an example cloud notification using XML</text></binding></visual></toast>",  ParameterType.RequestBody);
Console.WriteLine(response.Content);

리소스