備註
本主題中的程式代碼清單僅限 C#。 如需 C++/WinRT 和 C# 中的 App Service 範例應用程式,請參閱 App Services 範例,或取得 GitHub 上的 App Services 範例專案來源。
這很重要
本主題適用於使用 .NET Native 的通用 Windows 平臺 (UWP) 應用程式。 Visual Studio 2022 現在也包含使用 .NET 9 的 UWP 專案範本。 針對本主題,您必須使用名稱中包含 「.NET Native」 的 UWP 專案範本,例如 UWP 空白應用程式 (.NET Native) 。 使用 .NET 9 的 UWP 專案尚未使用本主題進行測試,可能無法如預期般運作。
應用程式服務是向其他 UWP 應用程式提供服務的 UWP 應用程式。 它們類似於裝置上的 Web 服務。 應用程式服務會在主應用程式中以背景工作的形式執行,並可將其服務提供給其他應用程式。 例如,應用程式服務可能會提供其他應用程式可以使用的條碼掃描器服務。 或者,企業應用程式套件有常見的拼字檢查應用程式服務,可供套件中的其他應用程式使用。 應用程式服務可讓您建立應用程式可以在相同裝置上呼叫的無UI服務,並從遠端裝置上的Windows 10版本1607開始。
從 Windows 10 版本 1607 開始,您可以建立與主應用程式相同進程中執行的應用程式服務。 本文著重於建立及取用在個別背景進程中執行的應用程式服務。 如需在與提供者相同的進程中執行應用程式服務的詳細資訊,請參閱 將應用程式服務轉換成與其主應用程式相同進程中運行。
建立新的應用程式服務提供者專案
在此操作說明中,我們會在一個解決方案中建立所有專案,以簡化。
- 在 Visual Studio 2022 或更新版本中,建立新的 UWP 應用程式專案,並將它命名為 AppServiceProvider。
- 選擇 [檔案] > [新增 > 專案...]
- 在 [ 建立新專案] 對話框中,選取 [UWP 空白應用程式] (.NET Native) 。 請務必選取 C# 項目類型。 這會是讓 App Service 可供其他 UWP app 使用的應用程式。
- 按 [下一步],然後將專案命名為 AppServiceProvider、為其選擇位置,然後按兩下 [ 建立]。
- 當系統要求為專案選取目標和最低版本時,請至少選取10.0.14393。 如果您想要使用新的 SupportsMultipleInstances 屬性,您必須以 10.0.15063 (Windows 10 Creators Update) 或更新版本為目標。
將 App Service 擴充功能新增至 Package.appxmanifest
在 AppServiceProvider 專案中,在文本編輯器中開啟 Package.appxmanifest 檔案:
- 在 [方案總管]中,以滑鼠右鍵點擊
。 - 選取「」並使用「」開啟。
- 選擇 XML(文字)編輯器。
在AppService
元素內新增以下<Application>
擴展。 此範例宣傳 com.microsoft.inventory
服務,並識別此應用程式為應用程式服務提供者。 實際服務將會作為背景任務實施。 App Service 專案會將服務公開給其他應用程式。 我們建議針對服務名稱使用反向域名樣式。
請注意, xmlns:uap4
只有在以 Windows SDK 10.0.15063 版或更新版本為目標時,命名空間前置詞和 uap4:SupportsMultipleInstances
屬性才有效。 如果您是以舊版 SDK 為目標,則可以安全地移除它們。
備註
如需 C++/WinRT 和 C# 中的 App Service 範例應用程式,請參閱 App Service 範例應用程式。
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4"
...
<Applications>
<Application Id="AppServiceProvider.App"
Executable="$targetnametoken$.exe"
EntryPoint="AppServiceProvider.App">
...
<Extensions>
<uap:Extension Category="windows.appService" EntryPoint="MyAppService.Inventory">
<uap3:AppService Name="com.microsoft.inventory" uap4:SupportsMultipleInstances="true"/>
</uap:Extension>
</Extensions>
...
</Application>
</Applications>
屬性會將 Category
此應用程式識別為應用程式服務提供者,而 EntryPoint
屬性會識別實作服務的命名空間限定類別。 接下來我們將實作此作業。
屬性 SupportsMultipleInstances
表示每次呼叫應用程式服務時,都應該在新的進程中執行。 這並非必要專案,但如果您需要該功能,且以 10.0.15063 SDK (Windows 10 Creators Update) 或更新版本為目標,則可供您使用。 它也應該以 uap4
命名空間開頭。
建立應用程式服務
在本節中,我們將建立以背景工作身分執行的應用程式服務。 App Service 會提供簡單的物品清單服務,允許其他軟體應用查詢清單中項目的名稱和價格。
應用程式服務可以實作為背景工作。 這可讓前景應用程式在另一個應用程式中叫用應用程式服務。 若要建立應用程式服務作為背景工作,請將新的 Windows 執行階段元件專案新增至方案(檔案 > 新增 > 新專案),命名為 MyAppService。 在 [新增專案] 對話框中,選擇 [Windows 運行時間元件][.NET 原生]。
在 AppServiceProvider 專案中,將項目參考新增至新的 MyAppService 專案(在 方案總管中,以滑鼠右鍵按兩下 AppServiceProvider 專案 >[新增>參考>專案>方案],選取 [MyAppService>確定]。 此步驟很重要,因為如果您未新增參考,應用服務將不會在執行期間連線。
在 MyAppService 專案中,將下列 using 語句新增至 Class1.cs頂端:
using Windows.ApplicationModel.AppService; using Windows.ApplicationModel.Background; using Windows.Foundation.Collections;
將 Class1.cs 重新命名為 Inventory.cs,並將 Class1 的存根程式代碼取代為名為 Inventory 的新背景工作類別:
public sealed class Inventory : IBackgroundTask { private BackgroundTaskDeferral backgroundTaskDeferral; private AppServiceConnection appServiceconnection; private String[] inventoryItems = new string[] { "Robot vacuum", "Chair" }; private double[] inventoryPrices = new double[] { 129.99, 88.99 }; public void Run(IBackgroundTaskInstance taskInstance) { // Get a deferral so that the service isn't terminated. this.backgroundTaskDeferral = taskInstance.GetDeferral(); // Associate a cancellation handler with the background task. taskInstance.Canceled += OnTaskCanceled; // Retrieve the app service connection and set up a listener for incoming app service requests. var details = taskInstance.TriggerDetails as AppServiceTriggerDetails; appServiceconnection = details.AppServiceConnection; appServiceconnection.RequestReceived += OnRequestReceived; } private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { // This function is called when the app service receives a request. } private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { if (this.backgroundTaskDeferral != null) { // Complete the service deferral. this.backgroundTaskDeferral.Complete(); } } }
這個類別是 App Service 將執行其工作的地方。
建立背景任務時,會呼叫 執行。 因為背景工作會在 執行 完成後終止,所以程式碼使用延遲來保持背景工作的運行,以便繼續處理請求。 實作為背景工作的應用程式服務會在收到呼叫后保持運作約 30 秒,除非在該時間範圍內再次呼叫,否則延遲就會被取出。如果應用程式服務在與呼叫端相同的進程中實作,則應用程式服務的存留期會系結至呼叫端的存留期。
App Service 的存留期取決於呼叫端:
- 如果呼叫端位於前景,應用程式服務存留期會與呼叫端相同。
- 如果呼叫者位於背景中,應用程式服務會有 30 秒的執行時間。 選用延期方案會另外提供一次 5 秒的延長時間。
取消工作時會呼叫 OnTaskCanceled。 當用戶端應用程式處置 AppServiceConnection、用戶端應用程式被暫停、操作系統關閉或進入睡眠模式,或操作系統資源耗盡無法執行該工作時,該工作將被取消。
撰寫應用程式服務的程序代碼
OnRequestReceived 是應用程式服務程式碼所在的位置。 以本範例中的代碼取代 MyAppService的 Inventory.cs 中的 stub OnRequestReceived。 此程式代碼會取得清查專案的索引,並將它連同命令字串傳遞給服務,以擷取指定清查專案的名稱和價格。 針對您自己的專案,新增錯誤處理程序代碼。
private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
// Get a deferral because we use an awaitable API below to respond to the message
// and we don't want this call to get canceled while we are waiting.
var messageDeferral = args.GetDeferral();
ValueSet message = args.Request.Message;
ValueSet returnData = new ValueSet();
string command = message["Command"] as string;
int? inventoryIndex = message["ID"] as int?;
if (inventoryIndex.HasValue &&
inventoryIndex.Value >= 0 &&
inventoryIndex.Value < inventoryItems.GetLength(0))
{
switch (command)
{
case "Price":
{
returnData.Add("Result", inventoryPrices[inventoryIndex.Value]);
returnData.Add("Status", "OK");
break;
}
case "Item":
{
returnData.Add("Result", inventoryItems[inventoryIndex.Value]);
returnData.Add("Status", "OK");
break;
}
default:
{
returnData.Add("Status", "Fail: unknown command");
break;
}
}
}
else
{
returnData.Add("Status", "Fail: Index out of range");
}
try
{
// Return the data to the caller.
await args.Request.SendResponseAsync(returnData);
}
catch (Exception e)
{
// Your exception handling code here.
}
finally
{
// Complete the deferral so that the platform knows that we're done responding to the app service call.
// Note for error handling: this must be called even if SendResponseAsync() throws an exception.
messageDeferral.Complete();
}
}
請注意,OnRequestReceived 是 非同步,因為在此範例中我們呼叫了 SendResponseAsync 的可等待方法。
會採用延遲,讓服務可以在 OnRequestReceived 處理程式中使用 異步 方法。 它確保呼叫 OnRequestReceived 直到處理訊息完成才會結束。 SendResponseAsync 會將結果傳送給呼叫端。 SendResponseAsync 不會發出呼叫完成的訊號。 當延遲完成時,這代表 SendMessageAsync 已接收到 OnRequestReceived 完成的信號。 SendResponseAsync 的呼叫會包裝在 try/finally 區塊中,因為即使 SendResponseAsync 擲回例外狀況,您仍必須完成延遲作業。
應用程式服務會使用 ValueSet 物件來交換資訊。 您傳遞的數據大小只會受限於系統資源。
ValueSet中沒有預先定義的鍵可供您使用。 您必須確定您將用來定義應用服務通訊協定的關鍵值。 呼叫端必須以該通訊協定為基礎進行開發。 在此範例中,我們選擇了一個名為 Command
的鍵值,其值指出我們是否希望應用服務提供庫存項目的名稱或價格。 庫存名稱的索引儲存在 ID
鍵底下。 傳回值會儲存在 Result
索引鍵底下。
AppServiceClosedStatus 列舉會傳回給呼叫者,以表明對 App Service 的呼叫是成功還是失敗。 如果作業系統因為資源超出限制而中止服務端點,這可能會導致對應用服務的呼叫失敗。 您可以透過 ValueSet 傳回其他錯誤資訊。 在此範例中,我們使用名為 Status
的索引鍵,將更詳細的錯誤資訊傳回給呼叫端。
呼叫 SendResponseAsync 會將 ValueSet 傳回給呼叫端。
部署服務應用程式並取得套件系列名稱
您必須先部署應用程式服務提供者,才能從用戶端呼叫它。 您可以在 Visual Studio 中選取 建置、> 部署解決方案 來部署它。
您也需要應用程式服務提供者的套件系列名稱,才能呼叫它。 您可以遵循下列步驟來取得它:
- 在設計工具檢視中開啟 AppServiceProvider 專案的 Package.appxmanifest 檔案(在 方案總管中按兩下它)。
- 選擇 [包裝] 標籤,複製 封裝系列名稱旁的值,然後貼到 [記事本] 之類的地方。
撰寫用戶端以呼叫應用程式服務
在本節中,我們將建立用戶端應用程式,以呼叫我們剛才建立的應用程式服務。 用戶端應用程式將是具有文本框和按鈕的簡單 UWP 應用程式。 當使用者在文字框中輸入索引並按下按鈕時,應用程式會呼叫 App Service,以取得該索引處庫存專案的名稱和價格。
使用 [檔案] > [新增 > 新增專案],將新的空白 Windows 通用應用程式專案新增至方案。 在 [ 新增專案] 對話框中,選擇 [UWP 空白應用程式] [.NET Native] 並將其命名為 ClientApp。
在 ClientApp 專案中,將下列 using 語句新增至 MainPage.xaml.cs頂端:
using Windows.ApplicationModel.AppService;
將主頁面上的 Grid 變更為 StackPanel ,以便我們可以將文字框和按鈕新增至其中。
將名為 textBox 的 TextBox 和按鈕新增至 MainPage.xaml。
新增 Button ,其中包含名為 button_Click 的點擊事件處理程式,以及內容的某些文字,例如「點擊我」。
在 MainPage.xaml.cs 中,將 async 關鍵詞新增至按鈕處理程式的簽章。
以下列程式代碼取代按鈕點選處理程式的存根。 請務必在
inventoryService
類別中包含欄位宣告。private AppServiceConnection inventoryService; private async void button_Click(object sender, RoutedEventArgs e) { // Add the connection. if (this.inventoryService == null) { this.inventoryService = new AppServiceConnection(); // Here, we use the app service name defined in the app service // provider's Package.appxmanifest file in the <Extension> section. this.inventoryService.AppServiceName = "com.microsoft.inventory"; // Use Windows.ApplicationModel.Package.Current.Id.FamilyName // within the app service provider to get this value. this.inventoryService.PackageFamilyName = "Replace with the package family name"; var status = await this.inventoryService.OpenAsync(); if (status != AppServiceConnectionStatus.Success) { textBox.Text= "Failed to connect"; this.inventoryService = null; return; } } // Call the service. int idx = int.Parse(textBox.Text); var message = new ValueSet(); message.Add("Command", "Item"); message.Add("ID", idx); AppServiceResponse response = await this.inventoryService.SendMessageAsync(message); string result = ""; if (response.Status == AppServiceResponseStatus.Success) { // Get the data that the service sent to us. if (response.Message["Status"] as string == "OK") { result = response.Message["Result"] as string; } } message.Clear(); message.Add("Command", "Price"); message.Add("ID", idx); response = await this.inventoryService.SendMessageAsync(message); if (response.Status == AppServiceResponseStatus.Success) { // Get the data that the service sent to us. if (response.Message["Status"] as string == "OK") { result += " : Price = " + response.Message["Result"] as string; } } textBox.Text = result; }
將行
this.inventoryService.PackageFamilyName = "Replace with the package family name";
中的套件系列名稱取代為 AppServiceProvider 專案中的套件系列名稱,部署服務應用程式並取得套件系列名稱。備註
請務必貼上字串文字,而不是將它放入變數中。 如果您使用變數,它將無法運作。
程序代碼會先建立與 App Service 的連線。 在您處置
this.inventoryService
之前,連線會保持開啟。 應用程式服務名稱必須符合您新增至AppService
專案之Name
檔案中 元素的 屬性。 在這個範例中,它是<uap3:AppService Name="com.microsoft.inventory"/>
。我們建立了一個名為 的
message
,以指定我們想要傳送到 App Service 的命令。 範例 App Service 預期命令會指出要採取的兩個動作中的哪一個。 我們會從用戶端應用程式中的文字框取得索引,然後使用 命令呼叫服務Item
,以取得專案的描述。 然後,我們會使用Price
命令進行呼叫,以取得項目的價格。 按鈕文字會設定為結果。因為 AppServiceResponseStatus 只顯示操作系統是否能夠將呼叫連線到應用服務,因此我們會在從應用服務接收到的
Status
中檢查 鍵,以確保它能夠成功滿足請求。將 ClientApp 項目設定為啟始專案(以滑鼠右鍵按兩下 [方案總管]>[設定為啟始專案],然後執行方案。 在文字框中輸入數位 1,然後按下按鈕。 您應該從服務中取得 “Chair : Price = 88.99”。
解決常見問題
如果 App Service 呼叫失敗,請在 ClientApp 專案中檢查下列項目:
- 確認指派給清查服務連線的套件系列名稱符合 AppServiceProvider 應用程式的套件系列名稱。 使用 查看
this.inventoryService.PackageFamilyName = "...";
中的行。 - 在 button_Click中,確認指派給清查服務連線的應用服務名稱,是否與 AppServiceProvider的 Package.appxmanifest 檔案中的應用服務名稱一致。 請參閱:
this.inventoryService.AppServiceName = "com.microsoft.inventory";
。 - 確定已部署 AppServiceProvider 應用程式。 (在 方案總管中,以滑鼠右鍵按兩下方案,然後選擇 [ 部署方案]。
對應用程式服務進行偵錯
若要對應用程式服務進行偵錯,您必須設定解決方案,以便部署應用程式服務提供者,並從用戶端應用程式呼叫應用程式服務。 請遵循下列步驟:
- 請確定解決方案在偵錯之前已部署,因為必須先部署應用程式服務提供者應用程式,才能呼叫服務。 (在 Visual Studio 中,建置 > 部署解決方案)。
- 在 方案總管中,以滑鼠右鍵按兩下 AppServiceProvider 專案,然後選擇 [ 屬性]。 從 [偵錯] 索引標籤中,將 [開始動作] 變更為 [不要啟動,但在程序啟動時偵錯我的代碼]。 (請注意,如果您使用C++來實作應用程式服務提供者,請從 [偵錯] 索引標籤,將 [啟動應用程式] 變更為 [無]。
- 在 MyAppService 專案中的 Inventory.cs 檔案中,於 OnRequestReceived 中設定斷點。
- 將 AppServiceProvider 專案設定為啟始專案,然後按 F5。
- 從「開始」功能表啟動 ClientApp(而不是從 Visual Studio)。
- 在文字框中輸入數位 1,然後按按鈕。 調試程式將會在 App Service 的斷點上停止 App Service 呼叫。
對客戶端進行偵錯
若要對呼叫應用程式服務的用戶端應用程式進行偵錯,您必須將調試程式附加至用戶端應用程式進程。 請遵循下列步驟:
- 請遵循上一個步驟中的指示,對呼叫App Service的客戶端進行偵錯。
- 從 [開始] 選單啟動 ClientApp。
- 將調試程式附加至 ClientApp.exe 進程(而非 ApplicationFrameHost.exe 進程)。 (在 Visual Studio 中,選擇 [偵錯 > 附加至進程...]。
- 在 ClientApp 專案中,於 button_Click中設定斷點。
- 當您在 用戶端應用程式 文字框中輸入數字 1,然後按下按鈕時,用戶端和應用程式服務中的斷點現在都會被觸發。
一般應用程式服務疑難解答
如果您在嘗試連線到 App Service 之後遇到 AppUnavailable 狀態,請檢查下列事項:
- 確定已部署應用程式服務提供者專案和 App Service 專案。 這兩者都需要在執行客戶端之前部署,因為否則客戶端將無法連線到任何東西。 您可以從 Visual Studio 使用建置>部署方案來部署。
- 在 [方案總管]中,確保您的應用服務提供者專案已對實作應用服務的專案設置專案參考。
- 確認
<Extensions>
專案及其子元素已新增至屬於 app 服務提供者專案的 Package.appxmanifest 檔案,如上述所述,將 app Service 延伸模組新增至 Package.appxmanifest。 - 請確定用戶端中呼叫應用程式服務提供者的 AppServiceConnection.AppServiceName 字串符合
<uap3:AppService Name="..." />
應用程式服務提供者專案的 Package.appxmanifest 檔案中指定的 。 - 確定 AppServiceConnection.PackageFamilyName 與上述應用程式服務提供者元件的套件家族名稱相符,並在 中將應用程式服務擴充功能新增至 Package.appxmanifest。
- 針對此範例中的應用程式服務,請驗證您的應用程式服務提供者專案的
EntryPoint
文件中<uap:Extension ...>
元素所指定的 是否符合您應用程式服務專案中實作 IBackgroundTask 的公用類別的命名空間和類別名稱。
針對偵錯進行疑難解答
如果偵錯工具未在應用程式服務提供者或應用程式服務專案中的斷點中斷,請檢查下列專案:
- 確定已部署應用程式服務提供者專案和 App Service 專案。 執行用戶端之前,必須先部署這兩者。 您可以使用 組建>部署解決方案,從 Visual Studio 部署它們。
- 請確定您要偵錯的專案已設定為啟始專案,且該專案的偵錯屬性設定為在按下 F5 時不會執行專案。 以滑鼠右鍵按兩下項目,然後按下 [屬性]
,然後 [偵錯] (或C++ 中的 [偵錯 ]。 在 C# 中,將 [開始] 動作 變更為 [不要啟動,但在啟動程式代碼時偵錯我的程式代碼。 在 C++ 中,將 [啟動應用程式]設定為 [無 ]。
備註
此範例提供建立應用程式服務的簡介,此服務會以背景工作的形式執行,並從另一個應用程式呼叫它。 要注意的重點是:
- 建立背景任務來托管 App Service。
- 將
windows.appService
延伸模組新增至應用程式服務提供者的 Package.appxmanifest 檔案。 - 取得應用程式服務提供者的套件系列名稱,以便我們可以從用戶端應用程式連線到它。
- 將應用程式服務提供者專案的專案對專案參考新增至 App Service 專案。
- 使用 Windows.ApplicationModel.AppService.AppServiceConnection 呼叫服務。
MyAppService 的完整程序代碼
以下是 MyAppService 專案的完整程式代碼,其會將 App Service 實作為背景工作。 此程式代碼應該放在 MyAppService 專案的Inventory.cs檔案中。
using System;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;
namespace MyAppService
{
public sealed class Inventory : IBackgroundTask
{
private BackgroundTaskDeferral backgroundTaskDeferral;
private AppServiceConnection appServiceconnection;
private String[] inventoryItems = new string[] { "Robot vacuum", "Chair" };
private double[] inventoryPrices = new double[] { 129.99, 88.99 };
public void Run(IBackgroundTaskInstance taskInstance)
{
// Get a deferral so that the service isn't terminated.
this.backgroundTaskDeferral = taskInstance.GetDeferral();
// Associate a cancellation handler with the background task.
taskInstance.Canceled += OnTaskCanceled;
// Retrieve the app service connection and set up a listener for incoming app service requests.
var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
appServiceconnection = details.AppServiceConnection;
appServiceconnection.RequestReceived += OnRequestReceived;
}
private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
// Get a deferral because we use an awaitable API below to respond to the message
// and we don't want this call to get canceled while we are waiting.
var messageDeferral = args.GetDeferral();
ValueSet message = args.Request.Message;
ValueSet returnData = new ValueSet();
string command = message["Command"] as string;
int? inventoryIndex = message["ID"] as int?;
if (inventoryIndex.HasValue &&
inventoryIndex.Value >= 0 &&
inventoryIndex.Value < inventoryItems.GetLength(0))
{
switch (command)
{
case "Price":
{
returnData.Add("Result", inventoryPrices[inventoryIndex.Value]);
returnData.Add("Status", "OK");
break;
}
case "Item":
{
returnData.Add("Result", inventoryItems[inventoryIndex.Value]);
returnData.Add("Status", "OK");
break;
}
default:
{
returnData.Add("Status", "Fail: unknown command");
break;
}
}
}
else
{
returnData.Add("Status", "Fail: Index out of range");
}
// Return the data to the caller.
await args.Request.SendResponseAsync(returnData);
// Complete the deferral so that the platform knows that we're done responding to the app service call.
// Note for error handling: this must be called even if SendResponseAsync() throws an exception.
messageDeferral.Complete();
}
private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
if (this.backgroundTaskDeferral != null)
{
// Complete the service deferral.
this.backgroundTaskDeferral.Complete();
}
}
}
}