Июнь 2016

Том 31 номер 6

Azure App Services - Применение Azure App Services для преобразования веб-страницы в PDF

Бенджамин Перкинс

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

Microsoft Azure, C#, SignalR

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

  • Azure App Services Web Apps;
  • аутентификация и авторизация в App Service;
  • Azure Storage;
  • Azure WebJobs;
  • SignalR.

Исходный код можно скачать по ссылке.

В преобразовании веб-страницы в PDF нет ничего нового, но моя цель — разместить ссылку на моем веб-сайте, чтобы дать посетителям простой способ преобразования конкретной страницы в PDF-документ в реальном времени; это оказалось не таким-то простым делом. Существуют многочисленные веб-сайты и программы с открытым исходным кодом, позволяющие делать это, но мне никогда не удавалось соединить все элементы и получить такой результат, который я хотел.

Лучший конвертер (или, по крайней мере, мой любимый) веб-страниц в PDF — программа wkhtmltopdf с открытым исходным кодом (wkhtmltopdf.org), использующая командную строку, как показано на рис. 1.

Запустите Преобразователь wkhtmltopdf из консоли

Рис. 1. Запуск конвертера wkhtmltopdf из консоли

Однако запуск какой-либо программы из командной строки весьма далек от преобразования реального времени щелчком кнопки на веб-странице.

Последние месяцы я проработал разные части этого решения, но запуск процесса wkhtmltopdf упорно не давал мне добиться своей цели. Вопрос, остававшийся без ответа: «Как заставить Microsoft Azure App Service Web Apps порождать этот процесс для создания PDF?». App Service Web Apps выполняется в изолированной программной среде («песочнице»), и с самого начала я знал, что мне не удастся сделать это, — нет никакой возможности отправить запрос с клиентской машины, который запустил бы процесс на сервере. Поскольку я многие годы проработал в группе поддержки IIS, я знал, что даже на автономной версии IIS это потребовало бы использования таких конфигураций, что эксперт по безопасности тут же упал бы в обморок. И я подумал о WebJobs.

WebJobs рассчитаны как раз на такую ситуацию, поскольку могут выполнять исполняемые файлы либо постоянно, либо при инициации из внешнего источника, например вручную из Azure SDK, использованием Azure Scheduler, CRON или Azure WebJob API (bit.ly/1SD9gVJ). И тут я получил ответ. Я мог вызывать программу wkhtmltopdf из своего App Service Web App, применяя WebJob API. Остальные компоненты решения уже были проработаны, я наконец-то уложил последний фрагмент головоломки, как показано на рис. 2.

Законченное решение

Рис. 2. Законченное решение

Azure App Service Web App Azure App Service Web App
Download URL URL скачивания
URL URL
Azure WebJob Azure WebJob
Convert() UploadToStorage() NotifyClient() Convert()
UploadToStorage()
UploadToStorage()
Azure Storage Azure Storage
return SUCCESS return SUCCESS

Пример кода содержит веб-сайт ASP.NET со страницей index, позволяющей пользователю вводить URL, отправлять соответствующую веб-страницу на преобразование в PDF, а затем скачивать PDF на клиентское устройство. С минимумом усилий можно динамически настраивать этот URL на текущую страницу и нажатием кнопки отправлять страницу в WebJob API для конвертации и скачивания. В следующих нескольких разделах этой статьи обсуждаются технологии, примененные при создании решения, и поясняется, как их использовать.

Обзор конвертера HTML-to-PDF

Я задействовал разнообразные технологии, чтобы создать решение HTML-to-PDF App Service Web App реального времени. В табл. 1 дано краткое описание этих технологий — подробнее о них я расскажу в следующих разделах.

Табл. 1. Технологии, применяемые в решении

Технология Краткое описание
Azure App Service Web App (тариф S2) Клиентская часть, где размещен SignalR-код
Аутентификация и авторизация App Service Подтверждает идентификацию клиента
Azure Storage Сохраняет PDF-документ
Azure WebJob Преобразует HTML в PDF, загружает PDF в Azure Storage
Azure WebJob API Интерфейс для инициации WebJob
ASP.NET SignalR Управляет приемом ответов от сервера на клиенте

Каждый раздел включает функциональное и техническое описание технологии плюс детали кодирования и/или требования к конфигурации. Я упорядочил разные части решения по мере того, как создавал их, но это можно было бы сделать, используя несколько других последовательностей. Техническая цель — передача URL в App Service Web App и возврат PDF. Давайте начнем.

Azure App Service Web App

Azure App Services позволяет работать с множеством типов приложений: Web, Mobile, Logic (предварительная версия) и API. Вся инфраструктура App Services функционирует так же, как и серверная часть, обеспечивая дополнительные конфигурируемые возможности на клиентской стороне. Здесь я имею в виду, что в App Services возможно выполнение по разным тарифным планам (Free, Shared, Basic, Standard и Premium) и с разными размерами экземпляров (F1–P4); подробнее см. по ссылке bit.ly/1CVtRec. В тарифы заложены такие характеристики, как слоты развертывания, ограничения дискового пространства, автоматическое масштабирование, максимальное количество экземпляров и т. д., тогда как размеры экземпляров описывают количество выделенных процессоров, а также объем памяти на каждый App Service Plan (ASP), который эквивалентен виртуальной машине (VM). Что касается клиентской части, то характеристики данного App Service обеспечивают конкретные возможности для конкретного типа App Service, чтобы ваше приложение можно было развернуть, сконфигурировать и запустить в кратчайшие сроки.

Для конвертера HTML-to-PDF я буду использовать S2 Azure App Service Web App, так как мне не нужна функциональность, предоставляемая другими типами App Service.

Для начала создайте веб-приложение на портале Azure, выбрав New | Web + Mobile | Web App, затем заполните поля App name, Subscription, Resource Groups и App Service Plan и щелкните кнопку Create. Создав приложение, вы используете это место для развертывания исходного кода, содержащегося в скачиваемом решении Visual Studio 2015 — convertHTMLtoPDF. Подробности развертывания даны в конце этой статьи; вам понадобится внести некоторые изменения, чтобы данный код работал с вашими веб-приложением и WebJob.

Приложения Web, Mobile и API включают федеративную идентификацию, основанную на настройке аутентификации и авторизации с помощью Azure Active Directory  и других провайдеров идентификации вроде Facebook, Microsoft Live, Twitter и т. д., как обсуждается в следующем разделе.

Аутентификация и авторизация в App Service

Я решил сконфигурировать функциональность аутентификации и авторизации в App Service для своего веб-приложения, поскольку она хорошо подходит для схемы SignalR, в которой желательно наличие отображаемого имени или идентификации клиента. SignalR создает ConnectionId для каждого клиента, но при отправке или публикации сообщений предпочтительно использовать реальные имена посетителей. Это достигается захватом реального имени из обратного вызова функциональности аутентификации и последующим его отображением с использованием SignalR-кода. Когда я реализовал провайдер идентификации (identity provider, IDP) Microsoft Account, имя аутентифицированного посетителя было возвращено в заголовке X-MS-CLIENT-PRINCIPAL-NAME запроса. Имя идентификации также доступно в свойстве System.Security.Principle.IPrinciple.Identity.Name.

Для работы с функциональностью аутентификации и авторизации не нужно никаких изменений в коде серверной части приложения, и вы можете просто следовать инструкциям по ссылке bit.ly/1MQZZdF. Эта реализация требует лишь разрешить использование App Service Authentication, доступной из раздела Settings для данного App Service, и сконфигурировать один или более провайдеров аутентификации, как показано на рис. 3.

Функциональность аутентификации и авторизации в App Service

Рис. 3. Функциональность аутентификации и авторизации в App Service

Этот функционал предлагает множество вариантов для случая Action to take when request is not authorized (действие, предпринимаемое, когда запрос не авторизован). Например, чтобы обратиться к веб-приложению HTML-to-PDF, вы должны иметь учетную запись Microsoft и пройти аутентификацию через провайдер идентификации; никакой код веб-приложения не выполняется до прохождения этой IDP-аутентификации. В данном случае требуется предварительная аутентификация, поскольку я выбрал «Log in with Microsoft Account» в раскрывающемся списке. Все ресурсы App Service требуют такой аутентификации, как только применяется некое действие. Вы можете так сконфигурировать функционал аутентификации, чтобы посетители могли обращаться к странице входа или другим конечным точкам Azure App Service, для чего из раскрывающегося списка следует выбрать элемент Allow request (no action). Но тогда за ограничение доступа к защищаемым страницам будет отвечать код самого приложения. Этот более детализированный подход обычно реализуется проверкой булева значения Context.User.Identity.IsAuthenticated до запуска кода внутри страницы.

Последний компонент решения для преобразования HTML-to-PDF реального времени без использования кода — создание и настройка учетной записи и контейнера Azure Storage.

Azure Storage

Контейнер Azure Storage — место, где PDF-файл хранится для скачивания. Если контейнер хранилища сделан общедоступным, к файлам в этом контейнере может обращаться кто угодно, ссылаясь на них по URL вида https://{storage-account}.blob.core.windows.net/{container-name}/{filename.pdf}. Вставка, обновление или удаление файлов из контейнера требует ключа доступа при выполнении этих операций из кода. Выполнение тех же операций через Azure Management Portal или из Visual Studio можно ограничить, используя управление доступом на основе ролей (role-based access control, RBAC) или просто запрещая доступ пользователям к данной подписке Azure.

Чтобы создать учетную запись хранилища, выберите New | Data + Storage и учетную запись хранилища. Атрибут Name становится учетной записью хранилища, где создается контейнер, и первая часть URL выглядит так: https//{storage-account}.blob.core.windows.net. Атрибут Deployment model позволяет выбирать либо Resource manager, либо Classic. Если только у вас нет приложений, развернутых в классической виртуальной сети (VNET), рекомендуется использовать Resource manager для всех новых развертываний. Применение Azure Resource Manager (ARM) — это более декларативный подход, при котором используются шаблоны и скрипты. Напротив, взаимодействие с моделью Classic, обычно называемой Azure Service Manager (ASM), обычно осуществляется с помощью кода и библиотек.

Решая, выбрать Standard или Premium Performance, примите во внимание стоимость и пропускную способность. Standard — самый экономичный и оптимальный вариант для приложений, которые нечасто обращаются к массивам данных. Хранилище в варианте Premium поддерживается твердотельными накопителями (SSD), которые обеспечивают оптимальную производительность для виртуальных машин с интенсивным вводом-выводом.

Атрибут Replication имеет ряд значений (Local, Zone, Global и Read-Access Global), каждое из которых соответственно повышает уровни избыточности и доступности. Я использовал настройки по умолчанию для решения HTML-to-PDF и выбрал те же Subscription, Resource group и Location, что и для ранее созданного веб-приложения.

Наконец, после успешного создания учетной записи хранилища выберите сервисы Blobs на вкладке General учетной записи Storage, а затем добавьте контейнер.

Параметр Access Type в колонке (blade) нового контейнера может быть либо Private (для всех операций нужен ключ доступа), Blob (разрешается открытый доступ для чтения) или Container (разрешается открытый доступ для чтения и перечисления).

Вот и все, что требует конфигурирование Azure для этого решения. Теперь давайте перейдем к коду на C#, чтобы понять, как работает преобразование HTML в PDF реального времени.

Azure WebJob

Azure WebJob поддерживает выполнение скрипта или исполняемого файла в нескольких режимах: постоянном, инициируемом по триггеру или по расписанию (bit.ly/1Og9P95). Не путайте эту функциональность с Windows-службой; рассматривайте ее как задачу или пакетное задание, которое нужно выполнять в определенное время или при появлении определенного события. В данном случае использование средства для преобразования HTML в PDF реального времени инициирует запуск WebJob через API. В качестве альтернативы WebJobs можно запускать вручную через Visual Studio или с помощью Azure Scheduler Job Collections.

Платформа Azure App Service определяет, является WebJob инициируемым триггером или постоянным согласно пути, по которому хранится WebJob. Если WebJob должен быть инициируемым по триггеру, его следует развернуть в каталоге d:\home\site\wwwroot\app_data\jobs\triggered\{job name}; если он должен быть постоянным, просто замените в пути triggered на continuous. Чтобы развернуть WebJob, добавьте каталог app_data\jobs\triggered\{job name} в проект веб-сайта в Visual Studio, добавьте в него скрипт или исполняемый файл по аналогии с тем, как описано по ссылке bit.ly/1Uczf8L, и опубликуйте его в Azure App Service.

Созданный мной WebJob выполняет две задачи, преобразуя страницу по заданному адресу в PDF-файл и загружая этот PDF-файл в контейнер внутри Azure Storage. Я мог бы вызывать wkhtmltopdf.exe напрямую через WebJob API, но тогда мне понадобился бы второй API-вызов для загрузки файла в хранилище, и это потребовало бы уймы сложностей в управлении файлом и отправке результата обратно клиенту. Поэтому я создал консольное приложение convertToPdf (которое вы увидите в сопутствующем исходном коде), которое выполняет эти две задачи, одну за другой, и возвращает местонахождение PDF-файла клиенту, сделавшему запрос.

Чтобы запустить wkhtmltopdf.exe и передать ей в двух обязательных параметрах веб-адрес и имя PDF-файла, я использовал System.Diagnostics.ProcessStartInfo (рис. 4).

Рис. 4. Запуск wkhtmltopdf.exe

static void Main(string[] args)
{
  var URL = args[0];
  var filename = args[1];
  try
  {
    using (var p = new System.Diagnostics.Process())
    {
      var startInfo = new System.Diagnostics.ProcessStartInfo
      {
        FileName = "wkhtmltopdf.exe",
        Arguments = URL + " " + filename,
        UseShellExecute = false
      };
      p.StartInfo = startInfo;
      p.Start();
      p.WaitForExit();
      p.Close();
    }
  }
  catch (Exception ex) { WriteLine(
    $"Something Happened: {ex.Message}");
  }
}

Код создает экземпляр класса ProcessStartInfo и задает FileName, Arguments и другие свойства этого класса. Затем метод запускает процесс, идентифицируемый свойством FileName, ожидает его завершения и закрывает процесс. По умолчанию, когда WebJob загружается в среду Azure App Service, он копируется платформой во временный локальный каталог D:\local\temp\jobs\triggered\{job name}\*****\, где ***** — динамически генерируемое имя каталога. В этом месте PDF-файл физически хранится до загрузки в контейнер Azure Storage. Поскольку файл исключительно локальный, на этот момент он не сохранен и недоступен никакому другому экземпляру Azure App Service Web App. Если вы работаете с несколькими экземплярами, то, возможно, не увидите его в этом локальном каталоге, но в контейнере Azure Storage он уже будет глобально доступен.

После создания PDF-файл нужно загрузить в контейнер Azure Storage. Вы найдете отличное учебное пособие, в котором подробно описывается, как это сделать, по ссылке bit.ly/1OAXIQ0. Вкратце, возможность создания, чтения, обновления и удаления контента в контейнере контролируется двумя NuGet-пакетами: Microsoft Azure Configuration Manager Library for .NET и Microsoft Azure Storage Client Library for .NET. Консольное приложение convertToPdf ссылается на оба пакета. Щелкните правой кнопкой мыши проект консольного приложения и выберите Manage NuGet Packages. Затем найдите эти библиотеки и установите их.

Я использовал метод CloudConfigurationManager.GetSetting, который является частью библиотеки Microsoft Azure Configuration Manager, для извлечения значений из строки подключения к хранилищу для соединения с контейнером Azure Storage. Значениями являются AccountName (имя учетной записи Azure Storage, а не имя контейнера, в данном случае — converthtmltopdf) и AccountKey, которое извлекается из колонки Storage Account щелчком Settings | Access keys. На рис. 5 показано, как загрузить PDF-файл в ранее созданный контейнер Azure Strorage.

Рис. 5. Загрузка PDF в контейнер Azure Storage

static void Main(string[] args)
{
  try
  {
    CloudStorageAccount storageAccount =
      CloudStorageAccount.Parse(
      CloudConfigurationManager.GetSetting(
      "StorageConnectionString"));
    CloudBlobClient blobClient =
       storageAccount.CreateCloudBlobClient();
    CloudBlobContainer container =
      blobClient.GetContainerReference("pdf");
    CloudBlockBlob blockBlob =
      container.GetBlockBlobReference(filename);
    using (var fileStream = System.IO.File.OpenRead(filename))
    {
      blockBlob.UploadFromStream(fileStream);
    }
  }
  catch (StorageException ex) { WriteLine(
    $"StorageException: {ex.Message}");
  }
  catch (Exception ex) { WriteLine(
    $"Exception: {ex.Message}");
  }
}

Эта конфигурационная информация используется как ввод для класса CloudStorageAccount, который является частью библиотеки Microsoft Azure Storage Client. В качестве альтернативы CloudConfigurationManager для получения StorageConnectionString из файла App.config можно использовать System.Configuration.ConfigurationManager.AppSettings["StorageConnectionString"].

Я применяю экземпляр класса CloudStorageAccount для создания CloudBlobClient, затем использую blobClient, чтобы получить ссылку на контейнер Azure Storage с помощью метода GetContainerReference. Затем, вызывая метод GetBlockBlobReference класса CloudBlobContainer, я создаю CloudBlockBlob, содержащий имя загружаемого файла. Оба исполняемых файла, как уже отмечалось, находятся в каталоге D:\local\temp\jobs\triggered\convertToPdf\***\ — в том же месте, где хранится PDF-файл. Вот почему дополнять имя файла путем к нему не требуется — файл создается в том же временном каталоге, что и исполняемые файлы. Наконец, я передаю экземпляр System.IO.FileStream методом System.IO.File.OpenRead и загружаю этот экземпляр в контейнер вызовом метода UploadFromStream класса CloudBlockBlock.

Закончив код и скомпилировав его, добавьте wkhtmltopdf.exe и convertToPdf.exe в каталог \app_data\jobs\triggered\convertToPdf решения Visual Studio, которое будет опубликовано в Azure App Service Web App. Кроме того, можно публиковать только файлы WebJob, используя FTP-утилиту, передающую код непосредственно на веб-сайт.

Теперь, когда мы закончили convertToPdf WebJob, который создает и сохраняет PDF, давайте рассмотрим, как вызвать WebJob из кода на C#, используя HttpClient. После этого останется лишь создать клиентскую часть Azure App Service Web App на основе SignalR, чтобы посетитель мог отправлять URL в WebJob и получать обратно URL для скачивания PDF.

Azure WebJob API

В свое время я написал статью об Azure WebJob API (bit.ly/1SD9gVJ), в которой обсудил, как вызывать этот API, инициирующий выполнение WebJob. По сути, WebJob API — это веб-интерфейс, который выполняет скрипт или исполняемый файл, используя аргументы, переданные в URL.

До создания SignalR Hub, который инициирует WebJob API, я создал простое консольное приложение-потребитель (рис. 6), вызывающее WebJob API. Оно включено в сопутствующее решение и называется convertToPDF-consumer. Это консольное приложение упростило кодирование, анализ проблем и тестирование, так как исключило SignalR из сценария.

Рис. 6. Простое консольное приложение-потребитель

static async Task<string> ConvertToPDFWebJobAPIAsync(
  string Url)
{
  try
  {
    using (var client = new HttpClient())
    {
      client.BaseAddress = new Uri(
        "https://converthtmltopdf.scm.azurewebsites.net/");
      client.DefaultRequestHeaders.Accept.Clear();
      var userName = "your userName";
      var password = "your userPWD ";
      var encoding = new ASCIIEncoding();
      var authHeader =
        new AuthenticationHeaderValue("Basic",
          Convert.ToBase64String(
          encoding.GetBytes(string.Format(
          $"{userName}:{password}"))));
      client.DefaultRequestHeaders.Authorization = authHeader;
      var content = new System.Net.Http.StringContent("");
      string filename = Guid.NewGuid().ToString("N").
        Substring(0, 8) + ".pdf";
      HttpResponseMessage response =
        await client.PostAsync(
        $"api/triggeredwebjobs/convertToPDF/run?arguments={Url}
          {filename}", content);
      if (!response.IsSuccessStatusCode)
      {
        return $"Conversion for {Url} {filename} failed: " +
          DateTime.Now.ToString();
      }
      return $"{response.StatusCode.ToString()}:
        your PDF can be downloaded from here:";
    }
  }
  catch (Exception ex)  {  return ex.Message;
  }
}

Для выдачи запроса вызовите метод HttpClient класса System.Net.Http.HttpClient. Затем используйте URL на основе Source Control Management (SCM) для Azure App Service Web App в качестве свойства BaseAddress запроса. Как вы, вероятно, знаете, каждое Azure App Service Web App поставляется с SCM URL (также известного как консоль KUDU), который доступен через https://{appname}.scm.azurewebsites.net и является URL, применяемым для вызова WebJob API. Добавление /basicAuth в конец URL позволяет вызвавшему клиенту аутентифицироваться, используя базовый механизм вызова и ответа (challenge and response). Параметры userName и password — это удостоверения Publish Profile, которые скачиваются с Azure Management Portal переходом в Azure App Service Web App и выбором Get publish profile. В скачанном файле *.PublishSettings вы найдете userName и userPWD для использования в коде. Простоты ради я «зашил» имя пользователя и пароль в код приложения, но в реальном приложении их нужно поместить в безопасное место вне кода, чтобы их можно было изменять при необходимости, выбирая Reset publish profile в Azure Management Portal. Вы вряд ли захотите развертывать обновленный код всякий раз, когда в нем что-то меняется.

Базовая аутентификация требует сопоставления ASCII-строки, кодированной по основанию Base64 и содержащей userName и password, с заголовком Basic в формате Basic userName:password. Создав значение заголовка с помощью метода ASCIIEncoding класса System.TextASCIIEncoding и метода ToBase64String класса System.Convert, добавьте его в новый экземпляр класса System.Net.Http.Headers.AuthenticationHeaderValue вместе с именем заголовка Basic. Используйте экземпляр класса System.Net.Http.HttpClient, созданного в выражении using для добавления AuthenticationHeaderValue к свойству DefaultRequestHeaders.Authorization класса System.Net.Http.Headers.HttpRequestHeaders.

В качестве имени файла я взял восемь символов GUID, используя метод Substring класса String, удалив дефисы из GUID. Этот GUID был создан методом NewGuid класса System.Guid с передачей параметра N методу ToString класса Guid. Наконец, я выполнил асинхронную передачу в WebJob API, используя метод PostAsync класса System.Net.Http.HttpClient, который принимает URL и имя файла как аргументы WebJob, и ожидая его завершения. После успешного окончания процесса URL контейнера Azure Storage с присоединенным к нему именем файла отображается в консоли, а иначе отправляется уведомление о том, что создать PDF не удалось.

Чтобы увидеть состояние WebJob, на Azure Management Portal перейдите к Azure App Service Web App, выполняющему WebJob, и выберите Settings | WebJobs. В колонке WebJobs содержатся Name, Type, Status и очень полезная ссылка на журналы выполнения WebJob. Щелкните эту ссылку, чтобы обратиться к KUDU-консоли, специфичной для этого WebJob, и увидеть недавние сеансы выполнения задания, их состояние и ссылку на журнал вывода от WebJob (рис. 7). Например, если WebJob является консольным приложением, то, когда вы используете метод System.Console.WriteLine для записи состояния выполнения в консольное окно вывода, эта информация также записывается в журнал WebJob, и ее можно просмотреть с помощью ссылки на Azure Management Portal.

Журнал вывода Azure WebJob

Рис. 7. Журнал вывода Azure WebJob

Как только эта часть стала работать, как ожидалось, все остальное свелось к простому копированию и вставке в решение SignalR, и об этом я расскажу в следующем разделе.

ASP.NET SignalR

ASP.NET SignalR — библиотека с открытым исходным кодом для ASP.NET-разработчиков, которая упрощает отправку уведомлений в реальном времени клиентам на основе браузеров, мобильным или .NET-клиентам. RPC-вызов (remote procedure call) сервером клиента использует API, который вызывает JavaScript-функции на клиенте из .NET-кода на серверной стороне. До появления этой технологии аналогичное решение реализовали с применением ASP.NET-элемента управления UpdatePanel, который часто обновлял сам себя, выдавая запрос серверу на проверку того, были ли какие-то изменения в состоянии данных. Это был в гораздо большей степени подход, где получение информации инициировалось клиентом, который запрашивал сервер вместо того, чтобы сам сервер передавал клиенту данные в реальном времени по мере их появления.

JavaScript-код на клиентской стороне создает экземпляр прокси Hub, предоставляет методы, которые могут быть вызваны сервером, и идентифицирует серверный метод для вызова (Send) при появлении события щелчка:

var pdf = $.connection.pDFHub;
pdf.client.broadcastMessage = function (userId, message) {};
pdf.client.individualMessage = function (userId, message) {};
$('#sendmessage').click(function () {
  pdf.server.send($('#displayname').val(),
  $('#message').val());
});

Имя прокси Hub в клиентском JavaScript — это имя Hub, созданного для выполнения на серверной стороне; в данном примере Hub называется PDFHub и наследует от класса Microsoft.AspNet.SignalR.Hub. Клиентом предоставляются два метода: broadcastMessage и individualMessage; каждый из них имеет функцию с параметрами, которые совпадают с шаблоном серверного метода Send, userId и message. Метод Send вызывается на сервере, когда посетитель в веб-приложении щелкает кнопку отправки. Метод ConvertToPDFWebJobAsync является результатом копирования и вставки консольного приложения, созданного в предыдущем разделу; он вызывает Azure WebJob API для преобразования переданной веб-страницы в PDF-файл и загрузки этого файла в контейнер Azure Storage. Наконец, серверный метод Send использует экземпляр свойства Microsoft.AspNet.SignalR.Hub.Clients, которое реализует интерфейс IHubCallerConnectionContext. Свойство Clients связывается с двумя клиентскими методами и предоставляет информацию, отправленную с сервера соответствующим клиентам (рис. 8).

Рис. 8. Класс PDFHub

public class PDFHub : Hub
{
  public void Send(string userId, string message)
  {
    string name = Context.User.Identity.Name;
    string convertMessage = "no message yet";
    Task.Run(async () =>
    {
      convertMessage =
        await ConvertToPDFWebJobAPIAsync(message);
    }).Wait();
    Clients.All.broadcastMessage(userId, "just converted: " +
      message + " to a pdf");
    Clients.Client(Context.ConnectionId).individualMessage(
      name, convertMessage);
  }
}

Возможно, вам интересно, почему я выбрал SignalR для потребления Azure WebJob API вместо простого приложения ASP.NET Web Forms или веб-приложения ASP.NET MVC. Это верно, что есть разные способы потребления API. Так, в сопутствующем коде для этого решения содержится консольное приложение, которое потребляет Azure WebJob API, — почему же я использую SignalR?

Чтобы ответить на этот вопрос, обратите внимание, что в коде на рис. 8, когда у сервера появляется сообщение для подключенных клиентов, вызываются два клиентских метода. Первый метод, broadcastMessage, уведомляет все подключенные клиенты о том, что конкретное лицо преобразовало данный URL в PDF, но не предоставляет ссылку на контейнер Azure Storage и на PDF-файл для скачивания. Второй клиентский метод, individualMessage, передает состояние операции конвертации HTML в PDF и ссылку на контейнер Azure Storage с добавленным именем PDF-файла. Причина для использования SignalR в том, чтобы дать клиентам эквивалент взаимодействия в пользовательской среде социальных сетей, сообщая всем подключенным клиентам информацию о том, что происходит в веб-приложении Azure App Service.

Вспомните: я упоминал о System.Security.Principle.IPrinciple.Identity.Name и отмечал, насколько он делает веб-приложение дружелюбнее, так как способен визуализировать для клиента имя посетителя вместо, например, уникального, но обобщенного connectionId. Свойство Context.User.Identity.Name позволяет указывать имя посетителя, проверяемое по его учетной записи Microsoft.

Теперь остается лишь развернуть код (клиентский, серверный и Azure WebJob) на платформе Azure App Service Web App с помощью Visual Studio или FTP-приложения и протестировать его. Подробные инструкции о развертывании в Azure App Service Web App см. по ссылке bit.ly/1nXnhmB.

SaaS

При написании этой статьи я задумался о SaaS (Software as a Service) и о том, является этот конвертер HTML-to-PDF реального времени решением SaaS или просто облачным приложением, доступным через API. Я решил, что предоставление Azure WebJob API по одному только названию делает его API, а не SaaS. В случае моего решения WebJob API предоставляется через URL и защищается базовой аутентификацией. API доступен другим потребителям, которые можно создавать поверх него или добавлять его функциональность в свои приложения, а это является определением API. Однако, когда появляется потребитель API, к этому API могут добавлять дополнительные средства, доступные множеству онлайновых пользователей, и тогда он подпадает под определение SaaS. Поэтому сам по себе Azure WebJob API является просто API, а мой клиент ASP.NET SignalR, выполняемый на платформе Azure App Service Web App, — это уже SaaS-решение. Конечно, у него нет масштабов OneDrive, Office 365, CRM Dynamics Online или Hotmail, но, если вам понадобится по-настоящему быстро преобразовать целый веб-сайт в PDF, вы теперь знаете, что нужно использовать.

Заключение

В этой статье мы исследовали Azure App Service Web App, аутентификацию и авторизацию в Azure Service, а также контейнер и учетную запись Azure Storage. Эти компоненты образуют платформу, которая поддерживает Azure WebJob, предоставляет Azure WebJob API и является хостом потребителя ASP.NET SignalR на основе браузера. Я обсудил каждый из этих компонентов и их конфигурирование. Я также описал код для Azure WebJob, код для вызова Azure WebJob API и клиент ASP.NET SignalR.


Бенджамин Перкинс (Benjamin Perkins) — инженер по координации технической поддержки (escalation engineer) в Microsoft и автор четырех книг по C#, IIS, NHibernate и Microsoft Azure. Соавтор книги «Beginning C# 6 Programming with Visual Studio 2015» (John Wiley & Sons). С ним можно связаться по адресу benperk@microsoft.com.

Выражаю благодарность за рецензирование статьи эксперту Microsoft Ричарду Марру (Richard Marr).