ASP.NET MVC: DataSource на JavaScript или обертка на Web API сервис (часть 1)
Подготовим проект
1. Создаем новый проект по шаблону ASP.NET MVC 4 (я выбрал “basic”). На момент создания статьи ASP.NET MVC 5 находится в статусе beta. Изучив указанные нововведения, могу предложить, что пример будет работать и версией MVC 5.
2. Запускаем процедуру обновления всех nuget-пакетов. Лог обработки команды показывать не буду.
3. Устанавливаем недостающие пакеты. Во-первых, пакет JsSite для начала:
PM> Install-Package jssite
Attempting to resolve dependency 'toastr (≥ 1.1.4.2)'.
Attempting to resolve dependency 'jQuery (≥ 1.6.3)'.
Attempting to resolve dependency 'AmplifyJS (≥ 1.1.0)'.
Attempting to resolve dependency 'knockoutjs (≥ 2.2.1)'.
Attempting to resolve dependency 'Knockout.Mapping (≥ 2.4.0)'.
Attempting to resolve dependency 'Knockout.Validation (≥ 1.0.1)'.
Attempting to resolve dependency 'underscore.js (≥ 1.4.3)'.
Attempting to resolve dependency 'Moment.js (≥ 1.7.2)'.
Attempting to resolve dependency 'infuser (≥ 0.2.1)'.
Attempting to resolve dependency 'TrafficCop (≥ 0.3.0)'.
Attempting to resolve dependency 'Knockout.js_External_Template_Engine (≥ 2.0.0)'.
Installing 'Knockout.js_External_Template_Engine 2.0.5'.
Successfully installed 'Knockout.js_External_Template_Engine 2.0.5'.
Installing 'JsSite 0.6.1'.
Successfully installed 'JsSite 0.6.1'.
Adding 'Knockout.js_External_Template_Engine 2.0.5' to JsSiteDataSourceDemo.
Successfully added 'Knockout.js_External_Template_Engine 2.0.5' to JsSiteDataSourceDemo.
Adding 'JsSite 0.6.1' to JsSiteDataSourceDemo.
Successfully added 'JsSite 0.6.1' to JsSiteDataSourceDemo.
PM>
Далее устанавливаем SampleData, чтобы были данные, с которыми можно ставить эксперименты:
PM> Install-Package SampleData
Installing 'SampleData 1.2.2'.
Successfully installed 'SampleData 1.2.2'.
Adding 'SampleData 1.2.2' to JsSiteDataSourceDemo.
Successfully added 'SampleData 1.2.2' to JsSiteDataSourceDemo.
PM>
А еще для разбивки данных на страницы нам потребуется PagedListExt:
PM> Install-Package PagedListExt
Installing 'PagedListExt 0.6.6'.
Successfully installed 'PagedListExt 0.6.6'.
Adding 'PagedListExt 0.6.6' to JsSiteDataSourceDemo.
Successfully added 'PagedListExt 0.6.6' to JsSiteDataSourceDemo.
PM>
4. Теперь правим BundleConfig.cs, чтобы подключить установленные скрипты. У меня после правки стал выглядеть следующим образом:
1: publicstaticvoid RegisterBundles(BundleCollection bundles) {
2: bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
3: "~/Scripts/jquery-{version}.js"));
4:
5: bundles.Add(new ScriptBundle("~/bundles/third").Include(
6: "~/Scripts/globalize.js",
7: "~/Scripts/cultures/globalize.culture.ru.js",
8: "~/Scripts/cultures/globalize.culture.ru-RU.js",
9: "~/Scripts/cultures/moment.min.js",
10: "~/Scripts/cultures/toastr.min.js",
11: "~/Scripts/cultures/underscore.min.js",
12: "~/Scripts/underscore.js",
13: "~/Scripts/moment.js",
14: "~/Scripts/infuser.js",
15: "~/Scripts/TrafficCop.js",
16: "~/Scripts/amplify.js"));
17:
18: bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
19: "~/Scripts/knockout-2.2.1.debug.js",
20: "~/Scripts/knockout.mapping-latest.debug.js",
21: "~/Scripts/koExternalTemplateEngine.js",
22: "~/Scripts/knockout.command.js",
23: "~/Scripts/knockout.dirtyFlag.js",
24: "~/Scripts/knockout.validation.debug.js"));
25:
26: bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
27: "~/bootstrap/js/bootstrap.js"));
28:
29: bundles.Add(new ScriptBundle("~/bundles/site").Include(
30: "~/Scripts/app/site.core.js",
31: "~/Scripts/app/site.controls.js",
32: "~/Scripts/app/site.bindingHandlers.js",
33: "~/Scripts/app/site.services.person.js",
34: "~/Scripts/app/site.m.all.js",
35: "~/Scripts/app/site.utils.js"));
36:
37: bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
38: "~/Scripts/jquery-ui-{version}.js"));
39:
40: bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
41: "~/Scripts/jquery.unobtrusive*",
42: "~/Scripts/jquery.validate*"));
43:
44: // Use the development version of Modernizr to develop with and learn from. Then, when you're
45: // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
46: bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
47: "~/Scripts/modernizr-*"));
48:
49: bundles.Add(new StyleBundle("~/bootstrap/css")
50: .Include("~/bootstrap/css/bootstrap.css")
51: .Include("~/bootstrap/css/bootstrap-responsive.css"));
52:
53: bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
54:
55: bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
56: "~/Content/themes/base/jquery.ui.core.css",
57: "~/Content/themes/base/jquery.ui.resizable.css",
58: "~/Content/themes/base/jquery.ui.selectable.css",
59: "~/Content/themes/base/jquery.ui.accordion.css",
60: "~/Content/themes/base/jquery.ui.autocomplete.css",
61: "~/Content/themes/base/jquery.ui.button.css",
62: "~/Content/themes/base/jquery.ui.dialog.css",
63: "~/Content/themes/base/jquery.ui.slider.css",
64: "~/Content/themes/base/jquery.ui.tabs.css",
65: "~/Content/themes/base/jquery.ui.datepicker.css",
66: "~/Content/themes/base/jquery.ui.progressbar.css",
67: "~/Content/themes/base/jquery.ui.theme.css"));
68: }
Следует иметь в виду, что все скрипты (или почти все), которые будут созданы в процессе работы над статьёй будут добавляться в bundle/site (строка 29).
5. Теперь надо создать HomeController и представление Index. Дело в том, что из всех любезно предложенных студией шаблонов, я выбрал для своего проекта Basic шаблон, который не включает в себя куча разных ненужных “ненужностей”, но при этом не имеет контролера по умолчанию. Хотя в настройках маршрутов RouteConfig.cs, название контролера по умолчанию указано “Home”.
Проект готов к работе. Теперь можно приступить непосредственно к реализации Master/Details паттерну. На первом этапе создадим сервисы Web API.
Сервисы WEB API
Первый из них будет работать с классом Person из сборки SampleData. Пусть для начала он выглядит таким образом:
1: publicclass PersonApiController : ApiController {
2:
3: private List _listOfPerson = new List();
4:
5: public HttpResponseMessage GetPersons(JsonQueryParams query) {
6: return Request.CreateResponse(HttpStatusCode.OK,
7: new {
8: success = "все данные",
9: total = _listOfPerson.Count,
10: items = _listOfPerson
11: });
12: }
13:
14: public HttpResponseMessage GetPerson(int id) {
15: return Request.CreateResponse(HttpStatusCode.OK,
16: new {
17: success = "один по идентификатору",
18: item = _listOfPerson.Where(x => x.Id.Equals(id))
19: });
20: }
21:
22: public HttpResponseMessage PostPerson(Person person) {
23: thrownew NotImplementedException();
24: }
25:
26: public HttpResponseMessage PutPerson(Person person) {
27: thrownew NotImplementedException();
28: }
29:
30: public HttpResponseMessage DeletePerson(Person person) {
31: thrownew NotImplementedException();
32: }
33: }
Class site.m.Person.js
Перед тем как создать сам файл сервиса, надо бы создать класс (ViewModel) как ViewModel для Person на Javascript:
1: (function (site, ko) {
2:
3: site.m.Person = function (dto) {
4: var me = this, data = dto || {};
5:
6: me.age = ko.observable(data.Age);
7: me.country = ko.observable(data.Country);
8: me.departmentId = ko.observable(data.DepartmentId);
9: me.description = ko.observable(data.Description);
10: me.id = ko.observable(data.Id);
11: me.gender = ko.observable(data.Gender);
12: me.isMember = ko.observable(data.IsMember);
13: me.name = ko.observable(data.Name);
14: me.weight = ko.observable(data.Weight);
15:
16: me.selected = ko.observable(false);
17:
18: return me;
19: };
20: })(site, ko)
Строки 6-14 определяют существующие поля и свойства класса Person, который мы взяли из пакета SampleData.
Строка 16 выполняет требование DataSource контрола, который, по нашей договоренности, может установить это свойство в значение “true”, если пользователь сделает выбор этой сущности (дальше будет понятнее).
Не забудьте добавить ссылку на этот файл в представлении (view) или в BundleConfig.cs.
1: @section scripts
2: {
3: <script src="~/Scripts/app/site.m.person.js"></script>
4: <script src="~/Scripts/app/site.vm.homeIndex.js"></script>
5: }
ViewModel для представления (View)
Наверное, вы уже обратили внимание на то что, в строке 4 предыдущего листинга присутствует ссылка на файл site.vm.homeIndex.js? Это как раз тот самый viewModel, который мы должны создать в этом разделе. Этот файл (ViewModel) является отправной точкой, именно он запускает весь механизм. На текущий момент его содержимое такое:
1: /// <reference path="site.controls.js" />
2: /// <reference path="site.core.js" />
3: /// <reference path="../knockout-2.2.1.debug.js" />
4:
5:
6: $(function() {
7:
8: "use strict";
9:
10: site.vm.homeIndex = function () {
11: var clock = new site.controls.Clock(),
12: dsPerson = new site.controls.DataSource({
13: autoLoad: true,
14: service : site.services.person
15: });
16:
17: return {
18: dsPerson: dsPerson,
19: clock: clock
20: };
21: }();
22:
23:
24: ko.applyBindings(site.vm.homeIndex);
25: });
В строке 11 создаем объект “часы”. Как я уже упоминал статьях опубликованных ранее, это делается для того, чтобы проверить, что необходимые скрипты загружены на странице, и не просто загружены, а “правильной” последовательности. Это своего рода тест конфигурации скриптов на странице. Я в разметку Index.cshtml добавил span-тег, чтобы часы заработали и запустил проект.
1: @{
2: ViewBag.Title = "DataSource: Master/Details";
3: }
4:
5: <spandata-bind="text: clock.time">span>
Часы заработали. Далее добавил строки 12-15.
В строке 12-15 создаем экземпляр контрола DataSource. Параметрами DataSource для него служат autoLoad (необязательный) со значение true и service (обязательный) со значением site.services.person. Этот сервис мы будем создаем в следующем разделе.
Сервис для DataSource
Скажу честно, я для создания сервисов для DataSource использую шаблон (snippet или Template от Resharper) в силу того, что для различных сущностей сервисы практически не отличаются. Вот полный текст работающего сервиса:
1: /// <reference path="site.core.js" />
2:
3:
4: (function(site) {
5:
6: site.services.person = function() {
7: var init = function() {
8: site.amplify.request.define("getperson", "ajax", {
9: url: "/api/personapi",
10: dataType: "json",
11: type: "GET",
12: cache: false
13: });
14: site.amplify.request.define("postperson", "ajax", {
15: url: "/api/personapi",
16: dataType: "json",
17: contentType: "application/json; charset=utf-8",
18: type: "POST",
19: cache: false
20: });
21: site.amplify.request.define("putperson", "ajax", {
22: url: "/api/personapi",
23: dataType: "json",
24: contentType: "application/json; charset=utf-8",
25: type: "PUT",
26: cache: false
27: });
28: site.amplify.request.define("delperson", "ajax", {
29: url: "/api/personapi",
30: dataType: "json",
31: contentType: "application/json; charset=utf-8",
32: type: "DELETE",
33: cache: false
34: });
35: },
36: mapItem = function(data) {
37: returnnew site.m.Person(data);
38: },
39: mapItems = function(data) {
40: var mapped = [];
41: site._.each(data, function(item) {
42: mapped.push(mapItem(item));
43: });
44: return mapped;
45: },
46: getData = function(params, back) {
47: if (typeof back !== "function") thrownew Error("callback not a function");
48: if (!params) thrownew Error("queryParams notis null");
49: return site.amplify.request({
50: resourceId: "getperson",
51: data: { qp: ko.toJSON(params) },
52: success: function(json) {
53: if (json) {
54: if (json.success) {
55: params.total(json.total);
56: var result = mapItems(json.items);
57: back(result);
58: return;
59: }
60: if (json.warning) {
61: site.logger.warning(json.warning);
62: }
63: if (json.error) {
64: site.logger.error(json.error);
65: }
66: }
67: back();
68: },
69: error: function() {
70: site.logger.error("Ошибка загрузки сущности\
71: \"Пользователь\" (method \"get\") Person");
72: back();
73: return;
74: }
75: });
76: },
77: getDataById = function(params, back) {
78: if (typeof back !== "function") thrownew Error("callback not a function");
79: if (!params) thrownew Error("queryParams notis null");
80: return site.amplify.request({
81: resourceId: "getperson",
82: data: { id: params },
83: success: function(json) {
84: if (json) {
85: if (json.success) {
86: var result = mapItem(json.item);
87: back(result);
88: return;
89: }
90: if (json.warning) {
91: site.logger.warning(json.warning);
92: }
93: if (json.error) {
94: site.logger.error(json.error);
95: }
96: }
97: back();
98: },
99: error: function() {
100: site.logger.error("Ошибка загрузки сущности \
101: \"Должность\" (method \"get\") Person");
102: back();
103: return;
104: }
105: });
106: },
107: postData = function(params, back) {
108: if (typeof back !== "function") thrownew Error("callback not a function");
109: return site.amplify.request({
110: resourceId: "postperson",
111: data: ko.toJSON(params),
112: success: function(json) {
113: if (json) {
114: if (json.success) {
115: site.logger.success(json.success);
116: back(new mapItem(json.item));
117: return;
118: }
119: if (json.warning) {
120: site.logger.warning(json.warning);
121: }
122: if (json.info) {
123: site.logger.info(json.info);
124: }
125: if (json.error) {
126: site.logger.error(json.error);
127: }
128: }
129: back();
130: },
131: error: function() {
132: site.logger.error("Ошибка сохранения сущности\
133: \"Пользователь\" (method \"post\") Person");
134: back();
135: return;
136: }
137: });
138: },
139: putData = function(params, back) {
140: if (typeof back !== "function") thrownew Error("callback not a function");
141: return site.amplify.request({
142: resourceId: "putperson",
143: data: ko.toJSON(params),
144: success: function(json) {
145: if (json) {
146: if (json.success) {
147: site.logger.success(json.success);
148: back(new mapItem(json.item));
149: return;
150: }
151: if (json.warning) {
152: site.logger.warning(json.warning);
153: }
154: if (json.error) {
155: site.logger.error(json.error);
156: }
157: }
158: back();
159: },
160: error: function() {
161: site.logger.error("Ошибка обновления сущности\
162: \"Пользователь\" (method \"put\") Person");
163: back();
164: return
165: }
166: });
167: },
168: delData = function(params, back) {
169: if (typeof back !== "function") thrownew Error("callback not a function");
170: return site.amplify.request({
171: resourceId: "delperson",
172: data: ko.toJSON(params),
173: success: function(json) {
174: if (json) {
175: if (json.success) {
176: site.logger.success(json.success);
177: back(new mapItem(json.item));
178: return;
179: }
180: if (json.warning) {
181: site.logger.warning(json.warning);
182: }
183: if (json.error) {
184: site.logger.error(json.error);
185: }
186: }
187: back();
188: },
189: error: function() {
190: site.logger.error("Ошибка удаления сущности\
191: \"Пользователь\" (method \"del\") Person");
192: back();
193: return;
194: }
195: });
196: };
197:
198: init();
199:
200: return {
201: getDataById: getDataById,
202: postData: postData,
203: getData: getData,
204: putData: putData,
205: delData: delData
206: };
207: }();
208:
209: })(site);
Теперь всё готово для первого запуска.
Заключение
В качестве заключения скажу следующее, в данной части мы подготовили проект, а именно: Web API сервис, ViewModel на JavaScript для класса Person, JavaScript обертку для Web API, главную страницу, ViewModel для главной странице.
В данной реализации совсем не работает пейджинг, нет возможности какой-либо фильтрации. В следующей части будем добавлять этот функционал на страницу.
Подробнее: http://feedproxy.google.com/~r/blogmusor/~3/0UWVfOkrDwY/131
Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 2024-12-26 » Универсальный промпт для нейросети: как выжать максимум из ChatGPT, YandexGPT, Gemini, Claude в 2025
- 2024-11-26 » Капитан грузового судна, или Как начать использовать Docker в своих проектах
- 2024-11-26 » Обеспечение безопасности ваших веб-приложений с помощью PHP OOP и PDO
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-15 » Перенос сайта на WordPress с одного домена на другой
- 2024-11-08 » OSPanel 6: быстрый старт
- 2024-11-08 » Как установить PhpMyAdmin в Open Server Panel
- 2024-09-30 » Как быстро запустить Laravel на Windows
- 2024-09-25 » Next.js
- 2024-09-05 » OpenAI рассказал, как запретить ChatGPT использовать содержимое сайта для обучения
- 2024-08-28 » Чек-лист: как увеличить конверсию интернет-магазина на примере спортпита
- 2024-08-01 » WebSocket
- 2024-07-26 » Интеграция с Яндекс Еда
- 2024-07-26 » Интеграция с Эквайринг
- 2024-07-26 » Интеграция с СДЕК
- 2024-07-26 » Интеграция с Битрикс-24
- 2024-07-26 » Интеграция с Travelline
- 2024-07-26 » Интеграция с Iiko
- 2024-07-26 » Интеграция с Delivery Club
- 2024-07-26 » Интеграция с CRM
- 2024-07-26 » Интеграция с 1C-Бухгалтерия
- 2024-07-24 » Что такое сторителлинг: техники и примеры
- 2024-07-17 » Ошибка 404: что это такое и как ее использовать для бизнеса
- 2024-07-03 » Размещайте прайс-листы на FarPost.ru и продавайте товары быстро и выгодно
- 2024-07-01 » Профилирование кода в PHP
- 2024-06-28 » Изучаем ABC/XYZ-анализ: что это такое и какие решения с помощью него принимают
- 2024-06-17 » Зачем вам знать потребности клиента
- 2024-06-11 » Что нового в работе Яндекс Метрики: полный обзор обновления
- 2024-06-11 » Поведенческие факторы ранжирования в Яндексе
"Успеха в ближайшем десятилетии добьются только те компании, которые сумеют реорганизовать свою работу с помощью электронного инструментария. Только это позволит им быстро принимать правильные решения, предпринимать эффективные действия и поддерживать тесные плодотворные связи со своими клиентами." |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.
Или напишите нам в WhatsApp
Или напишите нам в WhatsApp