Одностраничные приложения
193ASP.NET --- ASP.NET MVC 5 --- Одностраничные приложения
В этой и последующих статьях будет описано средство Web API, которое является относительно новым дополнением платформы ASP.NET и позволяет быстро и легко создавать веб-службы, предоставляющие API-интерфейс для HTTP-клиентов.
Средство Web API основано на том же базисе, что и приложения ASP.NET MVC Framework, но не является частью инфраструктуры ASP.NET MVC Framework. Вместо этого в Microsoft взяли набор ключевых классов и связанных с ними характеристик из пространства имен System.Web.Mvc и продублировали его в пространстве имен System.Web.Http.
Идея в том, что Web API - это часть главной платформы ASP.NET и может использоваться в других типах веб-приложений либо в качестве автономного механизма веб-служб. Одним из главных применений средства Web API считается создание одностраничных приложений (single-page application - SPA) путем комбинирования Web API с возможностями ASP.NET MVC Framework. Далее будет показано, что собой представляют SPA-приложения и как они работают.
Упрощение создания веб-служб является неотъемлемой особенностью Web API. Оно представляет собой значительное улучшение по сравнению с другими технологиями построения веб-служб, которые компания Microsoft предлагала на протяжении последнего десятилетия. Мне нравится средство Web API, и вы должны использовать его в своих проектах, не в последнюю очередь потому, что оно отличается простотой и построено на базе того же самого проектного решения, что и ASP.NET MVC Framework.
Термин одностраничное приложение (SPA) применяется довольно широко. Наиболее согласованным является его определение как веб-приложения, начальное содержимое которого доставляется в виде комбинации HTML-разметки и кода JavaScript, а последующие операции выполняются с участием веб-службы REST, доставляющей данные в формате JSON в ответ на запросы Ajax.
Это отличается от того вида приложений, которые строились в примерах ранее, где результатами операций, выполняемых пользователем, были новые HTML-документы, генерируемые в ответ на синхронные HTTP-запросы. Такие приложения будут называться приложениями полного обмена (round-trip application - RTA).
Преимущества приложения SPA связаны с тем, что оно требует меньшей полосы пропускания и пользователь получает более гладкий интерфейс. Недостатки касаются того, что такой более гладкий интерфейс может оказаться трудным в достижении, а сложность кода JavaScript, требуемого для приложения SPA, означает необходимость в тщательном проектировании и тестировании.
В большинстве приложений приемы SPA и RTA смешиваются, при этом каждая крупная область функциональности приложения доставляется как SPA, а навигация между областями функциональности управляется с применением стандартных HTTP-запросов, которые создают новый HTML-документ.
Пример одностраничного приложения
Для целей этих статей в Visual Studio создан новый проект MVC по имени WebServices с использованием шаблона Empty (Пустой). В разделе Add folders and core references for (Добавить папки и основные ссылки для) были отмечены флажки MVC и Web API, как показано на рисунке ниже:
Этот проект будет использоваться для создания обычного приложения ASP.NET MVC Framework, после чего с помощью Web API будет создана веб-служба. По готовности веб-службы приложение ASP.NET MVC Framework будет превращено в одностраничное приложение.
Создание модели
Приложение будет создавать и поддерживать набор заявок на бронирование помещений. Приложение планируется сохранять простым, чтобы можно было сосредоточиться на механике описываемого средства, поэтому заявки на бронирование будут состоять только из имени заказчика и местоположения помещения. В папку Models добавлен файл класса по имени Reservation.cs, содержимое которого показано в примере ниже:
namespace WebServices.Models
{
public class Reservation
{
public int ReservationId { get; set; }
public string ClientName { get; set; }
public string Location { get; set; }
}
}
Планируется создать простую коллекцию объектов Reservation, хранящуюся в памяти, которая будет действовать в качестве хранилища данных. Нет необходимости заниматься установкой базы данных, однако нужна возможность выполнения операций CRUD над коллекцией объектов модели, что позволит продемонстрировать ряд важных аспектов Web API. В папку Models добавляется также файл класса по имени ReservationRepository.cs:
using System.Collections.Generic;
using System.Linq;
namespace WebServices.Models
{
public class ReservationRepository
{
private static ReservationRepository repo = new ReservationRepository();
public static ReservationRepository Current
{
get
{
return repo;
}
}
private List<Reservation> data = new List<Reservation> {
new Reservation {
ReservationId = 1, ClientName = "Петр", Location = "Отель"},
new Reservation {
ReservationId = 2, ClientName = "Вася", Location = "Библиотека"},
new Reservation {
ReservationId = 3, ClientName = "Игорь", Location = "Столовая"},
};
public IEnumerable<Reservation> GetAll()
{
return data;
}
public Reservation Get(int id)
{
return data.Where(r => r.ReservationId == id).FirstOrDefault();
}
public Reservation Add(Reservation item)
{
item.ReservationId = data.Count + 1;
data.Add(item);
return item;
}
public void Remove(int id)
{
Reservation item = Get(id);
if (item != null)
{
data.Remove(item);
}
}
public bool Update(Reservation item)
{
Reservation storedItem = Get(item.ReservationId);
if (storedItem != null)
{
storedItem.ClientName = item.ClientName;
storedItem.Location = item.Location;
return true;
}
else
{
return false;
}
}
}
}
В реальном проекте пришлось бы позаботиться о разрыве тесной связи между классами и ввести в приложение интерфейсы, а также обеспечить внедрение зависимостей. Однако в этой теме внимание уделяется только Web API и приложениям SPA, так что когда дело дойдет до стандартных приемов, будут предприняты некоторые упрощения.
Класс хранилища имеет начальный список из трех объектов Reservation и определяет методы, которые позволяют просматривать, добавлять, удалять и обновлять коллекцию. Поскольку постоянство в хранилище отсутствует, любые изменения, вносимые в хранилище, теряются в результате останова и перезапуска приложения, но этот пример целиком сосредоточен на способе, которым содержимое может быть доставлено, а не на том, каким образом оно хранится на сервере. Для обеспечения определенной доли постоянства между запросами создается экземпляр класса ReservationRepository, который доступен через статическое свойство Current.
Установка пакетов NuGet
В этой и последующих статьях будут применяться три пакета NuGet: jQuery, Bootstrap и Knockout. Библиотеки jQuery и Bootstrap уже были описаны и использовались ранее. Knockout - это библиотека, которую в Microsoft приспособили для одностраничных приложений. Она была создана Стивом Сандерсоном. Несмотря на то что Стив работает в Microsoft, пакет Knockout доступен в виде открытого кода на веб-сайте библиотеки Knockout и получил широкое распространение. Позже будет показано, как функционирует Knockout, а пока нужно установить упомянутые выше пакеты.
Выберите пункт меню Tools --> Library Package Manager --> Package Manager Console (Сервис --> Диспетчер библиотечных пакетов --> Консоль диспетчера пакетов), чтобы открыть окно командной строки NuGet, и введите следующие команды:
Install-Package jquery -version 1.10.2 -projectname WebServices
Install-Package bootstrap -version 3.0.0 -projectname WebServices
Install-Package knockoutjs -version 3.0.0 -projectname WebServices
Добавление контроллера
В пример проекта добавляется контроллер по имени Home, определение которого можно видеть в примере:
using WebServices.Models;
using System.Web.Mvc;
namespace WebServices.Controllers
{
public class HomeController : Controller
{
ReservationRepository repository = ReservationRepository.Current;
public ViewResult Index()
{
return View(repository.GetAll());
}
public ActionResult Add(Reservation item)
{
if (ModelState.IsValid)
{
repository.Add(item);
return RedirectToAction("Index");
}
else return View("Index");
}
public ActionResult Update(Reservation item)
{
if (ModelState.IsValid && repository.Update(item))
return RedirectToAction("Index");
else return View("Index");
}
}
}
Это совершенно типичный контроллер для такого простого приложения. Каждый метод действия напрямую соответствует одному из методов в хранилище. Единственная польза от контроллера связана с проведением проверки достоверности модели, выбором представлений и выполнением перенаправления. Конечно, в реальном проекте присутствовала бы дополнительная логика предметной области, но поскольку пример приложения настолько элементарен, контроллер оказывается не многим более чем простой оболочкой вокруг хранилища.
Добавление компоновки и представлений
Чтобы сгенерировать содержимое для приложения, создается папка Views/Shared, в которую добавляется файл представления по имени _Layout.cshtml с содержимым, показанным в примере ниже:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title</title>
<link href="~/Content/bootstrap.min.css" rel="stylesheet" type="text/css" />
@RenderSection("Scripts")
</head>
<body>
@RenderSection("Body")
</body>
</html>
В этой базовой компоновке предусмотрены элементы <link> для файлов CSS библиотеки Bootstrap. В компоновке определены два раздела, Scripts и Body, которые будут использоваться для вставки содержимого внутрь компоновки. Следующий шаг заключается в создании представления верхнего уровня для приложения. Хотя далее будет создаваться обычное приложение ASP.NET MVC Framework, известно, что в конечном итоге должно быть построено одностраничное приложение.
Трансформацию делать будет проще, если создать единственное представление, которое содержит всю HTML-разметку, требуемую для приложения, даже при условии, что результат первоначально выглядит несколько странно. В папку Views/Home добавляется файл представления по имени Index.cshtml, содержимое которого приведено в примере ниже:
@using WebServices.Models
@model IEnumerable<Reservation>
@{
ViewBag.Title = "Заявки на бронирование";
}
@section Scripts {
}
@section Body {
<div id="summary" class="section panel panel-primary">
@Html.Partial("Summary", Model)
</div>
<div id="editor" class="section panel panel-primary">
@Html.Partial("Editor", new Reservation())
</div>
}
Модель представления для этого представления является перечислением объектов Reservation, и для обеспечения строительных блоков функциональности, которую будет видеть пользователь, создаются два частичных представления. Файл с первым частичным представлением называется Summary.cshtml. Этот файл создан в папке Views/Home:
@model IEnumerable<WebServices.Models.Reservation>
<div class="panel-heading">Все заказы</div>
<div class="panel-body">
<table class="table table-striped table-condensed">
<thead>
<tr><th>ID</th><th>Имя</th><th>Помещение</th><th></th></tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>@item.ReservationId</td>
<td>@item.ClientName</td>
<td>@item.Location</td>
<td>
@Html.ActionLink("Удалить", "Remove",
new { id = item.ReservationId },
new { @class = "btn btn-xs btn-primary" })
</td>
</tr>
}
</tbody>
</table>
</div>
Модель представления для частичного представления - то же самое перечисление объектов Reservation, и оно используется для генерации стилизованной таблицы с помощью Bootstrap в виде элемента <table>, который отображает значения свойств этих объектов. Вспомогательный метод Html.ActionLink() применяется для генерации ссылки, которая будет вызывать действие Remove контроллера Home; ссылка стилизована в виде кнопки с использованием Bootstrap.
Еще одно частичное представление называется Editor.cshtml и также находится в папке Views/Home. Содержимое этого файла приведено в примере ниже. Частичное представление содержит форму, которая применяется для создания новых заявок на бронирование. Отправка формы приводит к вызову действия Add контроллера Home.
@model WebServices.Models.Reservation
<div class="panel-heading">
Создать заказ
</div>
<div class="panel-body">
@using (Html.BeginForm("Add", "Home"))
{
<div class="form-group">
<label>Имя клиента</label>
@Html.TextBoxFor(m => m.ClientName, new { @class = "form-control" })
</div>
<div class="form-group">
<label>Помещение</label>
@Html.TextBoxFor(m => m.Location, new { @class = "form-control" })
</div>
<button type="submit" class="btn btn-primary">Сохранить</button>
}
</div>
Установка стартового URL и тестирование приложения
Последний подготовительный шаг связан с установкой местоположения, куда Visual Studio будет переходить при запуске приложения. Выберите пункт WebServices Properties (Свойства WebServices) в меню Project (Проект) среды Visual Studio, в открывшемся диалоговом окне перейдите на вкладку Web и отметьте переключатель Specific Page (Определенная страница) в категории Start Action (Начальное действие). Вводить какое-либо значение не нужно - достаточно только выбора переключателя.
Чтобы протестировать приложение в его классической форме ASP.NET MVC Framework, выберите пункт Start Debugging (Запустить отладку) в меню Debug среды Visual Studio. Вы увидите (немного странную) компоновку в стиле "все в одном", которая предоставляет пользователю список текущих заявок на бронирование вместе с возможностью их создания и удаления:
В следующей статье мы добавим средства Web API для нашего приложения.