快速入門:Windows 應用程式 SDK 中的推播通知

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

必要條件

範例應用程式

本快速入門會逐步說明如何在應用程式加入推播通知的支援功能。 請在範例應用程式的脈絡中參閱本快速入門從 GitHub 取得的的範例程式碼。

API 參考

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

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

Windows 應用程式 SDK 的推播通知會使用 Azure Active Directory (AAD) 的身分識別。 請求 WNS 通道 URI 以及請求傳送推播通知所需的存取權杖時,Azure 憑證為必要條件。 注意:我們支援將 Windows 應用程式 SDK 推播通知與 Microsoft 合作夥伴中心搭配使用。

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

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

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

  1. 提供應用程式名稱。

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

    1. 如需租用戶的詳細相關資訊,請參閱誰可登入您的應用程式?
  3. 選取 [註冊]

  4. 記下應用程式 (用戶端) ID,這就是您的 Azure AppId,您在處理啟動註冊與存取權杖請求時會用到它。

  5. 記下目錄 (租用戶) ID,這就是您的 Azure TenantId,您在請求存取權杖時會用到它。

    重要

    AAD App Registration Tenant記下應用程式 (用戶端) ID目錄 (租用戶) ID

  6. 記下物件 ID,這就是您的 Azure ObjectId,您在請求管道申請時會用到它。 請注意,此 ID 並非 Essentials 頁面列出的物件 ID。 若要找到正確的物件 ID,請在 Essentials 頁面的「本機目錄的受控應用程式」欄位按一下應用程式名稱:

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

    Screenshot showing the Object ID field

    注意

    如果您的應用程式沒有關聯物件 ID,那麼為了取得它,您需要服務主體,請按照以下任一篇文章的說明,在 Azure 入口網站或使用命令列建立服務主體:

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

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

步驟 3:建立應用程式註冊的密碼

請求傳送推播通知所需的存取權杖時,您必須配合 Azure AppId/ClientId 使用密碼。

AAD App Secret

請前往 [憑證] & [密碼],選取 [新增用戶端密碼]

重要

建立密碼後,請務必複製密碼並存放在安全位置,例如 Azure 金鑰保存庫。 密碼在建立後只能查看一次。

步驟 4:將應用程式的套件系列名稱對應到 Azure AppId

重要

Windows 推播通知服務 (WNS) 已經與 Azure 入口網站整合。 新的註冊體驗已提供預覽版。 如果您使用封裝應用程式 (包含具有外部位置的封裝),您可以透過此流程來對應應用程式的套件系列名稱 (PFN) 和 Azure AppId。

如果您的應用程式是封裝的 Win32 應用程式,請傳送電子郵件到 Win_App_SDK_Push@microsoft.com 請求存取新的 Azure 入口網站預覽版體驗,主旨行請寫「Windows 應用程式 SDK 推播通知請求」,內文則為「Azure 訂閱:(您的 Azure 訂閱 ID)」。 我們會每週處理一次請求。 對應請求處理完畢後,您會收到通知。

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

步驟 1:新增命名空間宣告

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

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

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

步驟 2:將 COM 啟動器新增到應用程式的資訊清單

重要

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

如果您的應用程式已封裝 (含有外部位置的封裝):請開啟 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>    

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

更新應用程式的 main() 方法,以便新增下列項目:

  1. 呼叫 PushNotificationManager::Default().Register(),藉此註冊應用程式以接收推播通知。
  2. 呼叫 AppInstance::GetCurrent().GetActivatedEventArgs(),藉此確認啟動請求的來源。 如果推播通知觸發啟動,則根據通知的承載來回應。

重要

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

以下範例來自從 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 分鐘的循環自動重新嘗試。 建立事件處理常式,等候呼叫完成。 呼叫完成時,如果呼叫成功,請將 URI 註冊到 WNS 伺服器。

// 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 伺服器首先需要請求存取權杖。 傳送 HTTP POST 請求,內含 Azure TenantId、Azure AppId 和密碼。 如需了解如何擷取 Azure TenantId 和 Azure AppId,請參閱取得登入需要的租用戶和應用程式 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 標題改成 toastContent-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);

資源