При создании мобильных приложений, интегрируемых с SharePoint, можно использовать различные подходы. В данной статье я расскажу о том, как на базе веб-службы API ASMX выполнить интеграцию поиска, и продемонстрирую операции с данными — создания, чтения, обновления и удаления - в списке данных SharePoint, используя API REST. Кроме того, я представлю несколько дополнительных методов аутентификации, которые используют стандартную страницу регистрации для аутентификации на базе форм в веб-приложении SharePoint.
Подходы к аутентификации
В тестовом приложении я покажу, как использовать PhoneGap, jQuery, jQuery Mobile, JavaScript и HTML5 для мобильного приложения, так, чтобы приложение выполняло операции модификации со списком данных SharePoint, используя механизм REST. Я продемонстрирую, как это будет работать на устройствах Windows Phone, iPhone, iPad и Android.
Когда вы используете API REST для получения доступа к списку данных SharePoint, вы должны аутентифицировать себя для сервера SharePoint, прежде чем начнете использовать API REST. Существуют два способа это сделать. Первый заключается в использовании authentication.asmx из веб-службы для передачи учетных записей на сервер и извлечении cookie-файла, который будет применяться для аутентификации будущих запросов.
Второй подход заключается в попытке выполнить вызов REST API и проконтролировать результат, определив, аутентифицирован ли пользователь. Если пользователь не был аутентифицирован, вы перенаправляете его на страницу регистрации в SharePoint, где он может ввести свои учетные данне. Если пользователь был аутентифицирован, вы обрабатываете результаты вызова в REST API. На рисунке 1 показан пример приложения. Заметьте, что нижняя кнопка называется Get Items With Redirect. Эта кнопка выполняет действия, которые я только что описал и перенаправляет пользователя на страницу регистрации SharePoint, если он не был аутентифицирован.
Рисунок 1. Главная страница мобильного приложения |
Чтобы создать кнопку, добавьте такой код к функции onDeviceReady в файле init.js:
$(«#topnavcontent»).append(«Get Items With Redirect»);
После этого добавьте код в файл main.html, чтобы вывести результат.
Когда вы нажимаете кнопку Get Items With Redirect, вызывается функция GetItemsREST. Внутри этой функции, показанной в листинге 1, я сначала очистил представление списка на странице, которая обычно используется для демонстрации результатов вызова API. Затем я задействовал функцию jQuery getJSON для запроса программного интерфейса REST API, чтобы он возвращал элементы из списка Ski Resorts SharePoint. Заметьте, что функция обратного вызова RESTGetItemsError регистрируется в случае ошибки.
Функция обратного вызова RESTGetItemsError, показанная в следующем примере, определяет, возникает ли ошибка, поскольку пользователь еще не аутентифицирован, а это необходимо для перенаправления пользователя на страницу регистрации SharePoint.
function processRESTGetItemsError(xhr) { if (xhr.status == 12150 || xhr.status == 302) { window.location = «http://server/_forms/default.aspx?ReturnUrl=«+ "http://server/SitePages/MobileRedirect.aspx»; } }
Если статус объекта XMLHttpRequest 12150 или 302, мы можем сказать, что пользователь не был аутентифицирован. Если это тот самый случай, пользователь перенаправляется на страницу регистрации на сервере SharePoint.
Обратите внимание на параметр ReturnUrl QueryString, который передается на страницу регистрации. Этот параметр QueryString инструктирует SharePoint, и служба направляет пользователя на веб-страницу, после того как тот введет учетные данные в форме регистрации и успешно аутентифицируется. Это небольшой трюк, поскольку параметр ReturnUrl может быть использован только для перенаправления пользователей на страницу веб-сайта SharePoint. Однако мы хотим перенаправлять пользователей обратно на страницу в нашем мобильном приложении, которое запускается внутри веб-браузера.
Итак, как можно перенаправить пользователей обратно на соответствующую страницу в мобильном устройстве при использовании данного метода? Есть несколько вариантов, которые вы можете использовать. Первый заключается в том, чтобы внедрить какой-нибудь код JavaScript внутрь веб-страницы SharePoint, на которую вы перенаправляете своего пользователя после успешной регистрации. Этот внедренный код JavaScript используется для перенаправления пользователя на страницу, на которую он должен попасть в своем мобильном приложении. Для завершения операции я создал новую страницу SharePoint и назвал ее mobileredirect.aspx. Затем я создал файл JavaScript для перенаправления пользователя на страницу в мобильном приложении и загрузил файл JavaScript на веб-сайт SharePoint:
Наконец, на странице mobileredirect.aspx я добавил часть Content Editor Web Part и настроил ее так, чтобы она загружала файл JavaScript, указав URL к файлу JavaScript в текстовом поле Content Link на панели инструментов Content Editor Web Part. В данном случае, после того как пользователь аутентифицируется и будет перенаправлен на страницу mobileredirect.aspx, он автоматически вернется на страницу в мобильном приложении, откуда инициировал запрос. Этот сценарий, как показано на рисунке 2, требует минимума параметров и кодирования на сервере SharePoint.
Рисунок 2. Передача страницы при использовании регистрации SharePoint |
Однако, поскольку вы используете механизм историй в веб-браузере для навигации на две страницы обратно, данный подход можно назвать уязвимым. Выбирать ли этот подход для развертывания в своем приложении – решать вам. Но если вы хотите тщательно отладить свои приложения, необходим более жизнеспособный метод. Для тестирования данный подход является целесообразным, поскольку запуск может занять всего несколько минут.
В этом месте, когда вызов REST API повторяется, функция обратного вызова в GetItemsREST обрабатывает список элементов SharePoint и показывает их на странице, как мы видим на рисунке 3. Данные здесь выглядят так же, как и в списке Ski Resorts в SharePoint, см. рисунок 4.
Рисунок 3. Список элементов SharePoint, которые выводятся на?странице после успешного вызова REST API |
Рисунок 4. Появление данных в списке Ski Resorts в?SharePoint |
Нажатие кнопки Authenticate вызывает веб-службу authentication.asmx, а затем перенаправляет пользователя в главное меню приложения, показанное на рисунке 5.
Рисунок 5. Перенаправление пользователя в?главное меню приложения |
Анализ приложения
Первое, что вы заметите в приложении, это кнопка Search. Нажатие кнопки Search загружает страницу поиска, куда вы вводите искомый термин. Когда вы щелкаете по кнопке Search, вызывается функция SearchSharePoint, показанная в листинге 2. Эта функция использует jQuery Ajax для выполнения операции POST по отношению к веб-службе search.asmx в SharePoint. Функция SearchSharePoint вызывает функцию escapeQueryPacket, чтобы избежать символов в queryXml, который пересылается дальше:
function escapeQueryPacket(queryPacketXML) { return queryPacketXML.replace(/&/g, '&').replace(/«/g, '»').replace(//g, '>'); }
После завершения запроса веб-службы search.asmx в SharePoint функция processSearchResultSuccess, показанная в листинге 3, просматривает результаты и добавляет их к представлению списка jQuery Mobile.
Функцию поиска в действии вы можете видеть на рисунке 6, где показан результат поиска для слова Monarch так, как он появляется в iPhone.
Рисунок 6. Результат поиска на смартфоне для?слова Monarch |
Еще одна вещь, которую вы заметите в главном меню на рисунке 5 – это кнопка REST Operations. Нажатие кнопки REST Operations загружает функции, которые поддерживают операции REST и работают почти так же, как и их копии ASMX.
Функция GetItems, которая показана в листинге 4, отыскивает все элементы из списка Ski Resorts. Функция GetItems использует ListData.svc REST API для запроса списка Ski Resorts. Функция getJSON jQuery применяется для того, чтобы сделать запрос JSON службе. Я использую функциональность jQuery Ajax для обновления элементов списка при помощи REST API. ID для редактирования элементов списка передается в файл ListData.svc для того, чтобы идентифицировать элемент, подлежащий обновлению.
Теперь давайте посмотрим на функцию beforeSendFunction, показанную в листинге 5, который регистрируется при помощи запроса Ajax. Переменная ETag была установлена запоминанием переменной result.__metadata.etag, возвращаемой для каждого элемента списка методом GetItems. Переменная ETag показывает текущую версию элемента на сервере, когда отыскивается элемент списка. Интерфейс REST использует etags для осуществления контроля взаимосовместимости и устанавливает, что версия сервера изменилась за период, пока клиент искал элемент и пока он его обновлял. Если версия изменилась, служба ListData.svc откажет в обновлении.
Также заметьте, что заголовок запроса инструктирует службу ListData.svc на предмет использования метода HTTP MERGE для обновления элемента. Применение глагола MERGE означает, что служба REST должна обновить только поля, которые включены в запрос. Однако реальный запрос Ajax посылается серверу как глагол POST, для того чтобы не допустить блокировки запроса правилами брандмауэра, поскольку правила брандмауэра часто блокируют HTTP-запросы, которые используют длинные глаголы, такие как MERGE.
Чтобы создать список элементов с помощью REST API, мы снова начинаем использовать функциональность jQuery Ajax. Объект listItemData JSON, показанный в листинге 6, содержит значения столбца, которые передаются в файл ListData.svc для создания нового элемента.
В данном примере я создаю только столбец под названием Title. Содержимое объекта JSON, которое представляет собой новый элемент списка, подается последовательно в виде строки и добавляется к запросу Ajax. На рисунке 7 видно, как страница Add A New Resort выглядит на iPhone.
Рисунок 7. Добавление страницы A New Resort на iPhone |
Удаление элемента при помощи REST API – простой процесс, он показан в листинге 7.
И вновь мы используем jQuery Ajax для вызова службы ListData.svc. ID передается в URL запроса, а тип устанавливается как DELETE.
Другой новой кнопкой, которую вы увидите в главном меню, является кнопка Excel Services. Нажатие Excel Services активирует приложения Excel Services REST API для показа диаграммы из рабочей книги Microsoft Excel, которая хранится на сервере SharePoint. Рабочая книга Excel, которую можно просмотреть в веб-браузере, как показано на рисунке 8, содержит ежегодный объем данных для Breckenridge Ski Resort и создает столбчатую диаграмму по этим данным.
Рисунок 8. Обзор рабочей книги Microsoft Excel в веб-браузере |
Чтобы показать диаграмму в мобильном приложении, я просто добавил img-элемент к HTML, который включает в себя веб-страницу, добавил настройку src-атрибута для использования веб-страницы ExcelRest.aspx, чтобы указать рабочую книгу Excel и диаграмму внутри ее. Атрибут также определяет формат образа.
Обратите внимание, что когда рабочая книга открыта в клиентском приложении Excel, имя диаграммы — Chart 1, как показано на рисунке 9. Это значение, которое используется атрибутом src для img-элемента в мобильном приложении источника HTML веб-страницы.
Рисунок 9. Рабочая книга Microsoft Excel, открытая в клиентском приложении Excel Client App |
Также заметьте, что когда рабочий журнал просматривается в клиентском приложении Excel, панели в диаграмме являются трехмерными цилиндрами. Однако когда панели визуализируются в мобильном устройстве или веб-браузере, они выглядят как плоские прямоугольники. Это не ограничение мобильного устройства — так Excel Services визуализирует диаграммы. На рисунке 10 видно, что диаграмма выглядит так, как будто она просматривается в мобильном устройстве Windows Phone.
Рисунок 10. Диаграмма Excel, просматриваемая в мобильном устройстве Windows Phone |
Это такая простая модель, что вы можете представить себе, насколько легко создавать приложения с панелями при помощи служб Excel.
Многосторонние службы
Кроме использования веб-служб ASMX для взаимодействия со списками SharePoint, вы можете задействовать их для поиска данных на сайте SharePoint и показа диаграмм из служб Excel. Существует и ряд других веб-служб ASMX, которые также могут взаимодействовать при использовании многих из описанных в данной статье моделей.
Кроме того, REST API поддерживают множество операций со списками данных SharePoint. И как вы могли заметить, страница регистрации на сайте SharePoint также может применяться для аутентификации пользователей.
Листинг 1. Опрос списка элементов при помощи REST API
function GetItemsREST() { $(«#listview»).empty(); $.getJSON(«http://server/_vti_bin/ListData.svc/SkiResorts», function (data) { $.each(data.d.results, function (key, result) { var output = « » + «"onclick=»ShowDetails('«+ result.Id + »','«+ result.Title + »','«+ escape(result.__metadata.etag) + »'); return false;«>» + result.Title + «" + » «; $(»#listview«).append(output); $(»#listview«).trigger(»create«); }); }).error(processRESTGetItemsError); }
Листинг 2. Поиск элементов в SharePoint
function SearchSharePoint(searchTerm) { var queryPacket = escapeQueryPacket(»«+ »«+ »«+ »«+ searchTerm +»«+ »«+ »«+ »«); var searchXML = » xmlns:xsd=«http://www.w3.org/2001/XMLSchema» xmlns:soap=«http://schemas.xmlsoap.org/soap/envelope/»>«+ »«+ »«+ »«+ queryPacket + »«+ »«+ »«+ »«; $.ajax({ url:»http://server/_vti_bin/search.asmx«, type:»POST«, dataType:»xml«, data: searchXML, complete: processSearchResultSuccess, contentType:»text/xml;charset='utf-8'«, error: processSearchResultError }); } function processSearchResultError(xData, status) { var errorMessage = (xData.status +» «+ xData.statusText +» «+ xData.responseText); $(»#errorContent«).empty(); $(»#errorContent«).append(»An error has occurred: «+ errorMessage +» «); $(»#errorContent«).trigger(»create«);
Листинг 3. Обработка результатов поиска
function processSearchResultSuccess(xData, status) { $(»#listview«).empty(); var output =»«; $(xData.responseXML).find(»QueryResult«).each(function () { var queryResult = $(xData.responseXML).find(»QueryResult«).text(); var xmlDoc = $.parseXML(queryResult); var $xml = $(xmlDoc); $xml.find(»Document«).each(function () { var title = $(»Title«, $(this)).text(); var url = $(»Action>LinkUrl«, $(this)).text(); var description = $(»Description«, $(this)).text() var output =»«+ » «onclick=»ShowDetails('«+ url +»','«+ description + »'); return false;«>» + title + «" + » «; $(»#listview«).append(output); }); }); $(»#listview«).listview(»refresh«); }
Листинг 4. Запрос элементов списка
function GetItems() { $(»#listview«).empty(); $.getJSON(»http://server/_vti_bin/ListData.svc/SkiResorts«, function (data) { $.each(data.d.results, function (key, result) { var output =»«+ » «onclick=»ShowDetails('«+ result.Id + »','«+ result.Title + »','«+ escape(result.__metadata.etag) + »'); return false;«>» + result.Title + «" + » «; $(»#listview«).append(output); $(»#listview«).listview(»refresh«); }); }); } function processGetItemsResultError(xData, status) { $(»#errorContent«).empty(); $(»#errorContent«).append(»Get Items Result:«+ status +» «); $(»#errorContent«).trigger(»create«); }
Листинг 5. Обновление списка элементов
function Update(ID, Title, ETag) { var beforeSendFunction; var listItemData = {}; url =»http://server/_vti_bin/listdata.svc/SkiResorts«+ "(» + ID + «)"; beforeSendFunction = function (xhr) { xhr.setRequestHeader(»If-Match«, unescape(ETag).replace(»~«, "/»)); xhr.setRequestHeader(«X-HTTP-Method», 'MERGE'); } listItemData.Title = Title; var data = JSON.stringify(listItemData); $.ajax({ type: 'POST', url: url, contentType: 'application/json', processData: false, beforeSend: beforeSendFunction, data: data, success: function () { //Replace RESTMain.html with the page you created to execute the REST queries. window.location = «RESTmain.html»; }, error: processRESTUpdateError }); } function processRESTUpdateError(xData, status) { var errorMessage = (xData.status + «" + xData.statusText +» «+ xData.responseText); $(»#errorContent«).empty(); $(»#errorContent«).append(»An error has occurred: «+ errorMessage +» «); $(»#errorContent«).trigger(»create«); }
Листинг 6. Создание списка элементов
function Create(title) { var listItemData = {}; listItemData.Title = title; var data = JSON.stringify(listItemData); $.ajax({ type: 'POST', url: httpServerURL +»/_vti_bin/listdata.svc/SkiResorts«, contentType: 'application/json', processData: false, data: data, success: function () { window.location =»RESTmain.html«; }, error: processRESTCreateError }); } function processRESTCreateError(xData, status) { var errorMessage = (xData.status +» «+ xData.statusText +» «+ xData.responseText); $(»#errorContent«).empty(); $(»#errorContent«).append(»An error has occurred: «+ errorMessage +» «); $(»#errorContent«).trigger(»create«); }
Листинг 7. Удаление списка элементов
function DeleteItem(ID) { url = httpServerURL +»/_vti_bin/listdata.svc/SkiResorts«+ "(» + ID + «)"; $.ajax({ type: 'DELETE', url: url, contentType: 'application/json', processData: false, success: function () { window.location =»RESTmain.html«; }, error: processRESTDeleteError }); } function processRESTDeleteError(xData, status) { var errorMessage = (xData.status +» «+ xData.statusText +» «+ xData.responseText); $(»#errorContent«).empty(); $(»#errorContent«).append(»An error has occurred: «+ errorMessage +» «); $(»#errorContent«).trigger(»create"); }