重要
本主题适用于使用 .NET Native 的通用 Windows 平台(UWP)应用。 Visual Studio 2022 现在还包括使用 .NET 9 的 UWP 项目模板。 对于本主题,必须使用名称中包含“.NET Native”的 UWP 项目模板,例如 UWP 空白应用(.NET Native)。 使用 .NET 9 的 UWP 项目尚未使用本主题进行测试,可能无法按预期工作。
应用服务是向其他 UWP 应用提供服务的 UWP 应用。 它们类似于设备上的 Web 服务。 应用服务作为主机应用中的后台任务运行,并且可以将其服务提供给其他应用。 例如,应用服务可能提供其他应用可以使用的条形码扫描程序服务。 或者,企业应用套件有一个常见的拼写检查应用服务,可用于套件中的其他应用。 应用服务允许你创建应用可以在同一设备上调用的无用户界面的服务,并且从 Windows 10 版本 1607 开始,还可以从远程设备调用这些服务。
从 Windows 10 版本 1607 开始,可以创建与主机应用在同一进程中运行的应用服务。 本文重点介绍如何创建和使用在单独的后台进程中运行的应用服务。 有关在提供程序所在的进程中运行应用服务的更多详细信息,请参阅 转换应用服务以在其主机应用 在同一进程中运行。
创建新的应用服务提供程序项目
在本操作指南中,我们将在一个解决方案中创建所有内容,为了简化操作。
- 在 Visual Studio 2022 或更高版本中,创建新的 UWP 应用项目并将其命名为 AppServiceProvider。
- 选择 文件 > 新建 > 项目...
- 在“创建新项目”对话框中,选择“UWP 空白应用”(.NET Native)。 请务必选择 C# 项目类型。 这是使应用服务可供其他 UWP 应用使用的应用。
- 单击“ 下一步”,然后将项目命名为 AppServiceProvider,为其选择一个位置,然后单击“ 创建”。
- 当系统要求为项目选择 目标 和 最低版本 时,请选择至少 10.0.14393。 如果要使用新的 SupportsMultipleInstances 属性,则必须面向 10.0.15063 (Windows 10 创作者更新)或更高版本。
将应用服务扩展添加到 Package.appxmanifest
在 AppServiceProvider 项目中,在文本编辑器中打开 Package.appxmanifest 文件:
- 在解决方案资源管理器中右键单击它。
- 选择 打开。
- 选择 XML(文本)编辑器。
在 AppService
元素中添加以下 <Application>
扩展。 此示例宣传com.microsoft.inventory
服务,并将此应用程序标识为应用服务提供商。 实际服务将作为后台任务实现。 应用服务项目向其他应用公开该服务。 建议对服务名称使用反向域名样式。
请注意,仅当你面向 Windows SDK 版本 10.0.15063 或更高版本时,xmlns:uap4
命名空间前缀和 uap4:SupportsMultipleInstances
属性才有效。 如果面向较旧的 SDK 版本,则可以安全地删除它们。
注释
有关 C++/WinRT 和 C# 中的应用服务示例应用,请参阅 应用服务示例应用。
<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 创作者更新)或更高版本,则可以使用。 它还应以 uap4
命名空间为前缀。
创建应用程序服务
在本部分中,我们将创建作为后台任务运行的应用服务。 应用服务将提供一个简单的库存服务,允许其他应用查询库存中项的名称和价格。
应用服务可以作为后台任务实现。 这样,前台应用程序就可以在另一个应用程序中调用应用服务。 若要将应用服务创建为后台任务,请将新的 Windows 运行时组件项目添加到名为 > 的解决方案(文件>添加新项目)。 在“添加新项目”对话框中,选择“Windows 运行时组件”(.NET Native)。
在 AppServiceProvider 项目中,添加对新 MyAppService 项目的项目引用(在 解决方案资源管理器中,右键单击 AppServiceProvider 项目 >添加>引用>项目>解决方案,选择 MyAppService>OK)。 此步骤至关重要,因为如果不添加引用,应用服务将不会在运行时连接。
在
MyAppService 项目中,使用 语句将以下添加到 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(); } } }
此类是应用服务将执行其工作的地方。
创建后台任务时,将调用 运行。 由于后台任务在 运行 完成后终止,因此代码会发出延迟,以便后台任务能够继续处理请求。 作为后台任务实现的应用服务在收到调用后将保持活动状态约 30 秒,除非该时间范围内再次调用该应用服务或延迟。如果在与调用方相同的进程中实现应用服务,则应用服务的生存期与调用方生存期相关联。
应用服务的生存期取决于调用方:
- 如果调用方位于前台,则应用服务生存期与调用方相同。
- 如果调用方处于后台,则应用服务将获取 30 秒的运行时间。 使用推迟额外增加5秒时间。
当任务被取消时,将调用 OnTaskCanceled。 当客户端应用释放 AppServiceConnection时、客户端应用被挂起时、操作系统关闭时、操作系统进入睡眠状态时,或者操作系统耗尽资源无法运行任务时,将取消该任务。
编写应用服务的代码
OnRequestReceived 是应用服务的代码所在的位置。 将 MyAppServiceInventory.cs 中的存根 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 枚举将返回到调用方,以指示对应用服务的调用是成功还是失败。 例如,如果 OS 中止服务终结点,因为它的资源已超出,因此对应用服务的调用可能会失败。 可以通过 ValueSet 返回其他错误信息。 在此示例中,我们使用名为 Status
的键将更详细的错误信息返回给调用方。
对 SendResponseAsync 的调用将 ValueSet 返回到调用方。
部署服务应用并获取包系列名称
必须先部署应用服务提供商,然后才能从客户端调用它。 可以通过在 Visual Studio 中选择 构建 > 部署解决方案 来部署它。
您还需要应用服务提供商的包源名称,以便调用该服务。 可以按照以下步骤获取它:
- 在设计器视图中打开 AppServiceProvider 项目的 Package.appxmanifest 文件(在 解决方案资源管理器中双击它)。
- 选择 包装 选项卡,复制 包系列名称旁边的值,并将其粘贴到记事本这样的地方。
编写客户端以调用应用服务
在本部分中,我们将创建一个客户端应用,用于调用刚刚创建的应用服务。 客户端应用将是具有文本框和按钮的简单 UWP 应用。 当用户在文本框中输入索引并单击该按钮时,应用将调用应用服务以获取该索引处库存项的名称和价格。
将一个新的空白 Windows 通用应用程序项目添加到解决方案中,使用 文件 > 添加 > 新项目。 在“ 添加新项目 ”对话框中,选择 “UWP 空白应用”(.NET Native) 并将其命名为 ClientApp。
在
ClientApp 项目中,使用 语句将以下添加到 MainPage.xaml.cs 顶部:using Windows.ApplicationModel.AppService;
将主页上的 网格 更改为 StackPanel ,以便我们可以向其添加文本框和按钮。
将名为 TextBox 的 TextBox 和按钮添加到 MainPage.xaml。
添加一个 按钮 ,其中包含名为 “button_Click ”的单击事件处理程序和内容(如“单击我”)的某些文本。
将 async 关键字添加到按钮处理程序的签名 MainPage.xaml.cs。
将按钮单击处理程序的存根替换为以下代码。 请务必在
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 项目的包系列名称。注释
请确保粘贴字符串文本,而不是将其放入变量中。 如果使用变量,则不起作用。
代码首先与应用服务建立连接。 在释放
this.inventoryService
之前,连接将保持打开状态。 应用服务名称必须与您添加到AppService
项目的Name
文件中的 元素的 属性相匹配。 在此示例中,是<uap3:AppService Name="com.microsoft.inventory"/>
。创建了一个名为 的
message
,用于指定我们想要发送到应用服务的命令。 示例应用服务需要一个命令来指示要采取的两项作中的哪一项。 我们从客户端应用中的文本框中获取索引,然后使用命令调用服务Item
以获取项的说明。 然后,我们通过Price
命令来获取项目的价格。 按钮的文本被设置为结果。由于 AppServiceResponseStatus 仅指示作系统是否能够将调用连接到应用服务,因此我们检查
Status
从应用服务收到的 ValueSet 中的密钥,以确保它能够满足请求。将 ClientApp 项目设置为启动项目(右键单击它在解决方案资源管理器>设置为启动项目),然后运行解决方案。 在文本框中输入数字 1,然后单击按钮。 应从服务中获取“Chair : Price = 88.99”。
解决常见问题
如果应用服务调用失败,请在 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,然后按按钮。 调试器将在应用服务中的断点上停止应用服务调用。
调试客户端
若要调试调用应用服务的客户端应用,需要将调试器附加到客户端应用进程。 执行以下步骤:
- 按照前面的步骤中的说明调试调用应用服务的客户端。
- 从“开始”菜单启动 ClientApp 。
- 将调试器附加到 ClientApp.exe 进程(而不是 ApplicationFrameHost.exe 进程)。 (在 Visual Studio 中,选择 调试 > 附加到进程...。)
- 在 ClientApp 项目中,在 button_Click中设置断点。
- 当您将数字 1 输入到 ClientApp 文本框并单击按钮时,客户端和应用服务中的断点都会被触发。
常规应用服务故障排除
如果在尝试连接到应用服务后遇到 AppUnavailable 状态,请检查以下内容:
- 确保部署应用服务提供商项目和应用服务项目。 两者都需要在运行客户端之前部署,否则客户端将没有任何可连接的对象。 可以从 Visual Studio 使用 生成>部署解决方案进行部署。
- 在 解决方案资源管理器中,确保您的应用服务供应商项目对实现应用服务的项目具有项目间引用。
- 验证是否已将
<Extensions>
项及其子元素添加到属于应用服务提供程序项目的 Package.appxmanifest 文件中,具体步骤如上所述在 向 Package.appxmanifest添加应用服务扩展中。 - 确保客户端中调用应用服务提供程序的 AppServiceConnection.AppServiceName 字符串与应用服务提供商项目的
<uap3:AppService Name="..." />
文件中指定的字符串匹配。 - 确保 AppServiceConnection.PackageFamilyName 与上面指定的应用服务提供程序组件的包系列名称匹配,将应用服务扩展添加到 Package.appxmanifest
- 对于进程外应用服务,例如本示例中的服务,确保应用服务提供程序项目的
EntryPoint
文件中<uap:Extension ...>
元素中指定的 与您的应用服务项目中实现 IBackgroundTask 的公共类的命名空间和类名匹配。
调试疑难解答
如果调试器未在应用服务提供商或应用服务项目中的断点处停止,请检查以下各项:
- 确保部署应用服务提供商项目和应用服务项目。 两者都需要在运行客户端之前进行部署。 可以通过从 Visual Studio 使用 生成>部署解决方案来部署它们。
- 确保要调试的项目设置为启动项目,并且当按下 F5 时,该项目的调试属性设置为不运行项目。 右键单击项目,然后单击“ 属性”,然后单击“ 调试 ”(或C++中的 调试 )。 在 C# 中,将 “开始”操作 更改为 “不启动,但在启动时调试我的代码”。 在C++中,将 启动应用程序 设置为 “无”。
注解
此示例介绍了如何创建作为后台任务运行的应用服务,并从另一个应用调用它。 要注意的要点包括:
- 创建用于托管应用服务的后台任务。
- 将
windows.appService
扩展添加到应用服务提供商的 Package.appxmanifest 文件。 - 获取应用服务提供商的包系列名称,以便我们可以从客户端应用连接到它。
- 将应用服务提供商项目中的项目引用添加到应用服务项目中。
- 使用 Windows.ApplicationModel.AppService.AppServiceConnection 调用该服务。
MyAppService 的完整代码
下面是 MyAppService 项目的完整代码,该项目将应用服务实现为后台任务。 此代码应放置在 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();
}
}
}
}