Август 2015

Том 30, выпуск 8

Azure Mobile Services - Azure Mobile Services: превосходная серверная часть для AngularJS

Джонатан Миллер

Продукты и технологии:

Azure Mobile Services, AngularJS, JavaScript

В статье рассматриваются:

  • преимущества Azure Mobile Services;
  • создание простого приложения Angular Notes;
  • хранение данных в Azure Mobile Services;
  • аутентификация пользователей в Azure Mobile Services.

Исходный код можно скачать по ссылке msdn.microsoft.com/magazine/msdnmag0815.

AngularJS — прекрасная инфраструктура для создания JavaScript-приложений как для Web, так и для мобильных устройств. Она очень мощная, но требует некоторых усилий в освоении. Для начала я прочитал блоги и книги и просмотрел учебные видеокурсы, которые здорово помогают в изучении клиентских средств, таких как формы, перенаправление и проверка. К сожалению, тематика по клиентской среде везде сильно затеняет вопросы, касающиеся серверной стороны. В большинстве учебных ресурсов эта тематика едва затронута или о ней вообще ничего не говорится.

В одном из учебных курсов почти эксклюзивно использовали Angular-сервис $httpBackend. А ведь $httpBackend — отличный способ создания имитаций для тестирования, но он никоим образом не рассчитан на сохранение данных для производственных приложений. На другом ресурсе использовали продукт с открытым исходным кодом — deployd (deployd.com), который позволяет легко и быстро подготавливать и запускать в работу сервер REST API. Deployd бесплатен для скачивания и может выполняться на компьютере для разработок или на сервере. Он хорош для моделирования и тестирования REST API. И вновь проблема с тем, что делать в производственной среде. В такой среде я должен предоставлять REST/JSON-сервер через Интернет для использования приложением AngularJS, но я не хочу иметь дело с хостингом серверов и управлением ими в Интернете. Мне нужна возможность быстрой раскрутки новых приложений и поддержка вертикального масштабирования, если такая необходимость возникнет. Мне требуются интегрированные средства защиты без чрезмерной сложности. Я хочу, чтобы была возможность подготовки REST/JSON API для хранения данных приложения. И мне нужно, чтобы все эти средства можно было легко освоить и интегрировать с моим приложением. К счастью, в своих поисках я обнаружил, что Microsoft уже решила эти проблемы. В этой статье я покажу, как интегрировать серверную часть Azure Mobile Services с клиентской частью AngularJS.

Azure Mobile Services (AMS) — это «готовая из коробки» серверная часть. Она сводит воедино все серверные части, необходимые для производственного приложения, и имеет ряд интересных средств, которые обеспечивают:

  • очень быстрое с поддержкой избыточности облачное хранилище;
  • упрощение создания таблиц, к которым можно обращаться через REST/JSON;
  • встроенные средства защиты и аутентификации в популярных провайдерах входа, таких как Microsoft, Google, Facebook и Twitter;
  • бесплатную начальную работу с возможностью горизонтального масштабирования для приложений с высокими требованиями;
  • упрощение проверки на серверной стороне;
  • поддержку серверных частей как JavaScript, так и .NET. Microsoft значительно облегчила подготовку AMS-сайта и его интеграцию почти с любой клиентской платформой.

Мое приложение Angular Notes

Чтобы продемонстрировать, как соединить AngularJS и AMS, я намерен создать очень простое приложение Angular Notes. В нем будет одна страница, содержащая список заметок. Каждая заметка (note) будет иметь кнопку удаления рядом с ней. Также будет текстовое поле для добавления новой заметки в список. Как выглядит это приложение, показано на рис. 1.

Приложение Angular Notes
Рис. 1. Приложение Angular Notes

Для этого приложения я буду использовать Visual Studio 2013 Update 4 и учетную запись Azure, полученную мной по подписке на MSDN. Чтобы следовать за мной, сначала создайте новый проект ASP.NET Web Application. Выберите пустой шаблон безо всяких вариантов, как показано на рис. 2.

Создание пустого проекта ASP.NET
Рис. 2. Создание пустого проекта ASP.NET

Теперь вы должны добавить библиотеки AngularJS и Bootstrap, поэтому установите NuGet-пакеты Angular.Core и Bootstrap.

Чтобы создать начальное представление, добавьте новую HTML-страницу notes.html в корень проекта. Модифицируйте HTML так, чтобы он совпадал с тем, что приведено на рис. 3. Заметьте, что этот HTML ссылается на Angular и Bootstrap. Кроме того, имеется тег ng-app, который сообщает Angular обрабатывать эту страницу. Наконец, в разметке есть раздел body с тегом 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>

Чтобы добавить список заметок, добавьте в нижнюю часть раздела body новый 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>

Чтобы добавить контроллер, создайте новый JavaScript-файл в корне проекта и назовите его notesCtrl.js. На рис. 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 рядом с заметкой удаляет эту заметку. Ввод новой заметки в текстовое поле и щелчок Add Note приводит к добавлению этой заметки в список. Но пока что список находится только в памяти. Обновление вернет страницу к исходному списку, и все изменения будут потеряны. Давайте перенесем хранилище данных из памяти в облако.

Хранение данных в Azure Mobile Services

Я изменю механизм хранения заметок. Вместо использования статического массива в памяти я буду загружать и сохранять свои заметки в AMS.

На Azure Portal создайте новый Azure Mobile Service. Убедитесь, что он использует серверную часть JavaScript. Затем щелкните вкладку Data в мобильном сервисе и создайте новую таблицу данных (рис. 6).

Создание новой таблицы заметок
Рис. 6. Создание новой таблицы заметок

Далее, чтобы добавить столбец текста заметки, вставьте в таблицу notes строковый столбец с именем notetext.

Теперь вы должны получить ключ приложения. На начальной странице Azure Portal для Azure Mobile Services вы увидите кнопку Manage Keys. Щелкните ее, чтобы получить ключ приложения, и сохраните его где-нибудь для последующего использования. Приложению Angular понадобится обращаться к AMS..

Чтобы AMS работал в приложении Angular, нужно добавить в представление notes.html две скриптовые ссылки: на JavaScript-библиотеку, предоставляемую Microsoft для Azure Mobile Services, и на сервис в стиле Angular, который обертывает библиотеку Microsoft. Основное преимущество этой библиотеки в том, что она расширяет библиотеку Microsoft JavaScript для AMS и предоставляет интерфейсы и обещания (promises) в стиле Angular. Прекрасный пример кода для этой библиотеки вы найдете на GitHub по ссылке bit.ly/1po76vI.

Убедитесь, что эти новые записи появляются между ссылками на 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 Mobile Services в контроллере измените первую строку в NotesCtrl.js и добавьте зависимость в 'azure-mobile-service.module':

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

Вставьте константы в нижнюю часть файла notesCtrl.js с URL мобильного сайта Azure и полученным ранее ключом приложения. Библиотека AMS будет использовать их для доступа к сайту AMS:

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

Теперь замените код в контроллере, который присваивает vm.notes статическому массиву, на код, который получает данные от AMS. Этот код извлекает всю таблицу заметок и помещает результат в массив vm.notes:

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

Потом измените функцию vm.addNote так, чтобы она сохраняла новые заметки в таблице данных notes. Как только AMS сообщит об успешном завершении операции, элемент добавляется в массив в памяти. Это избавляет приложение от необходимости повторной загрузки всего списка всякий раз, когда что-то добавляется в список:

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

Это одна из сильных сторон AMS -- он прост в настройке и легко интегрируется.

Наконец, измените функцию vm.deleteNote так, чтобы она удаляла заметку из таблицы notes в AMS. И вновь код ожидает, когда AMS успешно закончит операцию, а затем удаляет соответствующий элемент из массива в памяти:

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

Теперь все заметки получены из таблицы notes в AMS. Когда пользователь добавляет или удаляет заметку, эти операции выполняются в таблице данных в AMS. Чтобы добиться этого, мне понадобилось написать совсем немного кода. Это одна из сильных сторон AMS — он прост в настройке и легко интегрируется.

Аутентификация пользователей в Azure Mobile Services

Добавление аутентификации в веб-приложение может быть проблемой. Всегда возникает вопрос, стоит ли развертывать свою аутентификацию. В наши дни, если только у вас нет по-настоящему веских оснований, почти всегда имеет смысл использовать одного из основных провайдеров идентификации. Они заботится о безопасном хранении паролей и выполняют другие задачи. AMS упрощает подключение к популярным провайдерам идентификации: Microsoft, Facebook, Twitter и Google. AMS бесшовно интегрирует аутентификацию и авторизацию с функцией входа и таблицами, созданными в AMS. В этой статье я предпочел использовать аутентификацию по учетной записи в Microsoft. Сконфигурировав аутентификацию, я изменю пример так, чтобы лишь аутентифицированные пользователи могли просматривать и редактировать заметки, а также видеть собственные списки.

Первый шаг — настроить свое приложение на портале Microsoft Live (bit.ly/1JS4jq3). Он предоставляет Client ID и Client Secret, необходимые AMS для работы с Microsoft Identity. Теперь вставьте их в поля на вкладке Identity в AMS, как показано на рис. 7. Весь процесс регистрации приложения описывается в великолепной статье на bit.ly/1Ij22hy.

Настройка провайдера Microsoft Identity
Рис. 7. Настройка провайдера Microsoft Identity

Добавьте в представление кнопки входа и выхода. Для этого вставьте следующий div перед div, который содержит таблицу notes:

<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 для кнопки выхода отображает ее, только если пользователь уже вошел.

Затем добавьте другой тег ng-if в контейнер div, чтобы скрывать список и текстовое поле для ввода новой заметки, когда пользователь не вошел. Это не является средством защиты. Правила безопасности будут вводиться в действие AMS. Это просто придает странице соответствующий вид:

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

Добавьте функции аутентификации к контроллеру. Функция isLoggedIn используется представлением, чтобы определить, должно оно скрыть или отобразить кнопку входа/выхода и показать список заметок. Результат isLoggedIn возвращается из модуля Azureservice:

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;
    });
  });
}

Функция logout обеспечивает выход пользователя из AMS, вызывая функцию logout в модуле Azureservice. Она также сбрасывает массив notes:

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

Прямо сейчас единственное, что не дает доступа не аутентифицированному пользователю к списку заметок, — код, загружающий его только после аутентификации пользователя. Это совершенно не безопасно. Гораздо лучше, чтобы AMS управляла этим на своей серверной стороне. На Azure Portal откройте сервис AMS, затем откройте таблицу notes и щелкните Permissions. Измените все разрешения на Only Authenticated Users, как показано на рис. 8. Теперь любые вызовы этой таблицы будут терпеть неудачу, если пользователь не аутентифицирован.

Защита таблицы заметок
Рис. 8. Защита таблицы заметок

Разделение данных разных пользователей

Хотя аутентификация на сайте работает, все пользователи по-прежнему делят один список. Давайте сделаем так, чтобы каждый пользователь мог видеть и редактировать только свои заметки. Это требует всего нескольких изменений на Azure Portal. Я вообще не буду ничего менять в самом Angular-приложении.

Сначала откройте таблицу данных notes. Щелкните любой столбец и добавьте новый строковый столбец userid. Тем самым я сопоставлю заметку с пользователем. Потом перейдите на вкладку скрипта в таблице notes. Выберите 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 Mobile Services. У каждого пользователя свой список заметок, к которому можно обратиться только после аутентификации.

Заключение

С помощью небольшого объема кода это демонстрационное приложение теперь работает в облаке и располагает защищенным хранилищем и поддержкой аутентификации. Пусть вас не вводит в заблуждение, будто внедрение AngularJS на клиентской стороне вынудит вас отказаться от стека технологий Microsoft на серверной стороне. AMS бесшовно интегрируется с AngularJS. Azure Mobile Services — отличная серверная часть для приложений AngularJS.


Джонатан Миллер (Jonathan Miller) — старший архитектор CuroGens в Индианаполисе. Не менее десяти лет разрабатывает продукты на основе стека технологий Microsoft и программирует в .NET с момента ее появления. Разработчик всего спектра приложений с экспертными познаниями в области клиентских технологий (Windows Forms, Windows Presentation Foundation, Silverlight, ASP.NET, AngularJS/Bootstrap), промежуточного ПО (Windows-службы, Web API) и серверных технологий (SQL server, Microsoft Azure).

Выражаю благодарность за рецензирование статьи экспертам Microsoft Дэвиду Крофорду (David Crawford) и Саймону Гуревичу (Simon Gurevich).