ASP.NET MVC: DataSource на JavaScript для работы с Web API или снова про JsSite

ru-RU | created at: 7/13/2013 | published: 7/13/2013 | updated at: 1/1/2018 | number of views: 5997

Web API очень удобный фрэймворк, который существенно упрощает создание HTTP-сервисов доступных большому число клиентских программ включая браузеры и мобильные устройства. Цель данной статьи описать контрол DataSource, который является JavaScript-оберткой для Web API сервис в концепции ASP.NET MVC.

Описание и предназначение

DataSource – контрол написанный на языке JavaScript. Библиотека JsSite включает в себя, на мой взгляд, полезный js-контрол, который может очень просто отобразить на странице сущности полученные с Web API. При этом разбитие на странице осуществляется автоматически, а также в нем уже присутствует функционал по добавлении, удалению и редактированию сущностей в списке. Помимо того что DataSource “умеет” получать данные постранично, добавлять, удалять, обновлять сущности, еще “умеет” использовать параметризированные запросы к сервису Web API.

Конструктор

DataSource находится в пространстве имен site.controls библиотеки JsSite.

site.controls.DataSource = function (options, queryParams, aggr) {...}

Конструктор принимает по умолчанию следующие параметры:

Наименование параметра Тип Значение
options JSON для расширения или переопределения настроек контрола, которые применяются по умолчанию. См. описание ниже.
queryParams JSON Универсальный класс параметров запросов. Об этом классе будет рассказано в другой статье.
aggr JSON Json-объект, который призван пока не используется, но планируется для хранения вычисляемых агрегирующих значений “по столбцам” всего набора данных. Об этом классе будет рассказано в другой статье.

JSON-объект Options, как параметр настройки DataSource

Опции для настройки контрола DataSource помогут предопределить его поведение, в том числе и заданное по умолчанию.

Наименование параметра тип Значение по умолчанию Описание
index number 0 Индекс страницы запрашиваемый на сервера.
size number 10 Размер страницы (количество возвращаемых объектов) в запросе.
groupSize number 15 Количество кнопок страниц в пейджере.
service string null JavaScript “обертка” на Web API сервис. Смотри раздел “Сервис для DataSource”
items Array, [] null Коллекция объектов, которые требуется разбить на страницы и/или использовать для выборки (select)
autoLoad boolean true Запускает получение данных с Web API сразу после инициализации контрола.
optionsCaption JSON null Если DataSource используется для связывания с контролом <select>, то это свойство должно задавать объект для первого <option>, которое может быть использовано для выборки объектов с типом nullable.
pager JSON   Объект переопределяет настройки внутреннего контрола для настройки страниц.

JSON-объект Pager

Наименование параметра Тип Значение по умолчанию Описание
prev JSON { text: “<<”, css: “”} Текст надписи на кнопке “назад” и CSS стиль для этой кнопки.
current JSON { css: “active”} CSS стиль для выбранной кнопки.
next JSON { text: “<<”, css: “”} Текст надписи на кнопке “вперед” и CSS стиль для этой кнопки.

Свойства DataSource

Описание свойств сведу в простую таблицы. Так как контрол продолжает обрастать новыми свойствами, данная таблица будет обновляться.

Наименование свойства Тип Значение по умолчанию Описание
selectedItems ko.observableArray null Выбранные объекты в наборе полученных данных. На данный момент используется для внутренних расчетов. Планируется реализация множественного выбора.
queryParams JSON null Специализированный объект, который является универсальным классом параметров для запроса. Объект содержится в пространстве имен site.controls библиотеки JsSite.
execute JSON null Объект для хранения (вызова) методов сервиса используемого в DataSource
currentItem ko.observable null Выбранный объект
events JSON   Смотри описание событий ниже.
aggregate JSON   Зарезервированное свойство для обработки агрегирующих функций. (Реализация запланирована в следующий версии)
indicator JSON   Объект BusyIndicator из пространства имен site.controls библиотеки JsSite используется для индикации процесса обработки запроса на сервис.
hasPages boolean false После получения набора объектов принимает значение true, если количество страниц больше 1.
hasItems boolean false После получения набора объектов принимает значение true, если количество объектов для отображения больше 1.
pages ko.observableArray null Страницы сформированные контролом для отображения пейджера.
items ko.observableArray null Загруженные объекты

Методы DataSource

Наименование метода Описание
resetSelectedItem Очищает выбранный объект, устанавливая его в значение null.
postData Выполняет отправку данных на сервис Web API используя сервис (site.services.имясущности.js). Тип отправляемого запроса POST.
getData Отправляет запрос на сервер Web API для получения данных. В параметре отправки используется Options. Тип отправляемого запроса GET.
putData Тип отправляемого запроса PUT.
delData Тип отправляемого запроса DELETE.
select Помечает объект в коллекции как “selected”, а также устанавливает свойство “currentItem” этот объект.
remove Удаляет из коллекции выбранный объект (без отправки запроса на сервис Web API)
append Добавляет в коллекцию объект (без отправки запроса на сервис Web API)
reload Перезагружает объект (currentItem) с сервера. Параметром также служить id объекта.
clear Очищает коллекцию объектов

События Events контрола DataSource

Все события по умолчанию имеют значение null.

Наименование параметра Описание
selectedHandler Событие срабатывает после вызова метода select.
getCompleteHandler Событие срабатывает после получение коллекции сущностей.
postCompleteHandler Событие срабатывает после получение положительного результата при добавлении сущности (метод postData).
deleteCompleteHandler Событие срабатывает после получение положительного результата при удалении сущности (метод delData).

Сервис для DataSource

Для того чтобы DataSource начал работу с Web API требуется наличие специализированного сервиса. Это, своего рода, “обертка” (wrapper) на GET, POST, PUT, DELETE методы Web API. Сервис прост до безобразия, и преследует всего лишь одну цель: все обращения к сервису Web API должны быть из одного места! У вас на представлении (View) может быть несколько DataSource-объектов, которые могут использовать один и тот же сервис. Наверное это всё немного сложно, лучше показать всё на примерах.

Ничего не может быть лучше хорошего примера, чем код сервиса:

(function (site) {

   site.services.documents = function () {
       var
           init = function () {
               site.amplify.request.define("getdocument", "ajax", {
                   url: "/api/documentapi",
                   dataType: "json",
                   type: "GET",
                   cache: false
               });
               site.amplify.request.define("postdocument", "ajax", {
                   url: "/api/documentapi",
                   dataType: "json",
                   contentType: "application/json; charset=utf-8",
                   type: "POST",
                   cache: false
               });
               site.amplify.request.define("putdocument", "ajax", {
                   url: "/api/documentapi",
                   dataType: "json",
                   contentType: "application/json; charset=utf-8",
                   type: "PUT",
                   cache: false
               });
               site.amplify.request.define("deldocument", "ajax", {
                   url: "/api/documentapi",
                   dataType: "json",
                   contentType: "application/json; charset=utf-8",
                   type: "DELETE",
                   cache: false
               });
           },
           mapItem = function (data) {
               return new site.m.Document(data);
           },
           mapItems = function (data) {
               var mapped = [];
               site._.each(data, function (item) {
                   mapped.push(mapItem(item));
               });
               return mapped;
           },
           getData = function (params, back) {
               if (typeof back !== "function") throw new Error("callback not a function");
               if (!params) throw new Error("queryParams notis null");
               return site.amplify.request({
                   resourceId: "getdocument",
                   data: { qp: ko.toJSON(params) },
                   success: function (json) {
                       if (json) {
                           if (json.success) {
                               params.total(json.total);
                               var result = mapItems(json.items);
                               back(result);
                               return;
                           }
                           if (json.warning) {
                               site.logger.warning(json.warning);
                           }
                           if (json.error) {
                               site.logger.error(json.error);
                           }
                       }
                       back();
                   },
                   error: function () {
                       site.logger.error("Ошибка загрузки сущности \"Документ\");
                       back();
                   }
               });
           },
           getDataById = function (params, back) {
               if (typeof back !== "function") throw new Error("callback not a function");
               if (!params) throw new Error("queryParams notis null");
               return site.amplify.request({
                   resourceId: "getdocument",
                   data: { id: params },
                   success: function (json) {
                       if (json) {
                           if (json.success) {
                               var result = mapItem(json.item);
                               back(result);
                               return;
                           }
                           if (json.warning) {
                               site.logger.warning(json.warning);
                           }
                           if (json.error) {
                               site.logger.error(json.error);
                           }
                       }
                       back();
                   },
                   error: function () {
                       site.logger.error("Ошибка загрузки сущности \"Должность\");
                       back();
                   }
               });
           },
           postData = function (params, back) {
               if (typeof back !== "function") throw new Error("callback not a function");
               return site.amplify.request({
                   resourceId: "postdocument",
                   data: ko.toJSON(params),
                   success: function (json) {
                       if (json) {
                           if (json.success) {
                               site.logger.success(json.success);
                               back(new mapItem(json.item));
                               return;
                           }
                           if (json.warning) {
                               site.logger.warning(json.warning);
                           }
                           if (json.error) {
                               site.logger.error(json.error);
                           }
                       }
                       back();
                   },
                   error: function () {
                       site.logger.error("Ошибка сохранения сущности \"Документ\"");
                       back();
                   }
               });
           },
           putData = function (params, back) {
               if (typeof back !== "function") throw new Error("callback not a function");
               return site.amplify.request({
                   resourceId: "putdocument",
                   data: ko.toJSON(params),
                   success: function (json) {
                       if (json) {
                           if (json.success) {
                               site.logger.success(json.success);
                               back(new mapItem(json.item));
                               return;
                           }
                           if (json.warning) {
                               site.logger.warning(json.warning);
                           }
                           if (json.error) {
                               site.logger.error(json.error);
                           }
                       }
                       back();
                   },
                   error: function () {
                       site.logger.error("Ошибка обновления сущности \"Документ\");
                       back();
                   }
               });
           },
           delData = function (params, back) {
               if (typeof back !== "function") throw new Error("callback not a function");
               return site.amplify.request({
                   resourceId: "deldocument",
                   data: ko.toJSON(params),
                   success: function (json) {
                       if (json) {
                           if (json.success) {
                               site.logger.success(json.success);
                               back(new mapItem(json.item));
                               return;
                           }
                           if (json.warning) {
                               site.logger.warning(json.warning);
                           }
                           if (json.error) {
                               site.logger.error(json.error);
                           }
                       }
                       back();
                   },
                   error: function () {
                       site.logger.error("Ошибка удаления сущности \"Документ\");
                       back();
                   }
               });
           };

       init();

       return {
           getDataById: getDataById,
           postData: postData,
           getData: getData,
           putData: putData,
           delData: delData
       };
   }();

})(site);

Прошу обратить ваше внимание на то, что у каждого сервиса, который должен быть использован в контроле DataSource должны быть обязательные методы. Все они перечислены в строках 186-190. Дополнительные методы Web API сервиса прописываются аналогичным образом, и так же публикуются в публичные свойства (litteral object). Вызов дополнительных методов производится через объект execute контрола DataSource.

Все сервисы размещены в пространстве имен site.services. Пример сервиса приведен в предыдущем листинге. Но более свежий пример всегда есть в файле site.services.js, который поставляется с nuget-пакетом JsSite.

Ссылки по теме

JsSite – nuget-пакет, в котором находится DataSource-контрол и все необходимые для работы контрола дополнения в виде утилит и вспомогательных классов.

В следующих статьях я, на примере простого приложения, покажу как используя этот незамысловатый контрол можно с легкостью реализовать Master – Details связку. К обеим частям прилагаются проект для экспериментов.