共用方式為


快速入門:Windows App SDK 中的推播通知

在本快速入門中,您將建立使用 Windows 應用程式 SDK 來傳送和接收推播通知的桌面 Windows 應用程式。

Prerequisites

範例應用程式

本快速入門會逐步解說如何在 Windows 應用程式 SDK 1.7 上將推播通知支援新增至您的應用程式。 請參閱 GitHub 上的範例應用程式中與此快速入門類似的程式碼。 請務必 使用您慣用的 Windows 應用程式 SDK 版本簽出分支 ,以取得最符合您專案的範例。

您也可以在 範例存放庫中選取版本分支,以尋找每個 Windows 應用程式 SDK 版本的範例。

API 參考資料

如需推播通知的 API 參考檔,請參閱 Microsoft.Windows.PushNotifications 命名空間

在 Azure Active Directory 中設定應用程式的身分識別 (AAD)

Windows App SDK 中的推播通知會使用來自 Azure Active Directory (AAD) 的身分識別。 要求 WNS 通道 URI 時,以及要求存取令牌以傳送推播通知時,需要 Azure 認證。 附注:我們不支援 使用 Microsoft 合作夥伴中心的 Windows App SDK 推播通知

步驟 1:建立 AAD 應用程式註冊

登入您的 Azure 帳戶,並建立新的 AAD 應用程式註冊 資源。 選取新增註冊

步驟 2:提供名稱並選取多租用戶選項

  1. 提供應用程式名稱。

  2. 推播通知需要多租用戶選項,因此請選取該選項。

    1. 如需瞭解關於租戶的更多資訊,請參閱 誰可以登入您的應用程式?
  3. 選取 註冊

  4. 請記下您的 應用程式(用戶端)ID,因為這是您在進行啟用註冊及存取令牌請求期間將使用的 Azure AppId

  5. 請記下您的 目錄(租戶)ID,因為這就是您在要求存取權杖時將使用的 Azure 租戶ID

    Important

    AAD 應用程式註冊租戶 記下 應用程式識別碼目錄(租戶)識別碼

  6. 請記下您的 物件 ID,因為這是您在發起通道請求時將使用的 Azure 物件 ID。 請注意,這不是在 [基本資訊 ] 頁面上列出的物件標識符。 相反地,若要尋找正確的 對象標識符,請在 [Essentials] 頁面的 [Managed 應用程式] 的 [本機目錄] 欄位中,點擊應用程式名稱:

    顯示在 [基本資訊] 頁面上的 [本機目錄中的管理應用程式] 選項的螢幕截圖

    顯示 Object ID 欄位的螢幕快照

    Note

    如果應用程式沒有相關聯的物件標識符,則需要 服務主體 才能取得對象標識碼,請遵循下列其中一篇文章中的步驟,在 Azure 入口網站或使用命令行建立物件識別碼:

    使用入口網站建立 Azure AD 應用程式和服務主體,以存取資源

    使用 Azure PowerShell 建立含有憑證的服務主體

步驟 3:為您的應用程式註冊建立秘密

當您要求存取令牌以傳送推播通知時,您的密鑰將會與您的 Azure AppId/ClientId 共同使用。

AAD 應用程式秘密

流覽至 [憑證和秘密] ,然後選取 [ 新增客戶端密碼]。

Important

確定您在建立密碼后複製秘密,並將其儲存在安全的位置,例如 Azure Key Vault。 建立之後,它只能檢視一次。

步驟 4:將應用程式的封裝系列名稱對應至 Azure 應用程式 ID

如果您的應用程式已封裝(包括透過外部位置進行封裝),您可以使用此流程來映射應用程式的封裝系列名稱(PFN)及其 Azure 應用程式識別碼。

如果您的應用程式是已封裝的 Win32 應用程式,請透過電子郵件 Win_App_SDK_Push@microsoft.com 來建立套件系列名稱(PFN)對應要求。標題請寫「Windows App SDK 推播通知對應要求」,內容請填寫「PFN: [您的 PFN],AppId: [您的 APPId],ObjectId: [您的 ObjectId]」。 每周會完成映射請求。 當您的對應要求完成後,您將收到通知。

擁有 Azure AppId、ObjectId 和秘密之後,您可以將這些認證新增至下列範例程式碼。

設定您的應用程式以接收推播通知

步驟 1:新增 Windows 應用程式 SDK 和必要的 NuGet 套件

接下來,以滑鼠右鍵按一下 [方案總管] 中的解決方案,然後選取 [管理 NuGet 套件]。

在 [套件管理員] 中,新增下列套件:

  • Microsoft.WindowsAppSDK (最低版本 1.1.0)
  • Microsoft.Windows.SDK.BuildTools (最低版本 10.0.22000.194)
  • Microsoft.Windows.CppWinRT,(最低版本 2.0.210930.14)
  • Microsoft.Windows.ImplementationLibrary,(最低版本 1.0.210930.1)

如果這是您第一次在專案中使用 Windows 應用程式 SDK,而且它是以外部位置封裝或取消封裝,請將下列屬性新增至專案檔來初始化 Windows 應用程式 SDK:

<!-- your .vcxproj or .proj file -->
<PropertyGroup Label="Globals">
    <!-- Other properties -->
    <WindowsPackageType>None</WindowsPackageType>
</PropertyGroup>

或使用啟動載入器 API。 如需詳細資訊,請參閱針對以 外部位置封裝或未封裝的應用程式使用 Windows 應用程式 SDK 執行階段

Note

如果 SDK 未初始化,應用程式將會擲回 System.Runtime.InteropServices.COMException (0x80040154): Class not registered (0x80040154 (REGDB_E_CLASSNOTREG)) 且不會執行。

步驟 2:新增命名空間

接下來,新增 Windows 應用程式 SDK 推播通知 Microsoft.Windows.PushNotifications的命名空間。

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

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

如果您收到「找不到 Microsoft.Windows.PushNotifications」錯誤,這可能表示尚未產生標頭檔。 若要解決此問題,請確定您已安裝上述套件、註解掉導致錯誤的 include 和 using 陳述式,然後重建應用程式以產生標頭檔。 建置成功之後,請取消註解 include 和 using 陳述式,然後重建專案。 這應該可以解決錯誤。

步驟 3:將 COM 啟用器新增至應用程式的資訊清單

Important

如果您的應用程式未封裝 (也就是在執行階段缺少套件身分識別),請跳至 步驟 4:在應用程式啟動時註冊並回應推播通知

如果您的應用程式已封裝(包括使用外部位置進行封裝):請開啟您的 Package.appxmanifest。 在 <Application> 元素內新增下列內容。 將 IdExecutableDisplayName 的值替換為適合您的應用程式的特定值。

<!--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>    

Note

您可以在 步驟 5 之後找到此範例已完成的 C++ 類別範例。 步驟 4 和 5 提供了在最終範例中新增每個部分的逐步指導。

步驟 4:註冊並回應應用程式啟動時的推播通知

更新您的應用程式的 main() 方法,以添加以下內容:

  1. 呼叫 PushNotificationManager::Default().Register()註冊您的應用程式以接收推送通知。
  2. 呼叫 AppInstance::GetCurrent()來檢查啟用要求的來源。GetActivatedEventArgs(). 如果推播通知觸發了啟用,請根據通知的有效載荷進行回應。

Important

您必須在呼叫 AppInstance.GetCurrent.GetActivatedEventArgs之前,先呼叫 PushNotificationManager::Default().Register

新增前景事件處理常式

若要在前景中處理事件,請註冊 PushNotificationManager.PushReceived 的處理常式。

Important

您也必須先註冊任何 PushNotificationManager.PushReceived 事件處理常式,才能呼叫 PushNotificationManager.Register()。 否則,將會擲回下列執行階段例外狀況:

System.Runtime.InteropServices.COMException: Element not found. Must register event handlers before calling Register().

新增 PushNotificationManager::IsSupported() 檢查

接下來,新增檢查 PushNotificationManager.IsSupported() 是否支援 PushNotification API。 如果沒有,建議您使用輪詢或您自己的自訂通訊端實作。

現在已確認推播通知支援,請根據 PushNotificationReceivedEventArgs 新增行為。

步驟 5:要求 WNS 通道 URI 並向 WNS 伺服器註冊

WNS 通道 URI 是用來傳送推播通知的 HTTP 端點。 每個客戶端都必須要求通道 URI,並向 WNS 伺服器註冊以接收推播通知。

Note

WNS 通道 URI 會在 30 天后到期。

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

如果您遵循教學課程程式碼,請在此處新增您的 Azure 物件識別碼:

// To obtain an AAD RemoteIdentifier for your app,
// follow the instructions on https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app
winrt::guid remoteId{ "00000000-0000-0000-0000-000000000000" }; // Replace this with your own Azure ObjectId

PushNotificationManager 會嘗試建立通道 URI,且重試時間不超過 15 分鐘。 建立事件處理程式以等候呼叫完成。 呼叫完成後,如果成功,請向WNS 伺服器註冊 URI。

範例程式碼

#include <iostream>
#include <winrt/Microsoft.Windows.PushNotifications.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Microsoft.Windows.AppLifecycle.h>
#include <winrt/Windows.ApplicationModel.Background.h>
#include <wil/cppwinrt.h>
#include <wil/result.h>

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

// To obtain an AAD RemoteIdentifier for your app,
// follow the instructions on https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app
winrt::guid remoteId{ "00000000-0000-0000-0000-000000000000" }; // Replace this with your own Azure ObjectId

winrt::Windows::Foundation::IAsyncOperation<PushNotificationChannel> RequestChannelAsync()
{
    auto channelOperation = PushNotificationManager::Default().CreateChannelAsync(remoteId);

    // Set up the in-progress 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;
}

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

    std::cout << "Push notification foreground event handler registered." << std::endl;
}

int main()
{
    // Set up an event handler, so we can receive notifications in the foreground while the app is running.
    // You must register notification event handlers before calling Register(). Otherwise, the following runtime
    // exception will be thrown: System.Runtime.InteropServices.COMException: 'Element not found. Must register
    // event handlers before calling Register().'
    SubscribeForegroundEventHandler();

    // Register the app for push notifications.
    PushNotificationManager::Default().Register();

    auto args{ AppInstance::GetCurrent().GetActivatedEventArgs() };
    switch (args.Kind())
    {
        case ExtendedActivationKind::Launch:
        {
            std::cout << "App launched by user or from the debugger." << std::endl;
            if (PushNotificationManager::IsSupported())
            {
                std::cout << "Push notifications are supported on this device." << std::endl;

                // 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
            {
                std::cout << "Push notifications are NOT supported on this device." << std::endl;
                std::cout << "App implements its own custom socket here to receive messages from the cloud since Push APIs are unsupported." << std::endl;
                std::cin.ignore();
            }
        }
        break;

        case ExtendedActivationKind::Push:
        {
            std::cout << "App activated via push notification." << std::endl;
            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;
    }
}

步驟 6:建置和安裝應用程式

使用 Visual Studio 建置並安裝您的應用程式。 以滑鼠右鍵按一下 [方案總管] 中的解決方案檔案,然後選取 [部署]。 Visual Studio 會建置您的應用程式,並將其安裝在您的電腦上。 您可以透過 [開始] 功能表或 Visual Studio 除錯程式啟動應用程式來執行應用程式。

教學課程程式碼的主控台將如下所示:

工作範例主控台

您需要權杖才能 將推播通知傳送至您的應用程式

將推播通知傳送至您的應用程式

此時,所有設定都已完成,且 WNS 伺服器可以將推播通知傳送至用戶端應用程式。 在下列步驟中,您可以參考 推播通知伺服器要求和響應標頭 以取得更多詳細資訊。

步驟 1:要求存取令牌

若要傳送推播通知,WNS 伺服器必須先要求存取令牌。 使用您的 Azure TenantId、Azure AppId 和秘密傳送 HTTP POST 要求。 如需擷取 Azure 租戶 ID 和 Azure 應用程式 ID 的相關資訊,請參閱 取得登入用的租戶 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 App SDK 中的應用程式通知。 應用程式通知可以是推播(從雲端傳送)或從本機傳送。 傳送雲端來源應用程式通知與在步驟 2 中傳送原始通知類似,不過 X-WNS-Type 標頭為 toast內容類型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);

Resources