Книга: Microsoft Visual C#. Подробное руководство. 8-е издание
Назад: 26. Отображение и поиск данных в приложении универсальной платформы Windows
На главную: Предисловие

27. Доступ к удаленной базе данных из приложения универсальной платформы Windows

Прочитав эту главу, вы научитесь:

использовать среду Entity Framework для создания модели элементов предметной области (entity-модели), способной извлекать и изменять информацию, хранящуюся в базе данных;

создавать веб-сервис Representational State Transfer (REST), обеспечивающий удаленный доступ к базе данных через entity-модель;

извлекать данные из удаленной базы данных путем использования веб-сервиса REST;

производить вставку, обновление и удаление данных в удаленной базе данных путем использования веб-сервиса REST.

В главе 26 «Отображение и поиск данных в приложении универсальной платформы Windows» были показаны способы реализации шаблона Model — View — ViewModel (MVVM). В ней также объяснялось, как с использованием класса ViewModel, предоставляющего доступ к данным в модели и реализующего команды, которые могут использоваться пользовательским интерфейсом для вызова логики приложения, отделить бизнес-логику этого приложения от пользовательского интерфейса. В главе 26 также было показано, как привязка данных используется для отображения данных, предоставляемых классом ViewModel, и как пользовательский интерфейс может обновлять эти данные. Все эти технологии помогают создать полнофункциональное приложение универсальной платформы Windows.

В этой главе ваше внимание будет привлечено к модельному аспекту шаблона MVVM. В частности, вы увидите, как реализуется модель, которую ваше UWP-приложение может использовать для извлечения и обновления данных в удаленной базе данных.

174648.png

ПРИМЕЧАНИЕ Облачная платформа Microsoft Azure предоставляет мобильные сервисы, которые можно использовать для создания веб-сервисов RESTful, предоставляющих доступ к серверной базе данных, наряду с Windows-приложением, которое может обращаться к этим сервисам с устройств Windows. Но на время написания этих строк данное предложение могло создавать приложения только для Windows 8.1, хотя в скором будущем ожидается появление поддержки Windows 10 (если она уже не появилась на тот момент, когда вы читаете эту книгу). Но даже при этом предложение может помочь понять, как можно создать подобную систему своими силами. Дополнительные сведения о мобильных сервисах для создания мобильных приложений можно найти в статье «Создание приложения Windows» по адресу /.

Извлечение данных из базы данных

До сих пор вы использовали данные, ограничиваемые простой коллекцией, встроенной в модель представления приложения. В реальном мире данные, отображаемые и поддерживаемые приложением, чаще всего хранятся в таком источнике, как база данных.

Технологии, предоставляемые компанией Microsoft, не могут обеспечить непосредственный доступ к реляционной базе данных, хотя вам доступны решения для работы с базами данных от сторонних разработчиков. Это может восприниматься как весьма серьезное ограничение, но для него имеются довольно веские причины. Во-первых, эти технологии исключают зависимости от внешних ресурсов, которые могли бы возникнуть в UWP-приложениях, что превратило бы эти приложения в самостоятельный программный продукт, который несложно упаковать и загрузить из магазина Windows, при этом пользователям не потребуется устанавливать и настраивать на своем компьютере систему управления базами данных. Во-вторых, многие устройства, работающие под управлением Windows 10, имеют ограниченные ресурсы и не обладают достаточным объемом оперативной памяти или дискового пространства, позволяющим запустить локальную систему управления базами данных. Но многие бизнес-приложения все же выставляют требования по доступу к базе данных, и для удовлетворения потребностей при таком сценарии вы можете воспользоваться веб-сервисом.

Веб-сервисы могут реализовывать различные функции, но одним из самых распространенных сценариев их использования является предоставление интерфейса, с помощью которого приложение может подключиться к удаленному источнику данных с целью извлечения и обновления информации. Веб-сервис может находиться практически где угодно, от компьютера, на котором запущено приложение, до веб-сервера, размещенного на компьютере на другом континенте. При условии получения возможности подключения к веб-сервису вы можете воспользоваться им для предоставления доступа к хранилищу своей информации. Среда Microsoft Visual Studio предоставляет шаблоны и инструментальные средства, позволяющие вам очень быстро и легко создать веб-сервис. Самая простая стратегия, показанная на следующей схеме (рис. 27.1), заключается в том, чтобы положить в основу веб-сервиса entity-модель, созданную путем использования среды Entity Framework.

187083.png 

Рис. 27.1

Среда Entity Framework представляет собой эффективную технологию, позволяющую подключиться к реляционной базе данных. Она может сократить объем кода, который большинству разработчиков приходится создавать для добавления к приложениям возможности доступа к данным. С нее вы и начнете, но сначала нужно настроить базу данных AdventureWorks, содержащую информацию о клиентах компании Adventure Works.

174654.png

ПРИМЕЧАНИЕ Ограниченные объемы книги не позволяют вдаваться во все тонкости использования среды Entity Framework, и упражнения в данном разделе проведут вас по самым главным действиям, позволяющим приступить к работе с этой средой. Если вам понадобятся дополнительные сведения, обратитесь к статье «Entity Framework» на веб-сайте компании Microsoft, которая находится по адресу .

Чтобы придать сценарию более реалистичные очертания, упражнения в этой главе показывают, как создавать базу данных в облаке, используя Microsoft Azure SQL Database, и как развертывать веб-сервис в Azure. Эта архитектура используется во многих коммерческих приложениях, включая приложения электронной торговли, сервисы мобильного банкинга и даже системы потокового видео.

174672.png

ПРИМЕЧАНИЕ Для выполнения упражнений потребуется создать учетную запись Azure и войти в нее. Если у вас пока нет учетной записи в Azure, можете подписаться на бесплатную пробную версию учетной записи, обратившись по адресу /. Кроме того, облачная платформа Azure требует, чтобы у вас была действующая учетная запись в Microsoft, которую она могла бы связать с вашей учетной записью в Azure. Подписаться на учетную запись Microsoft можно по адресу /.

Создание сервера Azure SQL Database и установка учебной базы данных AdventureWorks

Через веб-браузер подключитесь к порталу Azure, находящемуся по адресу . Войдите на портал, используя свою учетную запись Microsoft.

В панели инструментов, находящейся в левой части окна портала, щелкните на пункте Создать. На странице Создать щелкните на пункте Данные + хранилище, а затем на пункте SQL Database.

На панели SQL Database наберите в поле Имя базы данных строку AdventureWorks.

Щелкните на пункте Сервер, а затем на пункте Создание нового сервера. На панели Новый сервер наберите уникальное имя для вашего сервера. (Воспользуйтесь именем вашей компании или даже своим именем, лично я использовал имя csharpstepbystep. Если введенное имя уже кем-то занято, вы получите преду­преждение, после чего нужно будет выбрать другое имя.) Введите имя и пароль для учетной записи администратора (если говорить об этих элементах, то я воспользовался именем JohnSharp, а пароль раскрывать не намерен), выберите наиболее близкое место расположения, а затем щелкните на кнопке Выбрать.

Щелкните на пункте Выбрать источник, а затем на пункте Пример. Убедитесь, что выбран пункт AdventureWorksLT [V12].

Щелкните на пункте Ценовая категория. На панели Выберите ценовую категорию щелкните на пункте Basic, а затем на кнопке Выбрать. (Эта самый дешевый вариант, если вы платите за базу данных самостоятельно, и его будет вполне достаточно для работы с упражнениями в этой главе. Если создается широкомасштабное коммерческое приложение, то вам, скорее всего, понадобится использование ценовой категории Premium, предоставляющей больше места и более высокий уровень производительности, но при более высокой цене.) Эти настройки показаны на рис. 27.2.

174963.png

ВНИМАНИЕ Не выбирайте какие-либо другие ценовые категории, кроме Basic, если не хотите получить в конце месяца счет на приличную сумму. Сведения о расценках на использование базы данных SQL Database можно найти по адресу /.

27_02.tif 

Рис. 27.2

Щелкните на кнопке Создать и дождитесь создания сервера базы данных и самой базы. Ход создания можно отслеживать на экране.

Щелкните на панели инструментов в левой части окна портала на кнопке Обзор.

Щелкните на сервере вашей базы данных, имя которого отображено на панели Все ресурсы страницы Обзор (но не на базе данных AdventureWorks).

Щелкните на пункте Параметры, который находится в верхней части панели сервера базы данных на панели инструментов.

На панели Параметры щелкните на пункте Брандмауэр и получите результат, показанный на рис. 27.3.

На панели инструментов Параметры брандмауэра щелкните на пункте Добавить IP-адрес. Щелкните на кнопке Сохранить. Убедитесь в том, что появилось сообщение «Правила брандмауэра сервера успешно обновлены», после чего щелкните на кнопке OK.

Закройте панель Параметры брандмауэра, затем панель Параметры, после чего закройте панель сервера базы данных.

27_03.tif 

Рис. 27.3

174679.png

ПРИМЕЧАНИЕ Выполнение этих действий играет важную роль. Без этого вы не сможете подключиться к базе данных из приложений, запущенных на вашем компьютере. Если нужно открыть доступ к набору компьютеров, вы также можете создать брандмауэры, пропускающие целый диапазон IP-адресов.

Используемая в качестве примера база данных AdventureWorks содержит в схеме SalesLT таблицу по имени Customer. Эта таблица включает столбцы, содержащие данные, предоставляемые UWP-приложением Customers, а также несколько других столбцов. Используя среду Entity Framework, можно игнорировать столбцы, не имеющие отношения к приложению, но если какие-либо проигнорированные столбцы не могут иметь пустых значений (null) и не имеют значений по умолчанию, вы не сможете создать записи о новых клиентах. В таб­лице Customer эти ограничения применяются к столбцам NameStyle, PasswordHash и PasswordSalt, используемым для шифрования паролей пользователей. Чтобы не возникали излишние сложности и вы смогли сконцентрироваться на функциональных возможностях приложения, в следующем упражнении вы удалите эти столбцы из таблицы Customer.

Удаление из базы данных AdventureWorks ненужных столбцов

В портале Azure щелкните в панели Все ресурсы на базе данных AdventureWorks.

На панели инструментов в верхней части панели AdventureWorks SQL Database щелкните на кнопке Сервис, а затем на пункте Открыть в Visual Studio. На панели Открыть в Visual Studio щелкните на пункте Открыть в Visual Studio.

Если появится сообщение «Did You Mean To Switch Applications?» («Вы имеете в виду переключение приложений?»), щелкните на кнопке Yes (Да) (рис. 27.4).

27_04.tif 

Рис. 27.4

Запустится среда Visual Studio, которая выведет приглашение на подключение к базе данных.

Если появится диалоговое окно Соединение с сервером, введите указанный ранее пароль администратора и щелкните на кнопке Соединить (рис. 27.5).

27_05.tif 

Рис. 27.5

Среда Visual Studio подключится к базе данных, которая появится на панели Обозреватель объектов SQL Server в левой части окна среды Visual Studio (рис. 27.6).

27_06.tif 

Рис. 27.6

На панели Обозреватель объектов SQL Server раскройте элемент с базой данных AdventureWorks, раскройте элемент Таблицы, элемент SalesLT.Customer, а затем элемент Столбцы.

Будет открыт список со столбцами таблицы. Должны быть удалены три столбца, которые не используются приложением и не допускают пустых значений (в противном случае приложение не сможет создать записи для новых клиентов).

27_07.tif 

Рис. 27.7

Щелкните на столбце NameStyle, нажмите клавишу Ctrl и, удерживая ее, щелкните на столбцах PasswordHash и PasswordSalt. Щелкните правой кнопкой мыши на столбце PasswordSalt, а затем на пункте Удалить.

Среда Visual Studio проанализирует эти столбцы. В диалоговом окне Предварительный просмотр обновлений базы данных она покажет список предупреждений и других вопросов, которые могут возникнуть при удалении столбцов (рис. 27.7).

В диалоговом окне Предварительный просмотр обновлений базы данных щелкните на кнопке Обновить базу данных.

Закройте панель Обозреватель объектов SQL Server, но оставьте открытым окно среды Visual Studio 2015.

Создание entity-модели

После создания в облаке базы данных AdventureWorks появляется возможность создать с помощью среды Entity Framework entity-модель, которой приложение сможет пользоваться для создания запросов к этой базе данных и обновления в ней информации. Если вы имеете опыт работы с базами данных, вам могут быть знакомы такие технологии, как ADO.NET, предоставляющие библиотеку классов, которой можно воспользоваться для подключения к базе данных и запуска SQL-команд. ADO.NET, конечно, полезная технология, но она требует определенного знания языка SQL, и если не проявить должной осмотрительности, она может навязать вам структурирование кода вокруг логики, необходимой для выполнения SQL-команд, вместо того чтобы сконцентрироваться на бизнес-операциях вашего приложения. Среда Entity Framework предоставляет уровень абстракции, сокращающий зависимость ваших приложений от SQL.

Фактически среда Entity Framework реализует уровень отображения между реляционной базой данных и вашим приложением: она создает entity-модель, состоящую из коллекций объектов, которую ваше приложение может использовать точно так же, как и любую другую коллекцию. Эта коллекция обычно соответствует таблице в базе данных, и каждая строка таблицы соответствует элементу коллекции. Запросы выполняются путем последовательного перебора элементов коллекции обычно с помощью интегрированного в C# языка запросов (Language-Integrated Query (LINQ)). Entity-модель скрытно преобразует ваши запросы в команды SQL SELECT, которые извлекают информацию. Данные в коллекции можно изменять, после чего сделать так, чтобы entity-модель генерировала и выполняла соответствующие SQL-команды INSERT, UPDATE и DELETE для проведения эквивалентных операций в базе данных. Короче говоря, среда Entity Framework является отличным механизмом для подключения к базе данных, извлечения из нее данных и управления данными, не требующим встраивания в ваш код SQL-команд.

В следующем упражнении вы создадите очень простую entity-модель для таб­лицы Customer, находящейся в базе данных AdventureWorks. При этом вы примените подход к entity-моделированию, названный database-first, при котором приоритет в формировании структуры отдается базе данных. Он предполагает создание средой Entity Framework классов на основе определений таблиц в базе данных. Среда Entity Framework также предоставляет подход, где приоритет отдается коду, и в базе данных могут создаваться наборы таблиц на основе классов, реализованных вами в приложении.

174685.png

ПРИМЕЧАНИЕ Если понадобятся дополнительные сведения о подходе, при котором для создания entity-модели приоритет отдается коду, обратитесь к статье «Code First to an Existing Database» на веб-сайте компании Microsoft, находящейся по адресу .

Создание entity-модели AdventureWorks

Откройте в среде Visual Studio проект Customers, находящийся в папке \Microsoft Press\VCSBS\Chapter 27\Web Service вашей папки документов.

Этот проект содержит измененную версию приложения Customers из главы 26. В модели представления реализованы дополнительные команды, позволяющие пользователю переходить к первому и последнему клиенту в коллекции клиентов, и панель команд, содержащая кнопки First и Last, вызывающие эти команды.

В обозревателе решений щелкните правой кнопкой мыши на решении Customers (но не на проекте Customers), укажите на пункт Добавить, а затем щелкните на пункте Создать проект.

В левой панели диалогового окна Добавить новый проект щелкните на узле Веб. В средней панели щелкните на шаблоне Веб-приложение ASP.NET. Убедитесь, что в раскрывающемся списке над средней панелью указывается версия .NET Framework 4.6 (или при необходимости внесите соответствующие изменения). Наберите в поле Имя строку AdventureWorksService и щелкните на кнопке OK.

В диалоговом окне Новый проект ASP.NET — AdventureWorksService щелкните под надписью Выбор шаблона в разделе Шаблоны ASP.NET 4.6 на значке Web API, а затем на кнопке Изменить способ проверки подлинности.

В диалоговом окне Изменить способ проверки подлинности выберите пункт без проверки подлинности и щелкните на кнопке OK, чтобы вернуться в диалоговое окно Новый проект ASP.NET.

Убедитесь, что в диалоговом окне Новый проект ASP.NET снят флажок Host In The Cloud (сначала вы протестируете Web API локально, а затем развернете его в облаке), после чего щелкните на кнопке OK (рис. 27.8).

174691.png

ПРИМЕЧАНИЕ Шаблон ASP.NET Web API может создать код для обработки регистрации пользователей, но Azure предоставляет собственную масштабируемую систему идентификации и управления доступом, которая предпочтительнее для встраивания системы управления безопасностью в ваш код, если вы развертываете сервис в облаке. Система безопасности Azure в данной книге не рассматривается, но дополнительные сведения можно получить в статье «Azure Active Directory» по адресу / и в статье «Многофакторная проверка подлинности» по адресу /.

27_08.tif 

Рис. 27.8

Как упоминалось в начале главы, получить непосредственный доступ к реляционной базе данных из UWP-приложения невозможно, даже если используется среда Entity Framework. Для этого нужно создать веб-приложение (которое не является UWP-приложением) и разместить в нем создаваемую entity-модель. Шаблон Web API предоставляет мастеры и инструменты, с помощью которых можно быстро создать веб-сервис, чем вы и займетесь в следующем упражнении. Этот веб-сервис предоставит удаленный доступ к entity-модели для UWP-приложения Customers.

В обозревателе решений щелкните правой кнопкой мыши на решении Customers, а затем на пункте Назначить запускаемые проекты.

В диалоговом окне Страницы свойств Решение «Customers» щелкните на пункте Несколько запускаемых проектов. Установите для проекта AdventureWorksService действие Запуск без отладки, а для проекта CustomersЗапуск, после чего щелкните на кнопке OK.

Такая конфигурация гарантирует, что веб-приложение AdventureWorksService запустится, как только произойдет запуск проекта из меню Отладка. В обозревателе решений щелкните правой кнопкой мыши на проекте AdventureWorksService, а затем на пункте Свойства.

На странице свойств щелкните на вкладке Веб, находящейся в левом столбце.

На странице Веб щелкните на пункте Не открывать страницу. Дождаться запроса от внешней программы.

Обычно при запуске веб-приложения из среды Visual Studio открывается веб-браузер (Microsoft Edge), который пытается отобразить главную страницу приложения. Но у приложения AdventureWorksService нет главной страницы, поскольку оно предназначено для содержания веб-сервиса, к которому клиентское приложение может подключиться, чтобы извлечь данные из базы данных AdventureWorks.

В поле URL-адрес проекта измените адрес веб-приложения на : 50000/, а затем щелкните на кнопке Создать виртуальный каталог. Убедитесь в появлении окна среды Microsoft с сообщением об успешном создании виртуального каталога, после чего щелкните на кнопке OK.

По умолчанию шаблон проекта ASP.NET создает веб-приложение, размещенное с помощью IIS Express, и выбирает для URL-адреса произвольный порт. Данная конфигурация устанавливает для порта значение 50000, чтобы было легче дать описание следующих этапов выполнения упражнений в этой главе.

В меню Файл щелкните на пункте Сохранить все, а затем закройте страницу Свойства.

В обозревателе решений щелкните правой кнопкой мыши на папке Models, которая находится в проекте AdventureWorksService, укажите на пункт Добавить, а затем щелкните на пункте Создать элемент.

В левой панели диалогового окна Добавить новый элемент — AdventureWorksService щелкните на пункте Данные. В средней панели щелкните на шаблоне Модель ADO.NET EDM. В поле Имя наберите строку AdventureWorksModel, а затем щелкните на кнопке Добавить. Запустится мастер моделей EDM. Этим мастером можно воспользоваться для создания entity-модели из существующей базы данных.

В разделе Что должна содержать модель? страницы мастера щелкните на пункте Конструктор EF из базы данных, а потом на кнопке Далее.

На странице Выбор подключения к данным щелкните на кнопке Создать соединение. Если появится диалоговое окно Выбор источника данных, выберите пункт Microsoft SQL Server, после чего щелкните на кнопке Продолжить.

174697.png

ПРИМЕЧАНИЕ Диалоговое окно Выбор источника данных появляется только в том случае, если вы раньше не пользовались мастером подключения к данным и не выбирали источник данных.

В поле Имя сервера диалогового окна Свойства подключения наберите строку tcp:<servername>.database.windows.net,1433, где <servername> является уникальным именем сервера базы данных Azure SQL, созданным вами в предыдущем упражнении. Щелкните на пункте Использовать аутентификацию SQL Server и введите имя и пароль, указанные вами для регистрации администратора в предыдущем упражнении. В поле Выберите или введите имя базы данных наберите строку AdventureWorks, а затем щелкните на кнопке OK (рис. 27.9).

27_09.tif 

Рис. 27.9

В результате будет создано подключение к базе данных AdventureWorks, запущенной в облаке.

На странице Выбор подключения к данным щелкните на пункте Нет, исключить конфиденциальные данные из строки подключения. Они будут заданы в коде приложения. Убедитесь в том, что установлен флажок Сохранить параметры соединения в Web.Config как:, а в качестве имени строки соединения указана строка AdventureWorksEntities. Щелкните на кнопке Далее (рис. 27.10).

27_10.tif 

Рис. 27.10

На странице Выберите версию выберите пункт Entity Framework 6.x, а затем щелкните на кнопке Далее.

На странице Выберите параметры и объекты базы данных раскройте пункт Таблицы, далее пункт SalesLT, а затем выберите пункт Customer. Убедитесь в том, что установлен флажок Формировать имена объектов во множественном или единственном числе. (Два остальных флажка также должны быть изначально установлены.) Заметьте, что среда Entity Framework генерирует классы для entity-модели в пространстве имен AdventureWorksModel, а затем щелкните на кнопке Готово (рис. 27.11).

27_11.tif 

Рис. 27.11

Мастер моделей EDM создаст модель для таблицы Customer и покажет графическое представление в редакторе Entity Model (рис. 27.12).

27_12.tif 

Рис. 27.12

Если появится окно Предупреждение о безопасности, установите флажок Больше не выводить это сообщение, а затем щелкните на кнопке OK. Данное окно преду­преждения о безопасности появляется из-за того, что среда Entity Framework использует для создания кода вашей entity-модели технологию, известную как шаблоны T4, и загружает эти шаблоны из сети, используя NuGet. Шаблоны среды Entity Framework были проверены компанией Microsoft: пользоваться ими не опасно (рис. 27.13).

27_13.tif 

Рис. 27.13

В редакторе Entity Model щелкните правой кнопкой мыши на столбце MiddleName, после чего выберите пункт Удалить из модели. Точно так же удалите из entity-модели столбцы Suffix, CompanyName и SalesPerson.

Приложение Customers эти столбцы не использует, и в их извлечении из базы данных нет никакой необходимости. Они могут содержать null-значения, поэтому их можно, не опасаясь негативных последствий, оставить в качестве части таблицы базы данных. А вот столбцы rowguid и ModifiedDate удалять нельзя, поскольку они используются базой данных для идентификации строк в таблице Customer и для отслеживания изменений в этих строках в многопользовательской среде. Если эти столбцы удалить, вы не сможете корректно сохранять измененные данные в базе данных.

В меню Сборка щелкните на пункте Собрать решение.

В обозревателе решений в проекте AdventureWorksService откройте папку Models, раскройте запись AdventureWorksModel.edmx, затем запись AdventureWorksModel.tt и дважды щелкните на файле Customer.cs.

В этом файле содержится класс, созданный мастером моделей EDM для представления сведений о клиенте. В этом классе содержатся автоматически созданные свойства для каждого столбца, имеющегося в таблице Customer, включенного вами в entity-модель:

public partial class Customer

{

    public int CustomerID { get; set; }

    public string Title { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string EmailAddress { get; set; }

    public string Phone { get; set; }

    public System.Guid rowguid { get; set; }

    public System.DateTime ModifiedDate { get; set; }

}

В обозревателе решений ниже записи AdventureWorksModel.edmx раскройте запись AdventureWorksModel.Context.tt, а затем дважды щелкните на файле AdventureWorksModel.Context.cs. В этом файле содержится определение класса по имени AdventureWorksEntities. (Точно такое же имя использовалось вами при создании подключения к базе данных в мастере моделей EDM.)

public partial class AdventureWorksEntities : DbContext

{

    public AdventureWorksEntities()

        : base("name=AdventureWorksEntities")

    {

    }

 

    protected override void OnModelCreating(DbModelBuilder modelBuilder)

    {

        throw new UnintentionalCodeFirstException();

    }

 

    public DbSet<Customer> Customers { get; set; }

}

Класс AdventureWorksEntities является производным от класса DbContext, а этот класс предоставляет функциональные средства, используемые приложением для подключения к базе данных. Пассивный конструктор передает конструктору базового класса параметр, указывающий имя строки подключения для подключения к базе данных. Если посмотреть на содержимое файла web.config, то эту строку можно найти в разделе <ConnectionStrings>. В ней, кроме всего прочего, содержатся параметры, которые вы указываете при запуске мастера моделей EDM. Но эта строка не содержит информации о пароле, требующемся для аутентификации подключения, — вы выбрали вариант указания соответствующих данных в ходе выполнения программы. Решением данного вопроса вы займетесь, выполняя следующие задания упражнения.

Метод OnModelCreating в классе AdventureWorksEntities можно проигнорировать. В коллекции Customers имеется единственный элемент. Эта коллекция относится к типу DbSet<Customer>. Тип-обобщение DbSet предоставляет методы, с помощью которых вы можете добавлять, вставлять, удалять и запрашивать объекты в базе данных. Для создания соответствующих SQL-команд SELECT, необходимых для извлечения клиентской информации из базы данных и заполнения коллекции, он работает в союзе с классом DbContext. Он также используется для создания SQL-команд INSERT, UPDATE и DELETE, запускаемых, если Customer-объекты добавляются, изменяются или удаляются из коллекции. Коллекция DbSet часто упоминается как entity-набор.

Щелкните в обозревателе решений правой кнопкой мыши на папке Models, на пункте Добавить, а затем на пункте Класс.

Убедитесь, что в диалоговом окне Добавить новый элементAdventureWorksService выбран шаблон Класс. Наберите в поле Имя строку AdventureWorksEntities, а затем щелкните на кнопке Добавить.

К проекту будет добавлен новый класс по имени AdventureWorksEntities, и его код будет выведен в окно редактора. На данный момент этот класс конфликтует с уже существующим классом с таким же именем, который был создан средой Entity Framework, но вы этот класс будете использовать в качестве расширения кода среды Entity Framework путем его преобразования в частичный класс (partial class). Частичным называется класс, в котором код разделен на несколько исходных файлов. Такой подход применяется для таких инструментальных средств, как среда Entity Framework, поскольку он позволяет вам добавлять собственный код без риска его случайной перезаписи в случае какого-либо преобразования среды Entity Framework в будущем.

Измените в окне редактора определение класса AdventureWorksEntities, объявив его частичным.

public partial class AdventureWorksEntities

{

}

Добавьте к классу AdventureWorksEntities конструктор, получающий строковый параметр по имени password. Конструктор должен вызвать конструктор базового класса с передачей ему имени строки подключения, ранее записанной в файл web.config мастером моделей EDM и указанной на странице Выбор подключения к данным:

public partial class AdventureWorksEntities

{

    public AdventureWorksEntities(string password)

        : base("name=AdventureWorksEntities")

    {

    }

}

Добавьте к конструктору код, показанный далее жирным шрифтом. Этот код изменяет строку подключения, используемую средой Entity Framework, с целью включения в нее пароля. Приложение Customers будет вызывать этот конструктор и предоставлять пароль в ходе своего выполнения:

public partial class AdventureWorksEntities

{

    public AdventureWorksEntities(string password)

        : base("name=AdventureWorksEntities")

    {

        this.Database.Connection.ConnectionString += $";Password={password}";

    }

}

Создание и использование веб-сервиса REST

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

С помощью среды Visual Studio 2015 можно создать модель веб-сервиса в веб-приложении ASP.NET, основываясь непосредственно на entity-модели, созданной средой Entity Framework. Веб-сервис использует entity-модель для извлечения данных из базы данных и обновления этой базы данных. Вы будете создавать веб-сервис, используя мастер под названием Добавление шаблона. Этот мастер может создать веб-сервис, реализующий REST-модель, которая использует схему навигации для представления бизнес-объектов и сервисов по сети, а также HTTP-протокол для передачи запросов на доступ к этим объектам и сервисам. Клиентское приложение, обращающееся к ресурсу, отправляет запрос в форме URL-адреса, который анализируется и обрабатывается веб-сервисом. Например, компания Adventure Works может опубликовать информацию о клиентах в виде единого ресурса, воспользовавшись схемой, похожей на следующую:

Обращение по этому URL-адресу заставляет веб-сервис извлечь данные для клиента № 1. Эти данные могут быть возвращены в нескольких форматах, но для обеспечения переносимости наиболее часто используемыми форматами являются XML и JavaScript Object Notation (JSON). Созданный веб-сервисом с REST-моделью типовой JSON-ответ на предыдущий запрос выглядит следующим образом:

{

"CustomerID":1,

"Title":"Mr",

"FirstName":"Orlando",

"LastName":"Gee",

"",

"Phone":"245-555-0173"

}

REST-модель зависит от приложения, обращающегося к данным, и от отправки им соответствующего HTTP-глагола в качестве части запроса на доступ к данным. Например, простой запрос, показанный ранее, должен вызвать отправку HTTP GET-запроса к веб-сервису. В HTTP поддерживаются и другие глаголы, например POST, PUT и DELETE, которыми можно воспользоваться для создания, изменения и удаления ресурсов соответственно. Написание кода для генерирования соответствующих HTTP-запросов и синтаксического разбора ответов, возвращенных веб-сервисом REST, представляется весьма непростой задачей. К счастью, создание основного объема этого кода мастер Добавление шаблона может взять на себя.

В следующем упражнении вы создадите для entity-модели простой REST веб-сервис. Он откроет для клиентского приложения возможность запрашивать информацию о клиентах и распоряжаться ею.

Создание веб-сервиса AdventureWorks

В среде Visual Studio, в проекте AdventureWorksService щелкните правой кнопкой мыши на папке Controllers, укажите на пункт Добавить, а затем щелкните на пункте Создать шаблонный элемент.

В средней панели мастера Добавление шаблона щелкните на шаблоне Контроллер Web API2 с действиями, использующий Entity Framework, а затем на кнопке Добавить (рис. 27.14).

27_14.tif 

Рис. 27.14

В диалоговом окне Добавить контроллер в раскрывающемся списке Класс модели выберите элемент Customer (AdventureWorksService.Models). Выберите в раскрывающемся списке Класс контекста данных элемент AdventureWorksEntities (AdventureWorksService.Models). Установите флажок Использование асинхронных действий контроллера. Убедитесь в том, что в поле Имя контроллера показано имя CustomersController, а затем щелкните на кнопке Добавить (рис. 27.15).

27_15.tif 

Рис. 27.15

В веб-сервисе, созданном путем использования шаблона ASP.NET Web API, все поступающие веб-запросы обрабатываются одним или несколькими классами контроллера и каждый класс контроллера предоставляет методы, отображающие различные типы REST-запросов для каждого ресурса, предоставляемого контроллером. Например, CustomersController имеет следующий вид:

public class CustomersController : ApiController

{

    private AdventureWorksEntities db = new AdventureWorksEntities();

        

    // GET: api/Customers

    public IQueryable<Customer> GetCustomers()

    {

        return db.Customers;

    }

 

    // GET: api/Customers/5

    [ResponseType(typeof(Customer))]

    public async Task<IHttpActionResult> GetCustomer(int id)

    {

        Customer customer = await db.Customers.FindAsync(id);

        if (customer == null)

        {

            return NotFound();

        }

 

        return OK(customer);

    }

 

    // PUT: api/Customers/5

    [ResponseType(typeof(void))]

    public async Task<IHttpActionResult> PutCustomer(int id, Customer customer)

    {

        if (!ModelState.IsValid)

    {

            return BadRequest(ModelState);

        }

 

        if (id != customer.CustomerID)

        {

            return BadRequest();

        }

 

        db.Entry(customer).State = EntityState.Modified;

 

        try

        {

            await db.SaveChangesAsync();

        }

        catch (DbUpdateConcurrencyException)

        {

            if (!CustomerExists(id))

            {

                return NotFound();

            }

            else

            {

                throw;

            }

        }

 

        return StatusCode(HttpStatusCode.NoContent);

    }

 

    // POST: api/Customers

    [ResponseType(typeof(Customer))]

    public async Task<IHttpActionResult> PostCustomer(Customer customer)

    {

        ...

    }

 

    // DELETE: api/Customers/5

    [ResponseType(typeof(Customer))]

    public async Task<IHttpActionResult> DeleteCustomer(int id)

    {

        ...

    }

    ...

}

Метод GetCustomers обрабатывает запросы на извлечение сведений обо всех клиентах и удовлетворяет эти запросы простым возвращением всей коллекции Customers из ранее созданной средой Entity Framework модели данных. Сама среда Entity Framework извлекает все сведения о клиентах из базы данных и использует эту информацию для заполнения коллекции Customers. Этот метод вызывается, если приложение отправляет HTTP GET-запрос к этому веб-сервису по URL-адресу api/Customers.

Метод GetCustomer (не перепутайте его с GetCustomers) получает целочисленный параметр. Этот параметр указывает значение идентификатора CustomerID конкретного клиента, и метод использует среду Entity Framework для поиска сведений об этом клиенте перед их возвращением. GetCustomer запускается, когда приложение отправляет HTTP GET-запрос по URL-адресу api/Customers/n, где n — идентификатор клиента, сведения о котором должны быть извлечены.

Метод PutCustomer запускается, когда приложение отправляет веб-сервису HTTP PUT-запрос. В запросе указываются идентификатор клиента и сведения о нем, а код в этом методе использует среду Entity Framework для обновления сведений о конкретном клиенте. Метод PostCustomer откликается на HTTP POST-запросы и получает в качестве своего параметра сведения о клиенте. Этот метод добавляет нового клиента с этими сведениями к базе данных (сведения в предыдущем примере кода не показаны). И наконец, метод DeleteCustomer обрабатывает HTTP DELETE-запросы и удаляет сведения о клиенте с конкретно указанным идентификатором.

174703.png

ПРИМЕЧАНИЕ В коде, созданном шаблоном Web API, оптимистично предполагается, что у него всегда будет возможность подключиться к базе данных. В мире распределенных систем, где база данных и веб-сервис расположены на разных серверах, такая возможность может представиться не всегда. В сетях бывают ошибки переходных процессов и задержки, попытки подключения могут проваливаться из-за временных сбоев и чуть позже удаваться при повторном обращении. Сообщение клиенту о временном сбое как об ошибке может стать неприятным сюрпризом для пользователя. Лучше будет по возможности молча повторить неудавшуюся операцию при условии, что число попыток не превысило определенный порог, поскольку замораживать веб-сервис в случае реальной недоступности базы данных нежелательно. Подробные сведения об этой стратегии можно найти в статье «Cloud Service Funda­mentals Data Access Layer—Transient Fault Handling» по адресу .

Шаблон ASP.NET Web API автоматически создает код, направляющий запросы к соответствующему методу в классах контроллеров, и если нужно управлять другими ресурсами, например товарами или заказами, вы можете добавить дополнительные классы контроллеров.

174711.png

ПРИМЕЧАНИЕ Дополнительные сведения о реализации веб-сервисов REST с использованием шаблона ASP.NET Web API можно найти в статье «Web API» по адресу .

Можно также создавать классы контроллеров вручную, воспользовавшись той же самой схемой, которая была показана для класса CustomersController, — вам необязательно извлекать данные и сохранять их в базе данных путем использования среды Entity Framework. Шаблон ASP.Net Web API содержит в файле ValuesController.cs демонстрационный контроллер, который вы можете скопировать и дополнить собственным кодом.

Измените в классе CustomersController инструкцию, которая создает объект контекста AdventureWorksEntities для использования конструктора, получающего пароль в качестве своего параметра. В качестве аргумента конструктора укажите пароль администратора, который был вами задан при создании базы данных. (В следующем примере кода замените строку YourPassword своим паролем.)

public class CustomersController : ApiController

{

    private AdventureWorksEntities db = new

            AdventureWorksEntities("YourPassword");

 

    // GET: api/Customers

    public IQueryable<Customer> GetCustomers()

    {

        return db.Customers;

    }

    ...

}

174719.png

ПРИМЕЧАНИЕ В реальном мире вы могли бы предложить пользователю, запустившему приложение Customers, ввести эту информацию или позволить приложению сохранить пароль локально на устройстве пользователя в виде части конфигурационных данных приложения. Затем приложение Customers передало бы эту информацию веб-сервису.

В папке Controllers щелкните правой кнопкой мыши на файле ValuesController.cs, а затем щелкните на пункте Удалить. В окне сообщения щелкните на кнопке OK, подтверждая намерение удалить этот файл.

В этом приложении пример класса ValuesController использоваться не будет.

174729.png

ПРИМЕЧАНИЕ Не удаляйте класс HomeController. Его контроллер действует в качестве точки входа в веб-приложение, размещающее веб-сервис.

Щелкните правой кнопкой мыши на проекте AdventureWorksService, щелкните на пункте Представление, а затем на пункте Просмотреть в браузере (Google Chrome). Если веб-сервис настроен правильно, веб-браузер запустится и покажет следующую страницу (рис. 27.16).

27_16.tif 

Рис. 27.16

В панели заголовков щелкните на пункте API. Появится еще одна страница, содержащая сводку REST-запросов, которые приложение может отправлять веб-сервису (рис. 27.17).

27_17.tif 

Рис. 27.17

Наберите в адресной строке адрес , а затем нажмите Ввод.

Этот запрос направлен перегруженному методу GetCustomer в классе CustomersController, а значение 1 передано этому методу в качестве параметра. Заметьте, что Web API использует маршруты, которые начинаются с адреса сервера, после которого указывается путь api.

Метод GetCustomer извлечет сведения о клиенте из базы данных и вернет их в объекте, имеющем JSON-формат, который отобразится в браузере. Данные будут выглядеть следующим образом (значение для ModifiedDate может отличаться от того, которое показано далее):

{"CustomerID":1,"Title":"Mr","FirstName":"Orlando","LastName":"Gee","EmailAddre

ss": "","Phone":"245-555-0173","rowguid":

"3f5ae95e-b87d-4aed-95b4-c3797afcb74f","ModifiedDate":"2001-08-01T00:00:00"}

Закройте веб-браузер и вернитесь в среду Visual Studio.

Теперь вместо того, чтобы запускать веб-сервис локально, вы развернете его в облаке Azure. Это можно сделать с помощью мастера публикации веб-сайта, доступного в Visual Studio 2015 для создания веб-приложения в облаке и выкладывания веб-сервиса в это приложение.

Развертывание веб-сервиса в облаке

В обозревателе решений щелкните правой кнопкой мыши на проекте AdventureWorksService, а затем щелкните на пункте Опубликовать. Запустится мастер публикации веб-сайта (рис. 27.18).

27_18.tif 

Рис. 27.18

В области Выберите цель публикации щелкните на пункте Веб-приложения Microsoft Azure.

Выберите в диалоговом окне свою учетную запись в Azure. Если вместо списка учетных записей появится надпись Добавить учетную запись, щелкните на ней, а затем на пункте Добавить учетную запись. В диалоговом окне регистрации в Visual Studio зарегистрируйтесь с использованием своей учетной записи в Microsoft.

В области Существующие веб-приложения щелкните на кнопке New.

В диалоговом окне Create Web App On Microsoft Azure дайте уникальное имя веб-приложению и укажите для его размещения ближайший к вам регион. Не указывайте сервер базы данных (о подключении к базе данных позаботится код среды Entity Framework в вашем веб-сервисе). В поле сервисного плана приложения App Service Plan щелкните на пункте создания нового сервисного плана Create New App Service Plan и дайте ему имя по своему выбору. Аналогично этому в поле группы ресурсов Resource group щелкните на пункте создания новой группы ресурсов Create New Resource Group и дайте имя этой группе. После введения всех данных щелкните на кнопке Create (Создать) (рис. 27.19).

На странице Подключение мастера публикации веб-приложения примите предлагаемые значения по умолчанию, щелкните на кнопке проверки подключения

27_19.tif 

Рис. 27.19

Validate connection, чтобы проверить, что веб-приложение, в котором размещается веб-сервис, было успешно создано, после чего щелкните на кнопке Далее.

На странице Параметры примите значения по умолчанию, а затем щелкните на кнопке Далее.

На странице Просмотр щелкните на кнопке Опубликовать.

Веб-сервис должен быть выложен в сеть. Должен открыться браузер и показать домашнюю страницу сайта, которую вы уже видели, когда выполняли локальный запуск сервиса.

Закройте веб-браузер и вернитесь в среду Visual Studio.

Следующий этап нашего путешествия предполагает подключение к веб-сервису из UWP-приложения Customers с использованием веб-сервиса для извлечения данных. Среда .NET Framework предоставляет класс HttpClient, который приложение может использовать для составления и отправки к веб-сервису запросов HTTP REST, и класс HttpResponseMessage, который приложение может использовать для обработки результата, полученного от веб-сервиса. Эти классы позволяют отделить от вашего кода подробности HTTP-протокола. Благодаря этому вы можете сконцентрироваться на бизнес-логике, отображающей объекты, опубликованные через веб-сервис, и манипулирующей ими. Вы воспользуетесь этими классами в следующем упражнении. Вами также будет использован JSON-парсер, реализованный в пакете Json.NET, поэтому придется добавить этот пакет к проекту Customers.

174969.png

ВНИМАНИЕ В этом упражнении извлекаются сведения о каждом клиенте. Оно полезно в качестве прототипа, подтверждающего концепцию извлечения данных посредством веб-сервиса, размещенного в облаке, но в реальности вы должны подходить к извлечению данных более избирательно. Если база данных содержит большой объем данных, этот подход может стать весьма расточительным в смысле использования сетевого трафика и требований, предъявляемых к объемам памяти со стороны приложения, запущенного на устройстве пользователя. Более рациональным подходом будет разбиение на страницы, при котором сведения о клиентах извлекаются в поблочном режиме (возможно, блоками, содержащими сведения о 20 клиентах). Для поддержки такого подхода веб-сервису понадобится обновление, а модели представления (ViewModel) в приложении Customers потребуется управлять извлекаемыми блоками явным образом. Пусть разработка этого подхода станет для читателя заданием для самостоятельного выполнения.

Извлечение данных из веб-сервиса AdventureWorks

В обозревателе решений щелкните правой кнопкой мыши на файле DataSource.cs проекта Customers, а затем щелкните на пункте Удалить. Щелкните в окне сообщения на кнопке OK, чтобы подтвердить намерение удалить файл.

В этом файле содержатся демонстрационные данные, используемые приложением Customers. Вы собираетесь внести изменения в класс ViewModel, чтобы извлекать эти данные из веб-сервиса, следовательно, надобность в этом файле исчезла.

Щелкните правой кнопкой мыши на проекте Customers, а затем щелкните на пункте Управление пакетами NuGet.

174737.png

ПРИМЕЧАНИЕ Если будет предложено ввести пароль для подключения к SQL Server, закройте диалоговое окно. Информация о пароле уже предоставлена веб-сервисом.

Убедитесь в том, что в окне Диспетчер пакетов NuGet: Customers в поле со списком Filter показано значение All, а затем наберите в поле поиска строку Json.NET.

Выберите в панели, показывающей результаты поиска, пакет Newtonsoft.Json. В правой панели настройте действие на установку пакета и щелкните на кнопке Установить.

Щелкните в окне Просмотр на кнопке OK.

Дождитесь установки пакета и закройте окно Диспетчер пакетов NuGet: Customers.

В обозревателе решений дважды щелкните на файле ViewModel.cs, чтобы его код появился в окне редактора.

Добавьте к списку в начале файла следующие директивы using:

using System.Net.Http;

using System.Net.Http.Headers;

using Newtonsoft.Json;

Добавьте к классу ViewModel следующие переменные, показанные жирным шрифтом, поставив их перед конструктором ViewModel. Поставьте вместо <webappname> имя веб-приложения, созданного вами в предыдущем упражнении, чтобы в нем содержался веб-сервис:

public class ViewModel : INotifyPropertyChanged

{

    ...

    private const string ServerUrl = "/";

    private HttpClient client = null;

 

    public ViewModel()

    {

        ...

    }

    ...

}

В переменной ServerUrl содержится базовый адрес веб-сервиса. А переменную client вы будете использовать для подключения к веб-сервису.

В конструкторе ViewModel инициализируйте список customers значением null и добавьте для настройки переменной client следующие инструкции, показанные жирным шрифтом:

public ViewModel()

{

    ...

    this.customers = null;

    this.client = new HttpClient();

    this.client.BaseAddress = new Uri(ServerUrl);

    this.client.DefaultRequestHeaders.Accept.

        Add(new MediaTypeWithQualityHeaderValue("application/json"));

}

В списке customers содержатся сведения о клиентах, которые показывает приложение, — ранее он был заполнен данными, содержавшимися в файле DataSource.cs, который вы удалили.

Переменная client инициализируется адресом веб-сервера, к которому будут отправляться запросы. REST веб-сервис может получать запросы и отправлять ответы в различных форматах, но в приложении Customers будет использоваться формат JSON. Последняя инструкция в предыдущем фрагменте кода настраивает переменную client на отправку запросов именно в этом формате.

В обозревателе решений дважды щелкните на файле Customer.cs, хранящемся в корневой папке проекта Customers, чтобы он был выведен в окно редактора. Добавьте к классу Customer сразу же после свойства Phone следующие открытые свойства, показанные жирным шрифтом:

public class Customer : INotifyPropertyChanged

{

    ...

    public string Phone

    {

        ...

    }

 

    public System.Guid rowguid { get; set; }

    public System.DateTime ModifiedDate { get; set; }

 

    ...

}

Веб-сервис извлекает эти поля из базы данных, и приложение Customers должно быть готово к их обработке, в противном случае, если пользователь решит изменить сведения о клиенте, данные из этих полей будут утрачены. (Возможность изменения данных будет добавлена к приложению Customers чуть позже.)

Вернитесь к классу ViewModel. Добавьте после конструктора показанный здесь открытый метод GetDataAsync:

public async Task GetDataAsync()

{

    try

    {

        var response = await this.client.GetAsync("api/customers");

        if (response.IsSuccessStatusCode)

        {

            var customerData =

                await response.Content.ReadAsStringAsync();

            this.customers =

                JsonConvert.DeserializeObject<List<Customer>>(customerData);

            this.currentCustomer = 0;

            this.OnPropertyChanged(nameof(Current));

            this.IsAtStart = true;

            this.IsAtEnd = (this.customers.Count == 0);

        }

        else

        {

            // TODO: Обработать сбой GET-запроса

}

    }

    catch (Exception e)

    {

        // TODO: Обработать исключения

    }

}

Этот метод работает в асинхронном режиме, для вызова в веб-сервисе операции api/customers он использует метод GetAsync объекта HttpClient. С помощью этой операции из базы данных AdventureWorks извлекаются сведения о клиентах. Сам метод GetAsync также имеет асинхронную природу и возвращает объект HttpResponseMessage, заключенный в Task-объект. Объект HttpResponseMessage содержит код состояния, показывающий, увенчался ли запрос успехом, и если да, приложение для фактического извлечения данных, возвращенных веб-сервисом, использует метод ReadAsStringAsync, принадлежащий свойству Content, которое в свою очередь принадлежит объекту HttpResponseMessage. Эти данные содержат строку в JSON-формате, в которой имеются сведения о каждом клиенте, поэтому они преобразуются в список из Customer-объектов и присваиваются коллекции customers путем использования статического метода DeserializeObject, принадлежащего классу JsonConvert, который является частью пакета Json.NET. Затем в качестве значения свойства currentCustomer класса ViewModel устанавливается указатель на первого клиента в этой коллекции, а свойства IsAtStart и IsAtEnd инициализируются таким образом, чтобы показывать состояние ViewModel.

174743.png

ПРИМЕЧАНИЕ Клиентские библиотеки ASP.NET Web API предполагают, что запуск всех запросов веб-службы может занимать неопределенное время. Соответственно, такие классы, как HttpClient и HttpResponseMessage, сконструированы под поддержку асинхронных операций, чтобы предотвратить блокировку приложения при ожидании ответа. Фактически, эти классы поддерживают только асинхронные операции — синхронных версий для GetAsync или ReadAsStringAsync не существует.

Заметьте, что на данный момент метод GetDataAsync не обрабатывает никаких исключений или отказов, он их просто молчаливо поглощает. Технология выдачи отчетов об исключениях в UWP-приложении будет показана чуть позже.

174751.png

ПРИМЕЧАНИЕ В коммерческих приложениях из сети не должна извлекаться ненужная информация, и при их разработке следует проявлять избирательность в отношении извлекаемых данных. Но в данном приложении база данных AdventureWorks содержит всего лишь несколько сотен клиентов, поэтому извлекаются и кэшируются сведения обо всех клиентах, которые затем попадают в список клиентов.

Измените, как показано далее, метод доступа get свойства Current:

public Customer Current

{

    get

    {

        if (this.customers != null)

        {

            return this.customers[currentCustomer];

        }

        else

        {

            return null;

        }

    }

}

Метод GetDataAsync является асинхронным, следовательно, на момент попытки привязки к клиенту формы MainPage коллекция клиентов может быть еще не заполнена. В такой ситуации данное изменение не дает привязке данных выдавать исключения, связанные с отсутствием ссылки, при обращении к коллекции клиентов.

Обновите в конструкторе ViewModel условия, позволяющие запускаться каждой команде, внеся изменения, выделенные в следующем примере жирным шрифтом:

public ViewModel()

{

    ...

    this.NextCustomer = new Command(this.Next,

        () => { return this.customers != null &&

                this.customers.Count > 1 && !this.IsAtEnd; });

    this.PreviousCustomer = new Command(this.Previous,

        () => { return this.customers != null &&

                this.customers.Count > 0 && !this.IsAtStart; });

    this.FirstCustomer = new Command(this.First,

        () => { return this.customers != null &&

                this.customers.Count > 0 && !this.IsAtStart; });

    this.LastCustomer = new Command(this.Last,

        () => { return this.customers != null &&

                this.customers.Count > 1 && !this.IsAtEnd; });

}

Эти изменения обеспечат недоступность кнопок в панели команд до тех пор, пока не появятся данные для отображения на странице.

В обозревателе решений раскройте элемент MainPage.xaml и дважды щелкните на файле MainPage.xaml.cs, чтобы он открылся в окне редактора.

Добавьте к конструктору MainPage следующую инструкцию, показанную жирным шрифтом:

public MainPage()

{

    ...

    ViewModel viewModel = new ViewModel();

    viewModel.GetDataAsync();

    this.DataContext = viewModel;

}

Эта инструкция заполнит данными ViewModel.

В меню Отладка щелкните на пункте Начать отладку, чтобы собрать и запустить приложение.

Сначала, пока будет работать метод GetDataAsync, появится пустая форма, но через несколько секунд будут показаны сведения о первом клиенте по имени Orlando Gee (рис. 27.20).

Воспользуйтесь кнопками навигации в панели команд для перемещения по списку клиентов, чтобы убедиться в том, что в работе формы нет никаких неожиданностей.

Вернитесь в среду Visual Studio и остановите отладку.

В качестве последнего штриха, добавляемого в этом разделе, будет полезно при начальном появлении формы оповестить пользователей о том, что, несмотря на ее пустой вид, приложение занимается извлечением данных. В UWP-приложении для создания этой обратной связи можно воспользоваться элементом управления типа ProgressRing (кольцевым индикатором занятости). Этот элемент управления должен выводиться на экран только в том случае, если объект типа ViewModel занят обменом данными с веб-сервером.

27_20.tif 

Рис. 27.20

Добавления к форме Customers индикатора занятости

В окне редактора откройте файл ViewModel.cs. Добавьте к классу ViewModel после метода GetDataAsync закрытое поле _isBusy и открытое свойство IsBusy:

private bool _isBusy;

public bool IsBusy

{

    get { return this._isBusy; }

    set

    {

        this._isBusy = value;

        this.OnPropertyChanged(nameof(IsBusy));

    }

}

Добавьте к методу GetDataAsync следующие инструкции, показанные жирным шрифтом:

public async Task GetDataAsync()

{

    try

    {

        this.IsBusy = true;

        var response = await this.client.GetAsync("api/customers");

        ...

    }

    catch (Exception e)

    {

        // TODO: Обработать исключения

    }

    finally

    {

        this.IsBusy = false;

    }

}

Перед тем как запустить запрос на извлечения сведений о клиентах, метод GetData устанавливает для свойства IsBusy значение true. Блок finally обеспечивает возвращение для свойства IsBusy значения false даже при условии выдачи исключения.

В окне конструктора откройте файл MainPage.xaml. Добавьте в XAML-панели элемент управления ProgressRing, показанный далее жирным шрифтом, сделав его первым элементом в элементе управления Grid верхнего уровня:

<Grid Style="{StaticResource GridStyle}">

    <ProgressRing HorizontalAlignment="Center"

VerticalAlignment="Center" Foreground="AntiqueWhite"

Height="100" Width="100" IsActive="{Binding IsBusy}"

Canvas.ZIndex="1"/>

    <Grid x:Name="customersTabularView" Margin="40,104,0,0" ...>

    ...

Установите для свойства Canvas.ZIndex значение "1", обеспечивающее появление элемента ProgressRing поверх всех остальных элементов управления, отображаемых элементом управления Grid.

В меню Отладка щелкните на пункте Начать отладку для сборки и запуска приложения.

Заметьте, что при запуске приложения кольцевой индикатор занятости появляется на некоторое время, прежде чем на экране возникнут сведения о первом клиенте. Если окажется, что сведения о первом клиенте появляются слишком быстро, можно вставить в метод GetDataAsync небольшую задержку, просто чтобы удостовериться в том, что кольцевой индикатор занятости работает. Добавьте следующую инструкцию, которая поставит выполнение метода на пятисекундную паузу:

public async Task GetDataAsync()

{

    try

    {

        this.IsBusy = true;

        await Task.Delay(5000);

        var response = await this.client.GetAsync(...);

        ...

    }

    ...

}

Не забудьте удалить эту инструкцию, как только закончите тестирование кольцевого индикатора занятости.

Вернитесь в среду Visual Studio и остановите отладку.

Вставка, обновление и удаление данных через REST веб-сервис

Многие приложения наряду с возможностью запрашивать данные и выводить их на экран требуют дать пользователям возможность вставки, обновления и удаления информации. В ASP.NET Web API реализуется модель, поддерживающая эти операции посредством использования HTTP PUT-, POST- и DELETE-запросов. В соответствии с соглашениями PUT-запрос изменяет существующий ресурс в веб-сервисе, POST-запрос создает новый экземпляр ресурса, а DELETE-запрос удаляет ресурс. Код, генерируемый в шаблоне ASP.NET Web API мастером Добавление шаблона, придерживается именно этих трех соглашений.

Идемпотентность в REST веб-сервисах

В REST веб-сервисе PUT-запросы должны быть идемпотентными, то есть при повторном выполнении одного и того же обновления результат всегда должен быть одним и тем же. В случае с примером AdventureWorks Service, если вы изменяете сведения о клиенте и устанавливаете для номера телефона значение «888-888-8888», то неважно, сколько раз проделана эта операция, поскольку эффект будет одним и тем же. Возможно, все это воспринимается как нечто само собой разумеющееся, тем не менее при разработке REST веб-сервиса это требование упускать из виду не следует. Благодаря этому подходу к проектированию веб-сервис сможет надежно работать в условиях одновременно отправляемых запросов или даже в случае сетевых сбоев (если клиентское приложение отключится от веб-сервиса, оно может просто попытаться восстановить подключение и выполнить тот же самый запрос еще раз, не выясняя, был ли успешным прежний запрос). Поэтому можно считать REST веб-сервис средством для сохранения и извлечения данных и не пытаться с его помощью реализовывать операции, определяемые для решения бизнес-задач.

К примеру, если создается банковская система, может появиться соблазн ввести в обращение метод CreditAccount, добавляющий сумму к балансу счета клиента, и предоставить этот метод в виде PUT-операции. Но результатом каждого вызова этой операции будет увеличение кредитной составляющей счета. Поэтому придется отслеживать успешность вызова операции. Ваше приложение не может вызывать эту операцию повторно, если есть подозрение, что ранее сделанный вызов был неудачным или истек срок его обработки, поскольку при дублировании кредитной операции на одном и том же счете результат может стать многократно увеличенным.

Дополнительные сведения о целостности данных в облачных приложениях можно найти в статье «Data Consistency Primer» по адресу .

В следующем упражнении вам предстоит расширить приложение Customers, дополнив его средствами, позволяющими пользователю добавлять сведения о новых клиентах и изменять сведения о существующих. Приложение будет конструировать соответствующие REST-запросы и отправлять их веб-сервису AdventureWorksService. Функциональные средства для удаления сведений о клиентах предоставлены не будут. Это ограничение станет гарантией того, что у вас имеются записи обо всех клиентах, когда-либо имевших деловые отношения с организацией Adventure Works, которые могут понадобиться для аудиторских целей. Кроме того, даже если клиент долго не проявлял активности, есть шанс, что когда-нибудь в будущем он все же разместит новый заказ.

174759.png

ПРИМЕЧАНИЕ В бизнес-приложениях все шире распространяется норма, согласно которой в них никогда не удаляются данные, а в отношении них просто проводится операция обновления, каким-либо образом помечающая их как удаленные и не позволяющая им появляться на экране. Главным образом это связано с требованиями сохранения всего набора записей, зачастую в свете выполнения определенных нормативных требований.

Реализация в классе ViewModel функциональных возможностей добавления и редактирования данных

Вернитесь в среду Visual Studio. Удалите в проекте Customers файл ViewModel.cs, чтобы убрать его из проекта. Позвольте среде Visual Studio окончательно удалить этот файл.

Щелкните правой кнопкой мыши на проекте Customers, укажите на пункт Добавить, а затем щелкните на пункте Существующий элемент. Выберите файл ViewModel.cs, который находится в папке \Microsoft Press\VCSBS\Chapter 27 вашей папки документов, после чего щелкните на кнопке Добавить.

Код в файле ViewModel.cs стал слишком длинным, поэтому он был реорганизован в области, чтобы им было легче управлять. Класс ViewModel был расширен за счет следующих булевых свойств, показывающих режим, в котором работает ViewModel: просмотра, добавления или редактирования. Эти свойства определены в области Properties For Managing The Edit Mode (свойства для управления режимом редактирования).

• IsBrowsing. Это свойство показывает, находится ли модель представления в режиме просмотра. Когда она находится в этом режиме, включены команды FirstCustomer, LastCustomer, PreviousCustomer и NextCustomer и представление может вызывать их для просмотра данных.

• IsAdding. Это свойство показывает, находится ли модель представления в режиме добавления. В этом режиме выключены команды FirstCustomer, LastCustomer, PreviousCustomer и NextCustomer. Вами будут определены ­команды AddCustomer, SaveChanges и DiscardChanges, которые будут включены в этом режиме.

• IsEditing. Это свойство показывает, находится ли модель представления в режиме редактирования. Как и в режиме добавления, в этом режиме выключены команды FirstCustomer, LastCustomer, PreviousCustomer и NextCustomer. Вами также будет определена команда EditCustomer, включенная в этом режиме. Кроме этого, будут включены команды SaveChanges и DiscardChanges, но команда AddCustomer будет выключена. В режиме добавления команда EditCustomer будет выключена.

• IsAddingOrEditing. Это свойство показывает, находится ли модель представления в режиме добавления или редактирования. Оно будет использоваться в методах, которые будут определены в данном упражнении.

• CanBrowse. Это свойство возвращает true, если модель представления находится в режиме просмотра и есть открытое подключение к веб-сервису. Код в конструкторе, который создает команды FirstCustomer, LastCustomer, PreviousCustomer и NextCustomer, был обновлен с целью использования этого свойства и получения возможности определения того, какими должны быть эти команды, включенными или выключенными:

    public ViewModel()

    {

        ...

        this.NextCustomer = new Command(this.Next,

            () => { return this.CanBrowse &&

                    this.customers != null && !this.IsAtEnd; });

        this.PreviousCustomer = new Command(this.Previous,

            () => { return this.CanBrowse &&

                    this.customers != null && !this.IsAtStart; });

        this.FirstCustomer = new Command(this.First,

            () => { return this.CanBrowse &&

                    this.customers != null && !this.IsAtStart; });

        this.LastCustomer = new Command(this.Last,

            () => { return this.CanBrowse &&

                    this.customers != null && !this.IsAtEnd; });

    }

• CanSaveOrDiscardChanges. Это свойство возвращает true, если модель представления находится в режиме добавления или редактирования и имеет открытое подключение к веб-сервису.

Область Methods For Fetching And Updating Data (методы для извлечения и обновления данных) содержит следующие методы.

• GetDataAsync. Этот тот самый метод, который был создан ранее. Он осуществляет подключение к веб-сервису и извлекает сведения о каждом клиенте.

• ValidateCustomer. Этот метод получает Customer-объект и проверяет свойства FirstName и LastName, чтобы убедиться, что они не пусты. Он также проверяет свойства EmailAddress и Phone, чтобы убедиться, что они содержат информацию, имеющую допустимый формат. Метод возвращает true, если данные приемлемы, и false — в противном случае. Этот метод будет использоваться в данном упражнении чуть позже, при создании команды SaveChanges.

174765.png

ПРИМЕЧАНИЕ Код, проверяющий свойства EmailAddress и Phone, определяет соответствие содержащихся в них значений регулярному выражению путем использования класса Regex, определенного в пространстве имен System.Text.RegularExpressions. Чтобы воспользоваться этим классом, нужно в Regex-объекте определить регулярное выражение, указывающее шаблон, которому должны соответствовать данные, а затем вызвать метод IsMatch, принадлежащий Regex-объекту, с данными, подлежащими проверке. Дополнительные сведения о регулярных выражениях и классе Regex можно найти в статье «Объектная модель регулярных выражений» на веб-сайте Microsoft по адресу .

• CopyCustomer. Задача этого метода заключается в создании поверхностной копии Customer-объекта. Метод будет использоваться при создании команды EditCustomer для создания копии исходных данных клиента перед их изменением. Если пользователь решит отменить изменения, исходные данные могут быть просто скопированы обратно из копии, созданной этим методом.

В обозревателе решений раскройте проект Customers и дважды щелкните на файле ViewModel.cs, чтобы открыть его в окне редактора.

Объявление строковой переменной ServerUrl ближе к началу класса ViewModel имеет следующий вид:

private const string ServerUrl = "/";

Измените строку и замените текст <webappname> именем веб-сервиса, созданного вами ранее при изучении этой главы.

В файле ViewModel.cs найдите область Methods For Fetching And Updating Data, раскрыв ее, если нужно. В этой области выше метода ValidateCustomer создайте показанный здесь метод Add:

// Создает новые (пустые) сведения о клиенте и переводит форму в режим добавления

private void Add()

{

    Customer newCustomer = new Customer { CustomerID = 0 };

    this.customers.Insert(currentCustomer, newCustomer);

    this.IsAdding = true;

    this.OnPropertyChanged(nameof(Current));

}

Этот метод создает новый Customer-объект. За исключением свойства CustomerID, для которого в целях отображения на экране временно установлено значение 0, он совершенно пуст. Как уже упоминалось, настоящее значение для этого свойства генерируется при сохранении сведений о клиенте в базе данных. Клиент добавляется к списку клиентов (для отображения данных в этом списке представление использует привязку данных), модель представления переводится в режим добавления, и инициируется событие PropertyChanged, показывающее, что Current-клиент подвергся изменениям.

Добавьте к списку в начале класса ViewModel следующую переменную типа Command, выделенную жирным шрифтом:

public class ViewModel : INotifyPropertyChanged

{

    ...

    public Command LastCustomer { get; private set; }

    public Command AddCustomer { get; private set; }

    ...

}

В конструкторе ViewModel создайте экземпляр команды AddCustomer, выделенный далее жирным шрифтом:

public ViewModel()

{

    ...

    this.LastCustomer = new Command(this.Last, ...);

    this.AddCustomer = new Command(this.Add,

        () => { return this.CanBrowse; });

    ...

}

Этот код ссылается на только что созданный вами метод Add. Команда включается, если у модели представления имеется подключение к веб-сервису и текущим является режим просмотра (команда AddCustomer не будет включена, если модель представления уже находится в режиме добавления).

Создайте после метода Add в области Methods For Fetching And Updating Data открытую переменную типа Customer по имени oldCustomer и определите еще один метод по имени Edit:

// Редактирует сведения о текущем клиенте

// сохраняет имеющиеся сведения о клиенте

// и переводит форму в режим редактирования

private Customer oldCustomer;

 

private void Edit ()

{

    this.oldCustomer = new Customer();

    this.CopyCustomer(this.Current, this.oldCustomer);

    this.IsEditing = true;

}

Этот метод копирует сведения о Current-клиенте в переменную oldCustomer и переводит модель представления в режим редактирования. В этом режиме пользователь может изменять сведения о текущем клиенте. Если пользователь сразу после этого решит отменить эти изменения, исходные данные могут быть скопированы обратно из переменной oldCustomer.

Добавьте к списку в начале класса ViewModel следующую переменную типа Command, выделенную жирным шрифтом:

public class ViewModel : INotifyPropertyChanged

{

    ...

    public Command AddCustomer { get; private set; }

    public Command EditCustomer { get; private set; }

    ...

}

В конструкторе ViewModel создайте экземпляр команды EditCustomer, выделенный далее жирным шрифтом:

public ViewModel()

{

    ...

    this.AddCustomer = new Command(this.Add, ...);

    this.EditCustomer = new Command(this.Edit,

        () => { return this.CanBrowse; });

    ...

}

Этот код аналогичен инструкции для команды AddCustomer, за исключением того, что он ссылается на метод Edit.

В области Methods For Fetching And Updating Data добавьте к находящемуся в ней классу ViewModel после метода Edit показанный далее метод Discard:

// Отменяет изменения, сделанные в режиме добавления или редактирования,

// и возвращает форму в режим просмотра

private void Discard ()

{

    // Если пользователь добавил нового клиента, этот клиент удаляется

    if (this.IsAdding)

    {

        this.customers.Remove(this.Current);

        this.OnPropertyChanged(nameof(Current));

    }

 

    // Если пользователь отредактировал сведения о существующем клиенте,

    // восстановление сохраненных сведений

    if (this.IsEditing)

    {

        this.CopyCustomer(this.oldCustomer, this.Current);

    }

 

    this.IsBrowsing = true;

}

Задача этого метода заключается в том, чтобы позволить пользователю отменить любые изменения, внесенные тогда, когда модель представления находится в режиме добавления или редактирования. Если модель представления находится в режиме добавления, текущий клиент удаляется из списка (речь идет о новом клиенте, который был создан методом Add) и инициируется событие PropertyChanged, чтобы показать, что сведения о текущем клиенте в списке клиентов подверглись изменениям. Если модель представления находится в режиме редактирования, исходные сведения, находящиеся в переменной oldCustomer, копируются обратно, в запись текущего, отображаемого на экране клиента. В заключение модель представления возвращается в режим просмотра.

Добавьте переменную DiscardChanges типа Command к списку в начале класса ViewModel и обновите конструктор для создания экземпляра этой команды (изменения и добавления показаны далее жирным шрифтом):

public class ViewModel : INotifyPropertyChanged

{

    ...

    public Command EditCustomer { get; private set; }

    public Command DiscardChanges { get; private set; }

    ...

    public ViewModel()

    {

        ...

        this.EditCustomer = new Command(this.Edit, ...);

        this.DiscardChanges = new Command(this.Discard,

            () => { return this.CanSaveOrDiscardChanges; });

    }

    ...

}

Заметьте, что команда DiscardChanges включается, только если свойство CanSaveOrDiscardChanges имеет значение true, у модели представления есть подключение к веб-сервису и она находится в режиме добавления или редактирования.

Добавьте в области Methods For Fetching And Updating Data после метода Discard еще один метод по имени SaveAsync, показанный в следующем примере кода. Этот метод должен быть помечен модификатором async:

// Сохраняет новые или обновленные сведения о клиенте, отправляя их

// веб-сервису, и возвращает форму в режим просмотра

private async void SaveAsync()

{

    // Проверка сведений о клиенте

    if (this.ValidateCustomer(this.Current))

    {

        // Продолжение только в том случае, если сведения о клиенте приемлемы

        this.IsBusy = true;

        try

        {

            // Преобразование сведений о текущем клиенте в формат HTTP-запроса

            // с полезной нагрузкой в формате JSON

            var serializedData = JsonConvert.SerializeObject(this.Current);

            StringContent content =

                new StringContent(serializedData, Encoding.UTF8, "text/json");

 

            // Если пользователь добавляет сведения о новом клиенте,

            // отправка веб-сервису HTTP POST-запроса со сведениями

            if (this.IsAdding)

            {

                var response =

                    await client.PostAsync("api/customers", content);

                if (response.IsSuccessStatusCode)

                {

                    // TODO: Отобразить сведения о новом клиенте

                }

                    // TODO: Обработать сбой POST-запроса

            }

            // Пользователь должен редактировать сведения об уже существующем

            // клиенте, поэтому отправка сведений с использованием PUT-запроса

               else

            {

                string path = $"api/customers/{this.Current.CustomerID}";

 

                var response = await client.PutAsync(path, content);

                if (response.IsSuccessStatusCode)

                {

                    this.IsEditing = false;

                    this.IsBrowsing = true;

                }

                // TODO: Обработать сбой PUT-запроса

            }

        }

        catch (Exception e)

        {

            // TODO: Обработать исключения

        }

        finally

        {

            this.IsBusy = false;

        }

    }

}

Этот метод еще не приобрел завершенный вид. Только что введенный код проверяет приемлемость сведений о клиенте. При положительном исходе сведения могут быть сохранены, после чего свойство IsBusy класса ViewModel устанавливается в true, чтобы показать, что операция сохранения может занять некоторое время, пока информация отправляется по сети веб-сервису. (Вспомним, что к этому свойству привязано свойство IsActive элемента управления ProgressRing, принадлежащего форме Customers, и пока данные не будут сохранены, на экране будет находиться кольцевой индикатор занятости.)

Код в блоке try определяет, добавляет ли пользователь сведения о новом клиенте, или же он редактирует сведения, принадлежащие уже существующему клиенту. Если пользователь добавляет сведения о новом клиенте, код использует метод PostAsync объекта типа HttpClient, чтобы отправить веб-сервису POST-запрос. Следует иметь в виду, что POST-запрос отправляется методу PostCustomer в классе CustomersController веб-сервиса, а этот метод ожидает получения в качестве своего параметра объекта типа Customer. Сведения передаются в формате JSON.

Если пользователь редактирует сведения о существующем клиенте, приложение вызывает метод PutAsync объекта типа HttpClient. Этот метод генерирует PUT-запрос, передаваемый методу PutCustomer в классе CustomersController веб-сервиса. Метод PutCustomer обновляет сведения о клиенте в базе данных и ожидает в качестве параметров идентификатор клиента и сведения о нем. Эти данные передаются в адрес веб-сервиса также в JSON-формате.

Когда данные отправлены, значение свойства IsBusy устанавливается в false, что заставляет элемент управления ProgressRing исчезнуть с экрана.

Замените в методе SaveAsync комментарий // TODO: Отобразить сведения о новом клиенте следующим кодом, выделенным жирным шрифтом:

if (response.IsSuccessStatusCode)

{

    // Получение ID для нового клиента и отображение сведений о нем

    Uri customerUri = response.Headers.Location;

    var newCust = await this.client.GetAsync(customerUri);

    if (newCust.IsSuccessStatusCode)

    {

        var customerData = await newCust.Content.ReadAsStringAsync();

        this.CopyCustomer(

            JsonConvert.DeserializeObject<Customer>(customerData), this.Current);

        this.OnPropertyChanged(nameof(Current));

        this.IsAdding = false;

        this.IsBrowsing = true;

    }

    else

    {

        // TODO: Обработать сбой GET-запроса

    }

}

В столбце CustomerID таблицы Customer, принадлежащей базе данных AdventureWorks, содержатся автоматически генерируемые значения. Когда создаются сведения о новом клиенте, данные для этих значений предоставляются не пользователем, а самой базой данных, когда в нее добавляются сведения о новом клиенте. Тем самым база данных обеспечивает наличие у каждого клиента уникального идентификатора. Следовательно, после отправки веб-сервису POST-запроса вы должны отправить GET-запрос, чтобы получить идентификатор пользователя. К счастью, HttpResponseMessage-объект передает обратно через веб-сервис в качестве результата POST-запроса URL-адрес, который приложение может использовать для запроса новых данных. Этот URL-адрес доступен в свойстве ответа Headers.Location, и он будет иметь формат api/Customers/n, где n — это идентификатор клиента. Только что добавленный вами код отправляет GET-запрос по этому URL-адресу, используя метод GetAsync объекта типа HttpClient. Он считывает обратно сведения о новом пользователе с помощью принадлежащего ответу метода ReadAsStringAsync. Затем код обновляет этими данными сведения о клиенте, хранящиеся в коллекции клиентов.

174772.png

ПРИМЕЧАНИЕ Может создаться впечатление, что этот код выполняет совершенно ненужный обмен данными с веб-сервисом, чтобы извлечь идентификатор клиента, который доступен в свойстве Headers.Location сообщения, полученного в ответ на POST-запрос. Но этим действием проверяется, что данные были сохранены правильно, ведь там могут быть и другие поля, преобразуемые веб-сервисом при сохранении данных, следовательно, этот процесс гарантирует приложению, что оно отображает данные в том виде, в котором они сохраняются в базе данных.

Добавьте показанную далее переменную SaveChanges типа Command к списку в начале класса ViewModel и обновите конструктор для создания экземпляра этой команды:

public class ViewModel : INotifyPropertyChanged

{

    ...

    public Command DiscardChanges { get; private set; }

    public Command SaveChanges { get; private set; }

    ...

    public ViewModel()

    {

        ...

        this.DiscardChanges = new Command(this.Discard, ...);

        this.SaveChanges = new Command(this.SaveAsync,

            () => { return this.CanSaveOrDiscardChanges; });

        ...

    }

    ...

}

Щелкните в меню Сборка на пункте Собрать решение и убедитесь, что ваше приложение прошло компиляцию без ошибок.

Веб-сервис нуждается в обновлении с целью поддержки функциональных возможностей редактирования. В частности, при добавлении или редактировании сведений о клиенте для свойства клиента ModifiedDate нужно устанавливать значение, отражающее дату внесения изменения. Кроме того, если создаются сведения о новом клиенте, то перед их сохранением свойство rowguid объекта типа Customer должно быть заполнено новым значением GUID. (Это обязательный столбец в таблице Customer, другие приложения, используемые в организации Adventure Works, используют этот столбец для отслеживания информации о клиентах.)

174778.png

ПРИМЕЧАНИЕ GUID означает глобально уникальный идентификатор. GUID представляет собой строку, сгенерированную Windows, которая практически гарантирует ее уникальность (существует весьма призрачная возможность, что Windows может сгенерировать не уникальный GUID, но она настолько мала, что ею можно пренебречь). GUID-идентификаторы часто используются базами данных в качестве значений для ключей, идентифицирующих отдельные строки, в данном случае таблицы Customer, имеющейся в базе данных AdventureWorks.

Обновление веб-сервиса для поддержки функциональных возможностей добавления и редактирования

В обозревателе решений в проекте AdventureWorksService раскройте папку Controllers и откройте файл CustomersController.cs, чтобы он отобразился в окне редактора.

Добавьте к методу PostCustomer перед инструкциями, сохраняющими сведения о новом клиенте в базе данных, следующий код, выделенный жирным шрифтом:

// POST api/Customers

[ResponseType(typeof(Customer))]

public async Task<IHttpActionResult> PostCustomer(Customer customer)

{

    if (!ModelState.IsValid)

    {

        ...

    }

    customer.ModifiedDate = DateTime.Now;

    customer.rowguid = Guid.NewGuid();

    db.Customers.Add(customer);

    await db.SaveChangesAsync();

    ...

}

Внесите в принадлежащее методу PutCustomer свойство клиента ModifiedDate, которое находится перед инструкцией, показывающей, что сведения о клиенте были обновлены, изменения, выделенные жирным шрифтом:

// PUT api/Customers/5

[ResponseType(typeof(void))]

public async Task<IHttpActionResult> PutCustomer(int id, Customer customer)

{

    ...

    customer.ModifiedDate = DateTime.Now;

    db.Entry(customer).State = EntityState.Modified;

    ...

}

Разверните веб-службу в облаке, следуя процедурам, рассмотренным ранее в упражнении «Развертывание веб-сервиса в облаке», но выложите веб-сервис не в новое веб-приложение, а в то, что уже существует в облаке.

Выдача отчета об ошибках и обновление пользовательского интерфейса

Вы уже добавили команды, с помощью которых пользователь может извлечь, добавить, редактировать и сохранить сведения о клиентах. Но если произойдет какой-либо сбой и случится ошибка, пользователь не будет знать, что случилось, потому что в класс ViewModel не включена возможность выдачи отчета об ошибках. Одним из способов добавления этой функции является перехват сообщений о выданных исключениях и предоставление их в виде свойства класса ViewModel. Для связи с этим свойством и отображения на экране сообщения об ошибках представление может использовать привязку данных.

Добавление к классу ViewModel возможности выдачи отчета об ошибках

Вернитесь в проект Customers и выведите в окно редактора файл ViewModel.cs. Найдите и раскройте, если необходимо, область по имени Properties For "Busy" And Error Message Handling (свойства для обработки сообщений о занятости и об ошибках).

Добавьте после свойства IsBusy закрытую строковую переменную _lastError и открытое строковое свойство LastError:

private string _lastError = null;

public string LastError

{

    get { return this._lastError; }

    private set

    {

        this._lastError = value;

        this.OnPropertyChanged(nameof(LastError));

    }

}

Найдите в области Methods For Fetching And Updating Data метод GetDataAsync. Этот метод содержит следующий обработчик исключений:

catch (Exception e)

{

    // TODO: Обработать исключения

}

Замените комментарий // TODO: Обработать исключения следующим кодом, выделенным жирным шрифтом:

catch (Exception e)

{

    this.LastError = e.Message;

}

Замените в блоке else, непосредственно предшествующем обработчику исключений, комментарий // TODO: Обработать сбой GET-запроса следующим кодом, показанным жирным шрифтом:

else

{

    this.LastError = response.ReasonPhrase;

}

Свойство ReasonPhrase объекта типа HttpResponseMessage содержит строку, показывающую причину сбоя, о которой сообщил веб-сервис.

Добавьте в конец блока if, непосредственно предшествующего блоку else, следующую инструкцию, показанную жирным шрифтом:

if

{

    ...

    this.IsAtEnd = (this.customers.Count == 0);

    this.LastError = String.Empty;

}

else

{

    this.LastError = response.ReasonPhrase;

}

Эта инструкция удаляет из свойства LastError любые сообщения об ошибках.

Найдите метод ValidateCustomer и добавьте непосредственно перед инструкцией return следующую инструкцию, показанную жирным шрифтом:

private bool ValidateCustomer(Customer customer)

{

    ...

    this.LastError = validationErrors;

    return !hasErrors;

}

Метод ValidateCustomer наполняет переменную validationErrors информацией обо всех свойствах в объекте типа Customer, содержащих неприемлемые данные. Только что добавленная вами инструкция копирует эту информацию в свойство LastError.

Найдите метод SaveAsync. Добавьте к нему код, показанный жирным шрифтом, чтобы перехватить любые ошибки и сведения о сбоях HTTP веб-сервиса:

private async void SaveAsync()

{

    // Проверка сведений о клиенте

    if (this.ValidateCustomer(this.Current))

    {

        ...

        try

        {

            ...

            // Если пользователь добавляет сведения о новом клиенте,

            // отправка веб-сервису HTTP POST-запроса со сведениями

            if (this.IsAdding)

            {

                ...

                if (response.IsSuccessStatusCode)

                {

                    ...

                    if (newCust.IsSuccessStatusCode)

                    {

                        ...

                        this.IsBrowsing = true;

                        this.LastError = String.Empty;

                    }

                    else

                    {

                        // TODO: Обработать сбой GET-запроса

                        this.LastError = response.ReasonPhrase;

                    }

                }

                // TODO: Обработать сбой POST-запроса

                else

                {

                    this.LastError = response.ReasonPhrase;

                }

            }

            // Пользователь должен редактировать сведения об уже существующем

            // клиенте, поэтому отправка сведений с использованием PUT-запроса

            else

            {

                ...

                if (response.IsSuccessStatusCode)

                {

                    this.IsEditing = false;

                    this.IsBrowsing = true;

                    this.LastError = String.Empty;

                }

                // TODO: Обработать сбой PUT-запроса

                else

                {

                    this.LastError = response.ReasonPhrase;

                }

            }

        }

        catch (Exception e)

        {

            // TODO: Обработать исключения

            this.LastError = e.Message;

        }

        finally

        {

            this.IsBusy = false;

        }

    }

}

Найдите метод Discard, а затем добавьте в конец этого метода инструкцию, выделенную здесь жирным шрифтом:

private void Discard()

{

    ...

    this.LastError = String.Empty;

}

В меню Сборка щелкните на пункте Собрать решение и убедитесь в том, что приложение было собрано без ошибок.

Теперь работа над моделью представления завершена. В заключение следует доработать представление, предоставляемое формой Customers, включив в него новые команды, информацию о состоянии и функции отчета об ошибках.

Включение в форму Customers функциональных возможностей по добавлению и редактированию сведений о клиентах

В окне конструктора откройте файл MainPage.xaml. XAML-разметка для формы MainPage уже подвергалась изменениям, и к элементам управления Grid были добавлены следующие отображающие данные элементы управления типа TextBlock:

<Page

    x:Class="Customers.MainPage"

    ...>

 

    <Grid Style="{StaticResource GridStyle}">

        ...

        <Grid x:Name="customersTabularView" ...>

            ...

            <Grid Grid.Row="2">

                ...

                <TextBlock Grid.Row="6" Grid.Column="1"

    Grid.ColumnSpan="7" Style="{StaticResource ErrorMessageStyle}"/>

            </Grid>

        </Grid>

        <Grid x:Name="customersColumnarView" Margin="20,10,20,110" ...>

            ...

            <Grid Grid.Row="1">

                ...

                <TextBlock Grid.Row="6" Grid.Column="0"

                Grid.ColumnSpan="2" Style="{StaticResource ErrorMessageStyle}"/>

            </Grid>

        </Grid>

    ...

    </Grid>

    ...

</Page>

Ресурс ErrorMessageStyle, на который ссылаются эти элементы управления типа TextBlock, определены в файле AppStyles.xaml.

Установите для свойства Text обоих элементов управления типа TextBlock выделенную далее жирным шрифтом привязку к свойству LastError класса ViewModel:

...

<TextBlock Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="7"

Style="{StaticResource ErrorMessageStyle}" Text="{Binding LastError}"/>

...

<TextBlock Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2"

Style="{StaticResource ErrorMessageStyle}" Text="{Binding LastError}"/>

Элементы управления типа TextBox и ComboBox, в которых в форме показываются сведения о клиенте, должны позволять пользователю изменять эти сведения только в том случае, если модель представления находится в режиме добавления или редактирования, в противном случае изменение сведений должно быть недоступно. Добавьте к каждому из этих элементов управления свойство IsEnabled и привяжите его к свойству IsAddingOrEditing класса ViewModel:

...

<TextBox Grid.Row="1" Grid.Column="1" x:Name="id"

IsEnabled="{Binding IsAddingOrEditing}" .../>

<TextBox Grid.Row="1" Grid.Column="5" x:Name="firstName"

IsEnabled="{Binding IsAddingOrEditing}" .../>

<TextBox Grid.Row="1" Grid.Column="7" x:Name="lastName"

IsEnabled="{Binding IsAddingOrEditing}" .../>

<ComboBox Grid.Row="1" Grid.Column="3" x:Name="title"

IsEnabled="{Binding IsAddingOrEditing}" .../>

...

<TextBox Grid.Row="3" Grid.Column="3" ... x:Name="email"

IsEnabled="{Binding IsAddingOrEditing}" .../>

...

<TextBox Grid.Row="5" Grid.Column="3" ... x:Name="phone"

IsEnabled="{Binding IsAddingOrEditing}" .../>

...

...

<TextBox Grid.Row="0" Grid.Column="1" x:Name="cId" />

IsEnabled="{Binding IsAddingOrEditing}" .../>

<TextBox Grid.Row="2" Grid.Column="1" x:Name="cFirstName"

IsEnabled="{Binding IsAddingOrEditing}" .../>

<TextBox Grid.Row="3" Grid.Column="1" x:Name="cLastName"

IsEnabled="{Binding IsAddingOrEditing}" .../>

<ComboBox Grid.Row="1" Grid.Column="1" x:Name="cTitle"

IsEnabled="{Binding IsAddingOrEditing}" .../>

...

<TextBox Grid.Row="4" Grid.Column="1" x:Name="cEmail"

IsEnabled="{Binding IsAddingOrEditing}" .../>

...

<TextBox Grid.Row="5" Grid.Column="1" x:Name="cPhone"

IsEnabled="{Binding IsAddingOrEditing}" .../>

Добавьте к нижней части страницы панель команд, воспользовавшись для этого элементом <Page.BottomAppBar> и поместив эту панель сразу же после верхней панели команд. На этой панели команд должны находиться кнопки для команд добавления сведений о клиенте — AddCustomer, редактирования сведений о клиенте — EditCustomer, сохранения изменений — SaveChanges и отмены изменений — DiscardChanges:

<Page ...>

    ...

    <Page.TopAppBar >

        ...

    </Page.TopAppBar>

    <Page.BottomAppBar>

        <CommandBar>

            <AppBarButton x:Name="addCustomer" Icon="Add"

Label="New Customer" Command="{Binding Path=AddCustomer}"/>

            <AppBarButton x:Name="editCustomer" Icon="Edit"

Label="Edit Customer" Command="{Binding Path=EditCustomer}"/>

            <AppBarButton x:Name="saveChanges" Icon="Save"

Label="Save Changes" Command="{Binding Path=SaveChanges}"/>

            <AppBarButton x:Name="discardChanges" Icon="Undo"

Label="Undo Changes" Command="{Binding Path=DiscardChanges}"/>

        </CommandBar>

    </Page.BottomAppBar>

</Page>

Заметьте, что значки, на которые ссылаются кнопки, являются стандартными изображениями, предоставляемыми шаблоном Пустое приложение.

Тестирование приложения Customers

В меню Отладка щелкните на пункте Начать отладку, чтобы собрать и запустить приложение. Когда появится форма Customers, обратите внимание на то, что элементы типа TextBox и ComboBox недоступны, поскольку представление работает в режиме просмотра.

Щелкните правой кнопкой мыши на форме и убедитесь в появлении верхней и нижней панелей команд.

Вы, как и прежде, можете пользоваться кнопками First, Next, Previous и Last на верхней панели команд (не забудьте, что кнопки First и Previous не будут доступны, пока вы не перейдете со страницы со сведениями о первом клиенте на следующие страницы). На нижней панели команд должны быть доступны кнопки Add и Edit, а кнопки Save и Discard — недоступны, поскольку команды AddCustomer и EditCustomer разрешены, когда модель представления находится в режиме просмотра, а команды SaveChanges и DiscardChanges разрешены, только когда модель представления находится в режиме добавления или редактирования (рис. 27.21).

27_21.tif 

Рис. 27.21

Щелкните на нижней панели команд на кнопке редактирования сведений о клиенте.

Кнопки на верхней панели команд станут недоступны, поскольку модель представления теперь находится в режиме редактирования. Кроме того, теперь также станут недоступны кнопки Add и Edit, но должны стать доступны кнопки Save и Discard. К тому же в форме должны стать доступны поля ввода данных, и пользователь сможет изменить сведения о клиенте (рис. 27.22).

27_22.tif 

Рис. 27.22

Отредактируйте сведения о клиенте: сотрите его фамилию, наберите Test для адреса электронной почты, наберите Test 2 для номера телефона, а затем щелкните на кнопке Save.

Эти изменения нарушают правила, используемые при проверке данных и реализованные в методе ValidateCustomer. Этот метод заполняет свойство LastError объекта типа ViewModel сообщением о результатах проверки, которое выводится в форме в элементе типа TextBlock, привязанном к свойству LastError (рис. 27.23).

27_23.tif 

Рис. 27.23

Щелкните на кнопке Discard и убедитесь, что в форме восстановились исходные данные. Сообщение о результатах проверки исчезло, а модель представления вернулась в режим просмотра.

Щелкните на кнопке Add. Поля формы должны очиститься (кроме поля ID, которое показывает значение 0). Введите сведения о новом клиенте. Не забудьте ввести фамилию и имя, правильный адрес электронной почты в формате и цифровой номер телефона (кроме цифр в него можно включать круглые скобки, дефисы и пробелы).

Щелкните на кнопке Save. Если данные приемлемы (проверка не выявила ошибок), они должны быть сохранены в базе данных. В поле ID вы должны увидеть идентификатор, сгенерированный для нового клиента, а модель представления должна переключиться обратно в режим просмотра.

Поэкспериментируйте с приложением, многократно добавляя и редактируя сведения о клиентах. Заметьте, что вы можете изменить размер области просмотра, чтобы вывести столбцовую разметку, но при этом форма все равно будет работать.

Завершив эксперименты, вернитесь в среду Visual Studio и остановите отладку.

Выводы

В этой главе вы узнали, как среда Entity Framework используется для создания entity-модели, которой можно воспользоваться для подключения к базе данных SQL Server. База данных может быть запущена локально или в облаке. Вы также увидели, как создается REST веб-сервис, которым UWP-приложение может воспользоваться для запроса и обновления данных, хранящихся в базе данных, посредством entity-модели, и узнали, как включать код, вызывающий веб-сервис, в модель представления (в класс ViewModel).

Вы выполнили все упражнения данной книги. Я надеюсь, что ваше знакомство с языком C# состоялось успешно и вы усвоили порядок использования среды Visual Studio 2015 для создания профессиональных приложений, работающих под управлением Windows 10. Но жизнь продолжается. Вы преодолели лишь первый барьер на своем пути, а лучшие программисты, работающие на C#, извлекают уроки из непрерывно приобретаемого опыта, который может быть получен вами только в процессе создания приложений на C#. По мере этого вы откроете для себя новые приемы использования языка C# и множество функциональных возможностей среды Visual Studio 2015, на описание которых мне в этой книге не хватило места. Также следует учесть, что язык C# не стоит на месте. В далеком 2001 году, когда мною было написано первое издание этой книги, C# предлагал синтаксис и семантику, необходимую для создания приложений, использующих среду Microsoft .NET Framework 1.0. Затем последовали усовершенствования, добавленные в Visual Studio и .NET Framework 1.1 в 2003-м и 2005-м, появился C# 2.0 с поддержкой обобщений и среды .NET Framework 2.0. В C# 3.0 было добавлено множество новых функций, таких как безымянные типы, лямбда-выражения и, что наиболее существенно, LINQ-расширение. В C# 4.0 произошло дальнейшее расширение языка, появились поддержка именованных аргументов, необязательных параметров, контрвариантных и ковариантных интерфейсов, интеграция с динамическими языками. В C# 5.0 была добавлена полная поддержка асинхронной обработки посредством ключевого слова async и оператора await. В C# 6.0 были представлены дальнейшие усовершенствования языка, такие как методы с телом в виде выражения, строковая интерполяция, оператор nameof, фильтры исключений и многое другое.

Параллельно с развитием языка программирования C# со времени выпуска первого издания книги подверглась существенным изменениям и операционная система Windows. Пожалуй, наиболее радикальными за это время стали изменения, появившиеся с выходом Windows 8 и следующих за ней версий, и перед разработчиками, знакомыми с ранними выпусками Windows, теперь стоят новые увлекательные задачи по созданию приложений для современных, основанных на сенсорных технологиях, мобильных платформ, предоставляемых Windows 10. Несомненно, среда Visual Studio 2015 и язык C# будут играть весьма важную роль в оказании вам помощи в решении этих задач.

Что принесет с собой новая версия C# и Visual Studio? Ждите новостей!

Краткий справочник

Чтобы

Сделайте следующее

Создать entity-модель с помощью среды Entity Framework

Добавьте новый элемент к своему проекту, воспользовавшись шаблоном Модель ADO.NET EDM. Используя мастер моделей EDM, создайте код подключения к базе данных, содержащей таблицы, которые нужно смоделировать, и выберите таблицы, необходимые для вашего приложения.

Удалите в модели данных все столбцы, не используемые вашим приложением (при условии, что в них есть значения по умолчанию и если ваше приложение вставляет в базу данных новые элементы)

Создать REST веб-сервис, предоставляющий удаленный доступ к базе данных через entity-модель

Создайте проект ASP.NET, воспользовавшись шаблоном Web API. Запустите мастер Добавление шаблона и выберите шаблон Контроллер Web API2 с действиями, использующий Entity Framework. Укажите в качестве класса модели имя соответствующего entity-класса из entity-модели, а в качестве класса контекста данных — класс, подходящий entity-модели

Воспользоваться REST веб-сервисом в UWP-приложении

Добавьте к проекту библиотеки веб-клиента ASP.NET и воспользуйтесь для подключения к базе данных объектом HttpClient. Установите в качестве значения свойства BaseAddress объекта типа HttpClient ссылку на адрес веб-сервиса, например:

string ServerUrl = "/";

HttpClient client = new HttpClient();

client.BaseAddress = new Uri(ServerUrl);

Извлечь данные из REST веб-сервиса в UWP-приложение

Вызовите метод GetAsync объекта типа HttpClient и укажите URI ресурса, к которому идет обращение. Если метод GetAsync отработает успешно, извлеките данные, возвращенные методом GetAsync, воспользовавшись методом ReadAsStringAsync объекта типа HttpResponseMessage, например:

 

HttpClient client = ...;

var response = await

    client.GetAsync(«api/customers»);

if (response.IsSuccessStatusCode)

{

    var customerData = await

    response.Content.

    ReadAsStringAsync();

    ...

}

else

{

    // Сбой GET-запроса

}

Добавить новый элемент данных в REST веб-сервис из UWP-приложения

Воспользуйтесь методом PostAsync объекта типа HttpClient и укажите в качестве параметра новый создаваемый элемент и URI коллекции, содержащей этот элемент. Проверьте статус объекта HttpResponseMessage, возвращенного методом PostAsync, чтобы убедиться, что POST-операция прошла успешно, например:

HttpClient client = ...;

StringContent newCustomer = ...;

var response = await client.PostAsync(

    "api/customers", newCustomer);

if (!response.IsSuccessStatusCode)

{

    // Сбой POST-запроса

}

Обновить существующий элемент в REST веб-сервисе из UWP-приложения

Воспользуйтесь методом PutAsync объекта типа HttpClient и укажите в качестве параметров обновляемый элемент и URL этого элемента. Проверьте статус объекта HttpResponseMessage, возвращенного методом PutAsync, чтобы убедиться, что PUT-операция прошла успешно, например:

HttpClient client = ...;

StringContent updatedCustomer = ...;

string path =

    $"api/customers/{updatedCustomer.CustomerID}";

var response = await client.PutAsync(

    path, updatedCustomer);

if (!response.IsSuccessStatusCode)

{

    // Сбой PUT-запроса

}

 

 

Назад: 26. Отображение и поиск данных в приложении универсальной платформы Windows
На главную: Предисловие

Антон
Перезвоните мне пожалуйста 8(812)642-29-99 Антон.
Антон
Перезвоните мне пожалуйста по номеру 8(904) 332-62-08 Антон.
Антон
Перезвоните мне пожалуйста, 8 (904) 606-17-42 Антон.
Антон
Перезвоните мне пожалуйста по номеру. 8 (953) 367-35-45 Антон
Ксения
Текст от профессионального копирайтера. Готово через 1 день. Консультация бесплатно. Жми roholeva(точка)com