2015 年 8 月

第 30 卷,第 8 期

Azure 移动服务 - Azure 移动服务: AngularJS 非常棒的后端

作者 Jonathan Miller

AngularJS 是非常棒的框架,可用于创建适合 Web 和移动的 JavaScript 应用程序。虽然它的功能非常强大,但它附带了一些学习曲线。首先,我阅读了相关博客和书籍,并观看了视频课程,这些非常有助于学习客户端功能,如窗体、路由和验证。遗憾的是,客户端主题似乎始终会掩盖后端问题。大多数学习资源都无法从根本上介绍这些功能:一个课程几乎全部使用 Angular $httpBackend 服务。$httpBackend 是构建用于测试的虚设的好方法,但是这并不表示为生产应用程序保留数据。另一个资源使用名为 deployd (deployd.com) 的开放源产品,它可提供一个既快速又简单的方法来让 REST/API 后端服务器启动并运行。Deployd 可免费下载并可在开发计算机或服务器上运行。它非常适合对 REST API 进行建模和测试。同样,问题还是在于要在生产中执行哪些操作。在生产环境中,我必须将 REST/JSON 服务器公开到 Internet,以便让 AngularJS 应用程序使用,但是我不想负责托管和管理 Internet 上的服务器。我需要能够加快新应用程序的速度并根据需要快速扩展。我需要集成安全,但不希望太复杂。我希望能够设置 REST/JSON API 以存储应用程序数据。而且,我需要能够轻松地了解这些问题并与我的应用程序集成。幸运的是,我在研究中,我发现 Microsoft 已经解决了这些问题。在本文中,我将向您演示 Azure 移动服务后端如何与 AngularJS 前端集成。

Azure 移动服务 (AMS) 确实是一个组合的后端。它将生产应用所需的所有后端部分汇集到一起,并拥有很多出色的功能:

  • 它提供的云存储速度快,容量大。
  • 通过它,可以非常轻松地创建通过 REST/JSON 访问的表。
  • 它向流行的登录提供商(Microsoft、Google、Facebook 和 Twitter)提供内置的安全和身份验证。
  • 它免费启动,并且可针对高要求的应用程序进行扩展。
  • 它使服务器端验证变得容易。
  • 它允许使用 JavaScript 和 .NET 后端。Microsoft 使它非常容易设置 AMS 网站,并将它与几乎所有客户端平台进行集成。

我的 Angular Notes 应用

为了演示如何连接 AngularJS 和 AMS,我打算组成一个非常简单的 Angular Notes 应用程序。此应用是单个页面,并且由一个备注列表组成。每个备注的旁边都有一个删除按钮。此外,有一个文本框,可用于向列表中添加新的备注。图 1 显示 Angular Notes 应用的外观。

Angular Notes 应用
图 1 Angular Notes 应用

对于此应用,我将使用 Visual Studio 2013 更新 4 和我接收 MSDN 权益使用的 Azure 帐户。若要继续,首先创建一个新的 ASP.NET Web 应用程序。如图 2 中所示,选择空的模板且不选择任何选项。

创建一个空的 ASP.NET 项目
图 2 创建一个空的 ASP.NET 项目

现在,您应该添加 AngularJS 和 Bootstrap 库,因此添加 Angular.Core 和 Bootstrap 的 NuGet 程序包。

若要创建初始视图,则将新的 HTML 页面添加到名为 notes.html 的项目的根部。修改 HTML,以便于它与图 3 相吻合。请注意,它会引用 Angular 和 Bootstrap。它还含有 ng-app 标记,其可指示 Angular 处理此页面。最后,它的主体部分含有我稍后将要创建的控制器的 ng-controller 标记。我已经散布在一些 Bootstrap 类中,以便让它看上去更美观。它们不是必需的,并且可以忽略。

图 3 初始 HTML 视图

<html ng-app="notesApp">
<head>
  <title>Angular Notes</title>
  <link type="text/css" rel="stylesheet" href="Content/bootstrap.css" />
  <script src="Scripts/angular.js"></script>
  <script src="notesCtrl.js"></script>
</head>
<body ng-controller="notesCtrl as vm">
  <div class="page-header text-center">
    <h1>
      <span class="glyphicon glyphicon-cloud" aria-hidden="true"></span>
      <span style="padding-bottom:10px">Angular Notes</span>
    </h1>
  </div>
</body>
</html>

若要添加备注列表,则在主体内部将一个新的 div 添加到底部,如图 4 中所示。此 div 将在备注列表中循环,并且为每个备注显示一个表行。它还会在每行上放置一个删除按钮。使此项工作得以顺利进展的关键标记是 ng-repeat 标记,其在控制器上的备注数组中循环。

图 4 添加备注列表

<div class="container">
  <div class="panel panel-default">
    <table class="table table-striped">
      <tr class="list-group" ng-repeat="note in vm.notes">
        <td>
          {{note.notetext}}
          <button class="btn btn-xs btn-danger pull-right"
            ng-click="vm.deleteNote(note)">
            <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
          </button>
        </td>
      </tr>
    </table>
  </div>
</div>

最终,若要创建一个新的备注框,则向此视图中添加最后一个 div,以便于用户可以创建一个新备注。将它放置在备注表的 div 的上方。在下面的代码中,请注意,输入框内容是绑定到 vm.addNoteText 的数据,而且单击该按钮或按下 Enter 将调用控制器上的方法 vm.addNote:

<div class="input-group" style="padding-bottom:15px">
  <input type="text" class="form-control" ng-model="vm.addNoteText"
    placeholder="new note" ng-keypress="($event.which === 13)?vm.addNote():0" />
  <span class="input-group-btn">
    <button ng-click="vm.addNote()" class="btn btn-success">Add Note</button>
  </span>
</div>

若要添加控制器,在名为 notesCtrl.js 的项目的根部创建一个新的 JavaScript 文件。图 5 显示了整个控制器的代码。它由以下三部分组成:要显示的初始备注数组、向数组中添加项目的 addNote 函数以及从数组中移除备注的 deleteNote 函数。确保存在对 notes.html 视图中的此脚本的引用。

图 5 添加控制器

angular.module('notesApp', [])
  .controller('notesCtrl', function () {
    var vm = this;
    vm.addNoteText = '';
    vm.notes = [
      { "notetext": "Fix driveway" },
      { "notetext": "Replace roof" },
      { "notetext": "Fix dryer" },
      { "notetext": "Tear out deck" },
      { "notetext": "Add electricity to garage" }
    ];
    vm.addNote = function () {
      if (vm.addNoteText !== '') {
          vm.notes.push({ "notetext": vm.addNoteText });
          vm.addNoteText = '';
      }
    }
    vm.deleteNote = function (note) {
      vm.notes.splice(vm.notes.indexOf(note), 1);
    }
  });

现在,核心的 Angular 应用程序已完成。运行该页面显示备注列表。单击备注旁边的红色 X 即可删除该备注。向文本框中键入新的备注并单击“添加备注”可将备注添加到该列表。但到目前为止,该列表仅在内存中。刷新该页面将恢复原始列表,并且所有更改内容都会丢失。我们将数据存储移出内存,并将其移入云。

Azure 移动服务中的存储数据

我打算更改备注的存储机制。我打算从 AMS 中加载并保存我的备注,而不是使用静态的、内存中的数组。

在 Azure 门户中,创建新的 Azure 移动服务。请确保它使用 JavaScript 后端。接下来,单击移动服务内部的“数据”选项卡,然后创建新的数据表,如图 6 中所示。

创建新的备注表
图 6 创建新的备注表

接下来,若要添加备注文本列,则将名为 notetext 的字符串列添加到备注表。

现在,您需要获取应用程序密钥。在 Azure 移动服务的 Azure 门户主页上,您将看到一个标记“管理密钥”的按钮。单击此按钮可以获取该应用程序密钥,并将它保存到某个位置以供日后使用。Angular 应用程序需要该密钥来访问 AMS。

若要使 AMS 在 Angular 应用中正常使用,您需要向 Notes.html 视图中添加两个脚本引用。第一个用于 Microsoft 针对 Azure 移动服务提供的 JavaScript 库。第二个是封装 Microsoft 库的 Angular 风格的服务。此库的主要优势在于它可扩展 AMS 的 Microsoft JavaScript 库,并提供 Angular 风格的接口和保证。您可以在 bit.ly/1po76vI 上的 GitHub 中找到有关此库的优秀示例代码。

请确保这些新条目显示在 angular.js 和 notesCtrl.js 引用之间,如下所示:

<script src="Scripts/angular.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/mobileservices/MobileServices.Web-1.1.2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-azure-mobile-service/1.3.4/angular-azure-mobile-service.min.js"></script>
<script src="notesCtrl.js"></script>

若要引用控制器中的 Azure 移动服务,请更改 NotesCtrl.js 的第一行,并将依赖关系添加到“azure-­mobile-service.module”:

angular.module('notesApp', ['azure-mobile-service.module'])

将一个常量添加到含有 Azure 移动网站的 URL 和之前检索的应用程序密钥的 notesCtrl.js 文件的底部。AMS 库将使用这些内容访问 AMS 网站:

angular.module('notesApp').constant('AzureMobileServiceClient', {
  API_URL: "https://angularnotes.azure-mobile.net/",
  API_KEY: "gkwGJioLD3jNxrAX6krXh6jVk6SFkeQr",
});

现在,使用从 AMS 中检索的数据替换将 vm.notes 设置为静态数组的控制器中的代码。此代码可检索整个备注表并将该结果填充到 vm.notes 数组:

Azureservice.query('notes', {})
.then(function (items)
{
  vm.notes = items;
});

接下来,更改 vm.addNote 函数,以便它将新备注保存到备注数据表中。AMS 返回成功之后,则该项目被添加到内存中的数组中。这可以让该应用程序避免在每次向其添加内容时重新加载整个列表:

vm.addNote = function () {
  if (vm.addNoteText !== '') {
    Azureservice.insert('notes', {
      "notetext" : vm.addNoteText                 
    }).then(function (newitem) {
      vm.notes.push(newitem);
      vm.addNoteText = '';
    });
  }
}

最后,更改 vm.deleteNote 函数,以便它从 AMS 备注表中删除备注:同样,该代码要等待 AMS 成功,然后才从内存中的数组中删除备注:

vm.deleteNote = function (note) {
  Azureservice.del('notes', {
    "id": note.id
  }).then(function () {
    vm.notes.splice(vm.notes.indexOf(note), 1);
  });
}

现在,从 AMS 中的所有备注表中检索所有备注。当用户添加或删除备注时,这些操作作用到 AMS 中的数据表上。我需要编写一点代码来实现此目的。这是 AMS 的主要优势之一—很容易创建和集成。

对 Azure 移动服务中的用户进行身份验证

向 Web 应用中添加身份验证可能非常麻烦。是否自己动手一直是个问题。最近,除非您确实拥有令人叹服的案例,否则使用主要标识提供程序之一始终是最有意义的。它们可以处理安全存储密码和执行重设的问题。AMS 使得连接到流行的标识提供程序(Microsoft、Facebook、Twitter 和 Google)变得很容易。AMS 将身份验证和授权与登录功能和 AMS 中创建的表无缝集成。在本文中,我选择使用 Microsoft 帐户进行身份验证。配置身份验证之后,我将更改示例,以便只有经过身份验证的用户才可以查看或编辑备注以及查看他们自己的列表。

第一步是在 Microsoft Live portal (bit.ly/1JS4jq3) 中设置您的应用。这可提供 AMS 让 Microsoft 标识正常工作所需的客户端 ID 和客户端密钥。现在,将它们粘贴到 AMS 中的“标识”选项卡中,如图 7 中所示。请参阅 bit.ly/1Ij22hy 中的优秀文章,它向您介绍了注册应用程序的整个过程。

设置 Microsoft 标识提供程序
图 7 设置 Microsoft 标识提供程序

接下来,向视图中添加登录和注销按钮。将以下 div 代码添加到包含备注表的 div 之上:

<div class="text-center">
  <button class="btn btn-primary" ng-if="!vm.isLoggedIn()"
  ng-click="vm.login()">Login</button>
  <button class="btn btn-primary" ng-if="vm.isLoggedIn()"
  ng-click="vm.logout()">Logout</button>
</div>

登录按钮上的 ng-if 代码可以完成此操作,因此登录按钮仅在用户无法登录时显示。注销按钮上的 ng-if 代码可以完成此操作,因此注销按钮仅在用户登录时显示。

现在,向容器 div 中添加另一个 ng-if 标记,以在用户无法登录时隐藏列表和新的备注文本。这不是一项安全功能。AMS 将强制执行安全。这只是使页面外观更加适合:

<div class="container" ng-if="vm.isLoggedIn()" style="padding:15px">

接下来,向该控制器中添加身份验证函数。视图会使用 isLoggedIn 函数来确定是隐藏还是显示登录/注销按钮,以及是隐藏还是显示备注列表。它只是从 Azureservice 模块中返回 isLoggedIn 结果:

vm.isLoggedIn = function ()
{
  return Azureservice.isLoggedIn();
}

当用户单击登录按钮时,会调用该应用的登录函数。它会调用 Azureservice 库中的登录函数。将查询备注列表的 AMS 的代码从控制器的顶部移动到此函数中。现在,该列表仅在用户身份验证成功之后加载:

vm.login = function () {
  Azureservice.login('microsoftaccount')
  .then(function () {
    Azureservice.query('notes', {})
    .then(function (items) {
      vm.notes = items;
    });
  });
}

通过调用 Azureservice 模块上的注销函数,注销函数会使用户退出 AMS。它还会清空备注数组:

vm.logout = function () {
  vm.notes = [];
  Azureservice.logout();
}

现在,将未经身份验证的用户退出备注列表的唯一情况是,该代码只会在用户经过身份验证之后加载列表。这根本不安全。让 AMS 在后端执行身份验证会更好。在 Azure 门户中,打开 AMS 服务,然后打开备注表并单击“权限”。将所有权限更改为“仅经过身份验证的用户”,如图 8 中所示。现在,如果用户未经身份验证,对此表进行任何调用都将失败。

确保备注表的安全
图 8 确保备注表的安全

用户数据分离

即使网站身份验证正常运行,所有用户仍然需要共享单个列表。我们这样做可以让每个用户只查看和编辑他们自己的备注。这需要在 Azure 门户中进行少量更改。我根本不会更改 Angular 应用。

首先,进入备注数据表。单击列并添加名为 userid 的新字符串。这是我将备注与用户相关联的方式。接下来,转到备注表格上的“脚本”选项卡。从操作下拉列表中选择 INSERT 并将以下内容添加到脚本:

function insert(item, user, request) {
  item.userid = user.userId;
  request.execute();
}

此新行将新行的 userid 设置为身份验证提供程序中的 userid。在后端的代码中执行此操作更加安全。该代码在 Azure 中运行;用户(或攻击者)无权访问它。

若要筛选 userid 返回的备注,从操作下拉列表中选择 READ 并更改它的脚本:

function read(query, user, request) {
  query.where({ userid: user.userId });
  request.execute();
}

新的 query.where 行筛选 userid 列返回的行;登录用户的 userid 作为该值进行提供。在数据到达客户端之前在服务器上筛选数据比在客户端代码中执行此操作更加安全。

现在,Angular Notes 应用程序安全地将备注存储在 Azure 移动服务中。每个用户都含有一个独立的备注列表,而且该列表需要首先进行身份验证才能访问。

总结

只需使用少量的代码,此演示应用程序现在就能成为含有一流的存储和身份验证的云驱动应用程序。不要相信您在前端采用了 AngularJS,就必须在后端放弃 Microsoft 堆栈这样的观点。AMS 与 AngularJS 无缝集成。Azure 移动服务是适用于 AngularJS 应用程序的非常棒的后端。


Jonathan Miller 是 Indianapolis 的 CuroGens 中的一位高级架构师。十年来,他一直致力于在 Microsoft 堆栈上开发产品,并在 .NET 出现之后,致力于在其上进行编程。Miller 是完全堆栈产品开发人员,是前端技术(Windows 窗体、Windows Presentation Foundation、Silverlight、ASP.NET、AngularJS/Bootstrap)、中间件(Windows 服务、Web API)和后端(SQL 服务器、Microsoft Azure)方面的专家。

衷心感谢以下 Microsoft 技术专家对本文的审阅: David Crawford 和 Simon Gurevich
Simon Gurevich 是在 Microsoft 工作长达 15 年的老员工,目前是 Microsoft 咨询服务部门的首席顾问,擅长 Azure 平台技术。在他的职业生涯中,Simon 一直致力于分布式应用程序体系结构和 Microsoft 平台上的开发事宜,并在最近几年中与 Azure 产品小组展开紧密合作。在加入 Microsoft 之前,Simon 在一个计算机电话服务实验室负责研究与开发工作,他负责开发执行大容量电话拨号路由的基于服务器的应用程序。Simon 持有计算机科学和数学方面的硕士学位。