活力无限(第 2 部分):针对动态磁贴编写和调试服务

本博文系列的第 1 部分中我们探讨了为 Windows 8 的整体用户体验注入“活力”的磁贴更新、徽章更新和 toast 通知的本质。特别是,我们了解了这些通知如何与以下 XML 负载进行组合:稍后将通过正在运行的应用或后台任务在本地发布,或者可以应在线服务的请求进行提供。

使用 Visual Studio Express 2012 或适用于 Windows 8 的 Visual Studio Ultimate 2012,生成 XML 负载并通过正在运行的应用对其进行发布并非难事,可以在应用中轻松完成开发和调试。应用磁贴和徽章示例辅助磁贴示例以及计划通知示例向您展示了整个过程。

开发和调试 Web 服务以支持定期更新以及推送通知所需的工作略多一些。推送与定期通知客户端示例中出色地展示了客户端的这些情形,但是要充分利用该示例,您需要结合使用一些服务!在本篇博文中,我们特别介绍了如何开发支持针对磁贴和徽章更新的定期通知的服务,重点讲解在将这些服务部署到生产环境之前使用 Visual Studio 工具和本地主机调试服务。我们还将着手探索如何使用 Windows Azure 移动服务实现上述目的,这样做对于支持推送通知也会非常有帮助,我们将在第 3 部分中对此进行介绍。

服务的本质

支持定期磁贴和徽章更新的服务实际起到了哪些作用?我们首先回顾一下我们所谓的“服务”的含义,这个词往往会令那些主要致力于客户端应用的开发人员心生畏惧。

用最简单的话来说,服务就是存储在某一 Web 服务器上的一段代码,该代码会应针对它的 HTTP 请求在该服务器上运行。HTML 页面(.htm 或 .html)的机制并非如此:这些页面不涉及服务器端代码,服务器仅返回页面的文本,所有处理过程都在客户端上完成(包括运行页面中包含的任何客户端脚本)。不过,如果您的页面 URI 的后缀为 php、asp、aspx、cgi、cshtml 或一些其他服务器端扩展名,您将有效地从常规意义上谈论“服务”。

服务负责接收客户端 HTTP 请求,处理 URI 中可能包含的任何参数并返回适当的文本响应。对于使用 PHP 或 ASP.NET 之类的技术编写的网页,这些响应应采用 HTML 形式。实施 Web API 的服务,如 Facebook 和 Twitter 中的服务(以及成千上万的其他服务!),通常接受 URI 查询字符串中(或请求标头中)任意数量的参数并在响应中返回 XML 或 JSON 数据。(注意,我们在此谈论的是构建于代表性状态传输(即 REST)之上的服务,而不是构建于其他协议(如 SOAP)之上的服务,原因是 REST 在当前的服务中最常见。)

因此,提供定期磁贴和/或徽章更新的服务只是存在于应用出于此目的向 Windows 提供的 URI 中的一种服务,使用适当的 XML 负载对 HTTP 请求作出响应。该负载包含与任何支持的模板相对应的元素,在其中可以使用其他 URI 引用图像(不支持内联编码)。负载中包含的特定信息还可以来自任何来源,我们将在稍后进行介绍。

然而,抛开这些细节,所有此类服务都共用类似的结构:接收和处理请求,然后构建 XML 响应。现在,我们来看一下如何创建这种基本结构。

编写服务

要编写和调试服务,您可以使用支持所选服务器端语言的任何工具。在此我们将重点关注 Visual Studio,特别是 Visual Studio Ultimate 2012 和 Visual Studio Express 2012 for Web,后者随其 Windows 伙伴(您已了解)一起免费提供。要进行安装,请运行 Web Platform Installer,通过此工具,您还可以安装各种其他相关的技术,例如 PHP 和 WebMatrix。这样一来您可以使用各种语言创建服务。

以下代码是一个非常简单的示例,包含一个完整的 PHP 服务,该服务用于响应徽章更新 XML 负载,其中徽章值设置为当前日期(注意,该日期应与服务器保持一致!)。这段代码出自免费电子书第 13 章使用 HTML、CSS 和 JavaScript 编程 Windows 8 应用中的 HelloTiles 示例站点:

 <?php
    echo '<?xml version="1.0" encoding="utf-8" ?>';
    echo "<badge value='".date("j")."'/>";
?>

可以直接试用此服务。单击 Windows Azure 网站上为实现此目的而建立的链接--https://programmingwin8-js-ch13-hellotiles.azurewebsites.net/dayofmonthservice.php--您将看到返回的 XML 如下所示:

 <?xml version="1.0" encoding="UTF-8"?>
<badge value="24"/>

试用推送与定期通知客户端示例的应用场景 5“轮询徽章更新”中的同一 URI。首次运行此应用(在 Visual Studio Express for Windows 内)时,其磁贴会显示在“开始”屏幕上,如下所示:

轮询徽章更新磁贴

现在,将上述 URI 输入到应用场景 5 的文本框中,按开始定期更新按钮,在连接到网络的情况下,您很快就会看到示例的磁贴上显示数字徽章:

带有数字徽章的磁贴

注意,Windows 会在应用开始定期更新后立即开始尝试轮询更新,随后以指定的时间间隔持续进行轮询。

要查看更完整的 PHP 示例,请参见打造卓越的磁贴体验(第 2 部分),其中显示了磁贴更新的更多定制。在此示例中,假设的 get_trucks_from_database 函数使用 URI 的查询字符串参数中包含的邮政编码对数据库进行查询,然后使用该查询的结果生成 XML 响应。

除此之外服务还可以做更多事情。例如:

  • 使用上述日期 PHP 服务,由于可以轻松地将服务器定位到其他时区,因此应用可以通过在查询字符串中指出其当地时区来获得更准确的日期。
  • 天气服务可以通过在 URI 中使用经度和纬度值来检索该位置的当前天气情况。
  • 服务可以动态生成图像并将其存储在 Web 服务器上,然后将适当的 URI 插入到 XML 负载中。
  • 服务会将其自己的请求发送给其他服务以获取更多数据,通过查询字符串参数进行定制(此后会有更多此类功能)。
  • 如果应用启用磁贴更新队列(请参见 EnableNotificationQueue 方法),可以指定最多五个单独的 URI 来轮询定期更新,如推送与定期通知客户端示例应用场景 4 中所示。磁贴更新队列将使用每个 URI 中的更新进行填充。当然,其中每个 URI 都可以通过其自己的查询字符串实现进一步定制,因此,同一服务自身可以适应所有请求。
  • 应用可以在查询字符串中包括用户 ID,这样一来,服务可以通过对其数据存储进行查询了解用户的试用历史记录、他们在游戏中取得的高分以及他们通过已注册的源获得的新闻等。在这种情况下,用户 ID 就成为个人身份信息 (PII),因此,应用必须尊重隐私。这意味着应用应在查询字符串中对用户名进行加密或使用 https:// URI。

注意:Windows 未在定期更新机制中提供通过服务对用户进行身份验证的方法。只有使用推送通知(或在清单中声明企业身份验证功能的企业应用场景中)才能实现这一级别的支持。

当然,服务也可以使用其他技术编写。ASP.NET 是个不错的选择,使用此技术,您可以在随后采用可在应用中使用的 Notifications Extensions 库(使用 C# 编写)轻松地生成结构良好的 XML 负载。

要看一个简单的示例,不妨看看我在书中第 13 章为 HelloTiles 示例服务创建的非常基本的 WebMatrix 服务(请参见随附内容)。这项特定的服务仅返回固定的 XML 负载(绑定方形磁贴和宽形磁贴)并且结构上类似于打造卓越的磁贴体验(第 2 部分)中给出的第一个 ASP.NET 示例:

 @{
  //
  // This is where any other code would be placed to acquire the dynamic content
  // needed for the tile update. In this case we'll just return static XML to show
  // the structure of the service itself.
  // 
  var weekDay = DateTime.Now.DayOfWeek;
}
<?xml version="1.0" encoding="utf-8" ?>
<tile>
    <visual lang="en-US">
        <binding template="TileSquarePeekImageAndText02" branding="none">
            <image id="1" src="https://www.kraigbrockschmidt.com/images/Liam07.png"/>
            <text id="1">Liam--</text>
            <text id="2">Giddy on the day he learned to sit up!</text>
        </binding>
        <binding template="TileWideSmallImageAndText04" branding="none">
            <image id="1" src="https://www.kraigbrockschmidt.com/images/Liam08.png"/>
            <text id="1">This is Liam</text>
            <text id="2">Exploring the great outdoors!</text>
        </binding>
    </visual>
</tile>

当您希望在推送与定期通知客户端示例的应用场景 4 中试用这一特定服务时,可将该服务部署到 https://programmingwin8-js-ch13-hellotiles.azurewebsites.net/Default.cshtml。进行此部署后,您会在几秒钟后看到以下磁贴更新(左侧显示宽形磁贴,右侧显示两部分方形摘要磁贴):

liam_1

liam_2liam_tile

现在,让我们使用 Notification Extensions 库来编写相同的代码。首先要做的是为您的网站构建一个版本的库,如下所示:

  1. 转到应用磁贴和徽章示例,然后将 Notifications Extensions 文件夹从该项目复制到您自己的文件夹中。(还可以直接从 Visual Studio 中安装库,方法是右键单击一个项目,选择 [Manage NuGet Packages…](管理 NuGet 程序包…)并搜索 NotificationsExtensions.WinRT。不过,这样做会将库引入现有的应用项目中,而这样我们需要在此构建一个独立的 DLL。)
  2. 在 Visual Studio Express for Windows 中,打开 NotificationsExtensions.csproj 文件。
  3. 在解决方案资源管理器中,右键单击 NotificationExtensions 项目,选择“属性”,然后进行以下更改:
    1. 在“应用”设置中,将输出类型更改为“类库”(.dll)。要将库用于 ASP.NET 站点,这一更改非常必要。
    2. 在“构建”设置中,将配置更改为“所有配置”,将条件编译符号更改为 NETFX_CORE; WINRT_NOT_PRESENT 并确保选中页面底部附近的 XML 文档文件。WINRT_NOT_PRESENT 标记表示库可以在没有 WinRT 的情况下进行编译。
  4. 选择调试或发布目标,然后右键单击 Notifications Extensions 项目并选择构建

上述步骤将在该项目的文件夹中生成 DLL 和相关的文件。现在我们需要将其拉入到网站项目中。

  1. 在 Visual Studio Express for Web 中,右键单击您的网站项目并选择 [Add](添加) > [Add ASP.NET folder](添加 ASP.NET 文件夹) > Bin(如果您的网站还没有 Bin 文件夹)。
  2. 右键单击 Bin 文件夹,选择 [Add Reference](添加引用) …。在 [Add Reference](添加引用) 中,转到 Notifications Extensions 项目的 bin\Debugbin\Release 文件夹并选择其中的 DLL。

如果您使用的是 Visual Studio Ultimate,可以根据需要将 Notification Extensions 项目添加到网站解决方案中,因为此工具可以处理这两种项目类型。只需确保您不会将该项目的源代码部署到您的 Web 服务器!

另请注意,如果您将网站构建为在浏览器中以本地方式运行(如我们将在下面的“调试”部分中所看到的),可能会收到错误消息,指出向 System.Runtime 中添加引用时出错。要更正此错误,请打开 web.config 文件并将“compilation”元素更改为如下所示:

 <compilation debug="true" targetFramework="4.0">
  <assemblies>
    <add assembly="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> 
  </assemblies>
</compilation>

完成上述所有操作后,将获得如下页面(名为 DefaultNE.aspx),该页面能与之前的硬编码示例生成相同的输出:

 <?xml version="1.0" encoding="utf-8" ?>
<%@ Page Language="C#" %>
<script runat="server">   1:  
   2:     public string GenerateTileXML()
   3:     {
   4:         // Construct the square template
   5:         NotificationsExtensions.TileContent.ITileSquarePeekImageAndText02 squareTile = 
   6:             NotificationsExtensions.TileContent.TileContentFactory.CreateTileSquarePeekImageAndText02();
   7:         squareTile.Branding = NotificationsExtensions.TileContent.TileBranding.None;        
   8:         squareTile.Image.Src = "https://www.kraigbrockschmidt.com/images/Liam07.png";
   9:         squareTile.TextHeading.Text = "Liam--";
  10:         squareTile.TextBodyWrap.Text = "Giddy on the day he learned to sit up!";
  11:         
  12:         // Construct the wide template
  13:         NotificationsExtensions.TileContent.ITileWideSmallImageAndText04 wideTile =
  14:             NotificationsExtensions.TileContent.TileContentFactory.CreateTileWideSmallImageAndText04();
  15:         wideTile.Branding = NotificationsExtensions.TileContent.TileBranding.None;
  16:         wideTile.Image.Src = "https://www.kraigbrockschmidt.com/images/Liam08.png";
  17:         wideTile.TextHeading.Text = "This is Liam";
  18:         wideTile.TextBodyWrap.Text = "Exploring the great outdoors!";
  19:                 
  20:         // Attach the square template to the notification
  21:         wideTile.SquareContent = squareTile;
  22:         wideTile.Lang = "en-US";
  23:         
  24:         // The wideTile object is an XMLDOM object, suitable for issuing tile updates
  25:         // directly. In this case we just want the XML text.
  26:         return wideTile.ToString();        
  27:     }

</script>
<%

   1:  = GenerateTileXML() 

%>

您可以转到 https://programmingwin8-js-ch13-hellotiles.azurewebsites.net/DefaultNE.aspx 访问此服务,通过此服务,您将获得基本上与之前相同的 XML,只有细微的差异。将此 URI 粘贴到推送与定期通知客户端示例的应用场景 4 中同样也可以生成与之前相同的磁贴更新。

调试服务

当然,成功生成磁贴或徽章更新的前提是服务响应中 XML 的正确生成:Windows 拒绝任何未正确生成的 XML。这就很好地解释了为什么要使用 Notifications Extensions 库,因为它可以显著减少此过程中可能会出现的错误。

但是,如果服务根本无法很好地运行会怎样呢?如何诊断和调试服务处理请求和生成其响应的方式?

事实上,我第一次试用上述 ASP.NET 服务代码时,由于 XML 响应的顶部有一个前导新行,更新并未显示。这就是为什么 <?xml ?> 标头显示为文件中的第一行而并非显示在 <%@ Page %> 指令之后以及为什么没有任何额外的换行符的具体原因。

很显然,众多原因促使您希望能够单步执行自己的服务代码并对其进行逐行调试,在您对数据库进行查询并处理这些查询结果时尤其如此。

其中的技巧是在开发计算机上使用本地主机,这样您可以在运行客户端测试代码(如 SDK 示例)的同时在本地运行和调试您的服务。

使用本地主机意味着您将运行本地 Web 服务器(如 Internet Information Services 或 Apache)。要在 Windows 中打开 IIS(其内置组件),请访问控制面板 > 打开或关闭 Windows 功能。按照下图中所示选中顶级 Internet Information Services 框以安装核心功能:

Windows 功能对话框,其中 Internet Information Services 为选中状态

安装 IIS 之后,可在 c:\inetpub\wwwroot 文件夹中找到以 https://localhost/ 编址的本地站点。您可以向该文件夹中拖入一些内容,如上一部分中所述的 PHP 页面,这样,就可以在客户端示例中使用诸如 https://localhost/dayofmonthservice.php 之类的 URI 了。

要将 PHP 与 IIS 结合使用,您可能需要通过 Microsoft 的 Web 平台安装程序对其进行安装,否则服务器端代码将无法正常执行。安装 PHP 后,尝试在浏览器中输入本地 PHP 页面的 URI。如果您收到错误消息,指出“处理程序 PHP53_via_FastCGI 包含错误模块”(耶,这一错误提示真的很有用!),要更正这一错误,只需返回前面所示的“打开或关闭 Windows 功能”对话框,转到 Internet Information Services > [World Wide Web Services](万维网服务) > [Application Development Features](应用程序开发功能) ,选中 CGI 对应的框然后按 [OK](确定) 。安装 CGI 引擎后,您的 PHP 页面就应可以正常工作了。

本地主机就位后,您可以使用 Visual Studio Express for Web 或 Visual Studio Ultimate 在您自己的计算机上调试服务。还可以结合使用 Visual Studio Express for Web 和 Visual Studio Express for Windows 共同对客户端和服务器代码进行调试。

当您在 Visual Studio (for Web) 调试器中运行某一服务或网站时,该服务或网站在浏览器中运行时所用的 URL 为 https://localhost:<port>/,其中 <port> 是为该项目随机分配的。举例来说,在 Visual Studio Express for Web 中运行上一部分中的 DefaultNE.aspx 页面时,该页面将在 Internet Explorer 中打开,URI 为 https://localhost:52568/HelloTiles/DefaultNE.aspx。如果我在该页面的代码中设置了断点,调试器会在运行到该点时立即停止。

如果您在客户端代码中使用同一本地主机 URI 启动请求,断点也会被命中。举例来说,如果我在 Visual Studio Express for Windows 中运行推送与定期通知客户端示例时将该 URI 粘贴到应用场景 4 中,则 Visual Studio Express for Web 将在 Windows 发出请求后立即在调试器中停止我的服务。之后我可以单步执行该代码(幸运的是,Windows 很有耐心),确保生成正确的响应。如果不这样做,可以修复代码然后在本地主机上重新启动服务。

在您确信您的服务以预期方式运行后,可以将其上载到活动 Web 主机(或过渡环境)并进行最终生产测试。

注意,要以这种方式使用本地主机,不需要在调试器中运行客户端代码。不过,如果您要运行客户端代码,必须打开 Visual Studio 中相应的选项本地主机才能运行。默认情况下,该选项为选中状态,但是如果您需要更改它,可以在项目属性中的 [Debugging](调试) > [Allow Local Network Loopback](允许本地网络环回) 下找到它:

允许本地网络环回选项

来自外部来源的数据

定期通知服务除了可以对其自己的数据库进行查询之外,还可以通过向其他服务发送请求来获取响应数据。不过需要引起注意的是,这些请求本质上是异步的,往往会导致各种故障情况。因此,使用它们会显著增加服务实施的复杂性。

为简化相关事宜,我们可以利用 Windows 仅以 30 分钟或更长时间为间隔向您的服务发出请求这一情况(通过客户端 API 强制实施)。这意味着您拥有大把时间,在这段时间内其他服务器端过程可以发出外部请求来监控天气预警、排行榜、RSS 源或包含 Web API 的任何其他事物。这些过程将结果存储在数据库中,在数据库收到下一个请求时,这些结果就可供您的定期通知服务查询(同步)。

任意数量的代理可以更新同一数据库。例如,用户可能通过您的网站输入数据。他们可能使用手机应用跟踪其活动,并将结果自动上载到数据库。数据库还可能由在其各自的设备上使用同一应用的好友进行更新。

具体排列情况如下所示,数据库作为中央存储居于后端,而定期通知服务就其状态而言只是普通的用户。

database_servers_graph

将 Windows Azure 移动服务与定期更新服务结合使用

随着您开始了解后端服务并将其扩展用于支持动态磁贴和其他通知,您已将其用于探索 Windows Azure 移动服务,我将此服务简称为 AMS。AMS 除了能大大简化推送通知(我们将在本博文系列的第 3 部分中看到)之外,还可用于通过以下几种方式支持定期更新服务:

  • 在移动服务中,您可以创建计划后台作业,通过这些作业向其他服务发出请求并将结果存储在数据库中。从 Twitter 中获取 tweet 是其中一个例子,可以在在移动服务中计划重复执行的作业这一主题中找到。
  • 当您在 AMS 中(或在 Windows Azure 中的其他位置)创建 SQL Server 数据库时,可以像访问任何其他 Web 托管的 SQL Server 数据库一样访问该数据库,因此您可以通过网站和其他服务(包括使用 PHP 编写的网站和服务)使用该数据库。
  • 使用 AMS,可以非常轻松地借助移动服务 SDK 通过客户端应用将记录插入到数据库中。
  • 不同于移动服务,Windows Azure 可以托管使用多种语言(包括 Node.js、Python、Java、PHP 和 .NET)编写的服务器端过程。

在将来,还应期待 Azure 移动服务中推出的名为“服务操作”的新功能,通过此新功能,您将可以创建任意的 http 端点,包括定期通知服务。

有关 Windows Azure 的详细信息,请访问 https://www.windowsazure.com。要观看入门视频,请访问 Channel 9 上的 Windows Azure 移动服务教程

现在我们已探讨了如何创建定期通知服务,接下来我们将可以开始介绍推送通知了。在您需要以高于定期通知允许的频率发布更新时就要用到推送通知,实质上是按需(因此有了“推送”这个名字)。这是我们将在本博文系列的第 3 部分中讨论的主题,在第 3 部分中我们将更深入地探讨 Azure 移动服务。

Kraig Brockschmidt

Windows 生态系统团队项目经理

使用 HTML、CSS 和 JavaScript 编程 Windows 8 应用作者