Одностраничные приложения

193

В этой и последующих статьях будет описано средство 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, как показано на рисунке ниже:

Создание нового проекта со ссылками на 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 для нашего приложения.

Пройди тесты
Лучший чат для C# программистов