Работа с Ajax в ASP.NET MVC

88

Ajax (или, если хотите, AJAX) - это сокращение для Asynchronous JavaScript and XML (Асинхронный JavaScript and XML). Часть, касающаяся XML, не настолько важна, как это было раньше, но вот асинхронность является именно той частью, которая делает технологию Ajax полезной. Это модель для запрашивания данных из сервера в фоновом режиме, не требующая перезагрузки веб-страницы. Инфраструктура ASP.NET MVC Framework содержит встроенную поддержку для ненавязчивого Ajax, которая означает что для определения средств Ajax необходимо использовать вспомогательные методы, а не добавлять блоки кода повсеместно в представлениях.

При создании и обработке запросов Ajax инфраструктура ASP.NET MVC Framework полагается на пакет Microsoft Unobtrusive Ajax (Ненавязчивый Ajax). Мы продолжим пользоваться проектом HelperMethods, который создали ранее, и добавим в него этот пакет. Для установки выберите пункт меню Tools --> Library Package Managers --> Package Manager Console (Сервис --> Диспетчер библиотечных пакетов --> Консоль диспетчера пакетов), чтобы открыть окно командной строки NuGet. Введите следующие команды:

Install-Package jQuery -projectname HelperMethods
Install-Package Microsoft.jQuery.Unobtrusive.Ajax -version 3.0.0.0 -projectname HelperMethods

Средство NuGet установит указанный пакет в проект вместе с библиотекой jQuery, от которой он зависит, и создаст папку Scripts, содержащую несколько файлов JavaScript. Средство ненавязчивого Ajax в ASP.NET MVC Framework основано на библиотеке jQuery. Если вы знакомы с тем, каким образом jQuery обрабатывает Ajax, то очень быстро поймете возможности Ajax в ASP.NET MVC Framework. Подробности использования jQuery и Ajax описаны в статьях jQuery и Ajax и Использование Ajax.

Создание представления с синхронной формой

Мы начнем этот раздел с создания представления для действия GetPeople из контроллера People в виде файла /Views/People/GetPeople.cshtml. Содержимое этого файла приведено в примере ниже:

@using HelperMethods.Models
@model IEnumerable<User>
@{
    ViewBag.Title = "Данные пользователей";
}

<h2>Данные пользователей</h2>
<table>
    <thead>
        <tr><th>Имя</th><th>Фамилия</th><th>Роль</th></tr>
    </thead>
    <tbody>
        @foreach (User user in Model)
        {
            <tr>
                <td>@user.FirstName</td>
                <td>@user.LastName</td>
                <td>@user.Role</td>
            </tr>
        }
    </tbody>
</table>

@using (Html.BeginForm())
{
    <div>
        @Html.DropDownList("selectedRole", new SelectList(
            new[] { "All" }.Concat(Enum.GetNames(typeof(Role)))))
        <button type="submit">Отобразить</button>
    </div>
}

Это строго типизированное представление с типом модели IEnumerable<User>. Мы выполняем перечисление объектов User в модели для создания строк HTML-таблицы и применяем вспомогательный метод Html.BeginForm() для создания простой формы, которая осуществляет обратную отправку сгенерировавшим ее действию и контроллеру.

Форма содержит вызов вспомогательного метода Html.DropDownList(), используемого для создания элемента <select>, в котором присутствуют элементы <option> для каждого значения, определенного в перечислении Role, а также для значения All. (Чтобы создать список значений для элементов <option>, с помощью LINQ выполняется конкатенация значений перечисления с массивом, содержащим единственную строку All.)

Форма содержит кнопку, которая отправляет форму. В результате эту форму можно применять для фильтрации объектов User, определенных в контроллере People, который мы создали в предыдущей статье. Для проверки запустите приложение и перейдите на URL вида /People/GetPeople:

Простая синхронная форма

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

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

Подготовка проекта к использованию ненавязчивого Ajax

Средство ненавязчивого Ajax настраивается в двух местах приложения. В файле Web.config (расположенном в корневой папке проекта) элемент configuration/appSettings содержит запись для свойства UnobtrusiveJavaScriptEnabled, которое должно быть установлено в true, как показано в примере ниже. (Это свойство устанавливается в true по умолчанию, когда Visual Studio создает проект.)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    ...
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>
  <system.web>
    ...
  </system.web>
</configuration>

В дополнение к проверке настроек в Web.config, понадобится добавить ссылки на JavaScript-библиотеки jQuery, которые реализуют функциональность ненавязчивого Ajax из пакета NuGet, установленного в начале этой статьи. Ссылаться на эти библиотеки можно из индивидуальных представлений, но более распространенный подход предусматривает добавление ссылок в файл компоновки, так что они будут влиять на все представления, использующие данную компоновку.

В примере ниже показано, как можно добавить ссылки на две JavaScript-библиотеки в файл /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>
    <style>
        ...
    </style>
    <script src="~/Scripts/jquery-2.1.1.js"></script>
    <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
</head>
<body>
    @RenderBody()
</body>
</html>

Файлы, на которые производится ссылка посредством элементов <script>, добавлены в папку Scripts проекта пакетом NuGet. Файл jquery-2.1.1.js содержит основную библиотеку jQuery, а файл jquery.unobtrusive-ajax.js - функциональность Ajax для приложений ASP.NET (которая опирается на главную библиотеку jQuery).

Создание Ajax-формы

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

Подготовка контроллера

Наша цель - добиться того, чтобы по щелчку пользователем на кнопке "Отправить" в примере приложения заменялись только данные в HTML-элементе <table>. Это значит, что первым делом понадобится провести рефакторинг методов действий в контроллере People, чтобы через метод действия можно было получать только необходимые данные. Изменения, внесенные в код контроллера People, показаны в примере ниже:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using HelperMethods.Models;

namespace HelperMethods.Controllers
{
    public class PeopleController : Controller
    {
        private List<User> UserData = new List<User> {
            new User {FirstName = "Иван", LastName = "Иванов", Role = Role.Admin},
            new User {FirstName = "Петр", LastName = "Петров", Role = Role.User},
            new User {FirstName = "Сидор", LastName = "Сидоров", Role = Role.User},
            new User {FirstName = "Вася", LastName = "Васильев", Role = Role.Guest}
        };

        public ActionResult Index()
        {
            return View();
        }

        public PartialViewResult GetPeopleData(string selectedRole = "All")
        {
            IEnumerable<User> users = UserData;
            if (selectedRole != "All")
            {
                Role selected = (Role)Enum.Parse(typeof(Role), selectedRole);
                users = UserData.Where(p => p.Role == selected);
            }
            return PartialView(users);
        }

        public ActionResult GetPeople(string selectedRole = "All")
        {
            return View((Object)selectedRole);
        }
    }
}

Мы добавили метод действия GetPeopleData(), который выбирает объекты User, предназначенные для отображения, и передает их методу PartialView() для генерации требуемых строк таблицы. Поскольку выбор данных обрабатывается в методе действия GetPeopleData(), появляется возможность радикально упростить метод действия GetPeople() и полностью удалить его версию HttpPost. Этот метод предназначен для передачи представлению выбранной роли в виде string.

Для нового метода действия GetPeopleData() создано новое частичное представление в файле /Views/People/GetPeopleData.cshtml. Содержимое этого файла приведено в примере ниже. Это представление отвечает за генерацию элементов <tr>, которые будут заполняться с применением перечисления объектов User, передаваемого из метода действия:

@using HelperMethods.Models
@model IEnumerable<User>

@foreach (User user in Model)
{
    <tr>
        <td>@user.FirstName</td>
        <td>@user.LastName</td>
        <td>@user.Role</td>
    </tr>
}

Мы также должны обновить представление /Views/People/GetPeople.cshtml, которое показано в примере ниже:

@using HelperMethods.Models
@model string
@{
    ViewBag.Title = "Данные пользователей";
}

<h2>Данные пользователей</h2>
<table>
    <thead>
        <tr><th>Имя</th><th>Фамилия</th><th>Роль</th></tr>
    </thead>
    <tbody>
        @Html.Action("GetPeopleData", new { selectedRole = Model })
    </tbody>
</table>

@using (Html.BeginForm())
{
    <div>
        @Html.DropDownList("selectedRole", new SelectList(
            new[] { "All" }.Concat(Enum.GetNames(typeof(Role)))))
        <button type="submit">Отобразить</button>
    </div>
}

Тип модели представления был изменен на string, и это значение передается вспомогательному методу Html.Action() для вызова дочернего действия GetPeopleData. В результате визуализируется частичное представление, а также генерируются строки таблицы.

Создание асинхронной формы

После внесенных изменений мы все еще имеем синхронную форму в приложении, однако уже разделили функциональность в контроллере, так что можно запрашивать только строки таблицы с помощью действия GetPeopleData. Этот новый метод действия будет нацелен на наш запрос Ajax, и следующий шаг заключается в обновлении представления GetPeople.cshtml так, чтобы отправка формы обрабатывалась посредством Ajax:

@using HelperMethods.Models
@model string
@{
    ViewBag.Title = "Данные пользователей";
    AjaxOptions ajaxOptions = new AjaxOptions
    {
        UpdateTargetId = "tableBody"
    };
}

<h2>Данные пользователей</h2>
<table>
    <thead>
        <tr><th>Имя</th><th>Фамилия</th><th>Роль</th></tr>
    </thead>
    <tbody id="tableBody">
        @Html.Action("GetPeopleData", new { selectedRole = Model })
    </tbody>
</table>

@using (Ajax.BeginForm("GetPeopleData", ajaxOptions))
{
    <div>
        @Html.DropDownList("selectedRole", new SelectList(
            new[] { "All" }.Concat(Enum.GetNames(typeof(Role)))))
        <button type="submit">Отобразить</button>
    </div>
}

В основе поддержки инфраструктурой ASP.NET MVC Framework форм Ajax лежит вспомогательный метод Ajax.BeginForm(), который принимает в качестве аргумента объект AjaxOptions. Я предпочитаю создавать объекты AjaxOptions в начале представления внутри блока кода Razor, но при желании их можно создать непосредственно во время вызова Ajax.BeginForm().

В классе AjaxOptions, находящемся в пространстве имен System.Web.Mvc.Ajax, определены свойства, которые позволяют конфигурировать то, как асинхронный запрос выполняется, и что происходит с данными, возвращаемыми обратно. Эти свойства описаны в таблице ниже:

Свойства класса AjaxOptions
Свойство Описание
Confirm

Устанавливает сообщение, которое выводится пользователю в окне подтверждения, прежде чем будет выполнен запрос Ajax

HttpMethod

Устанавливает HTTP-метод, с помощью которого будет выполнен запрос: Get или Post

InsertionMode

Указывает способ, которым содержимое, полученное с сервера, вставляется в HTML-разметку. Возможные варианты выражаются как значения из перечисления InsertionMode: InsertAfter, InsertBefore и Replace (стандартное значение)

LoadingElementId

Указывает идентификатор HTML-элемента, который отображается во время выполнения запроса Ajax

LoadingElementDuration

Задает количество миллисекунд, через которое постепенно появится элемент, указанный с помощью LoadingElementId

UpdateTargetId

Устанавливает идентификатор HTML-элемента, в который будет вставлено содержимое, полученное с сервера

Url

Устанавливает URL, который будет запрашиваться из сервера

В примере выше мы установили свойство UpdateTargetId в tableBody. Это идентификатор, который был назначен HTML-элементу tbody в представлении. Когда пользователь щелкает на кнопке "Отправить", производится асинхронный запрос к методу действия PeopleData(), а возвращаемый фрагмент разметки используется для замены существующих элементов в tbody.

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

Это все, что требовалось: мы заменили вызов Html.BeginForm() вызовом Ajax.BeginForm() и обеспечили нацеливание на новое содержимое. Все остальное происходит автоматически, и мы получаем асинхронную форму.

Выяснить, применяются ли запросы Ajax, может быть нелегко при проведении тестирования с участием браузера и сервера, которые функционируют на одной и той же машине, но определить, что браузер делает запросы Ajax для фрагментов HTML-разметки, можно с помощью инструментов <F12> браузера. Эти инструменты позволяют отслеживать сетевые запросы, которые выполняет браузер, и на рисунке ниже видно, что инструменты Google Chrome отражают вызов метода действия GetPeopleData():

Подтверждение выполнения запросов Ajax

Особенности работы ненавязчивого Ajax

При вызове вспомогательного метода Ajax.BeginForm() параметры, указываемые с помощью объекта AjaxOptions, трансформируются в атрибуты, применяемые к элементу <form>. Представление из примера выше генерирует следующий элемент <form>:

...
<form action="/People/GetPeopleData" id="form0" method="post"
    data-ajax="true" 
    data-ajax-mode="replace" 
    data-ajax-update="#tableBody" >
...

Когда HTML-страница, визуализированная из представления GetPeople.cshtml, загружается браузером, JavaScript-код в библиотеке jquery.unobtrusive-ajax.js сканирует HTML-элементы и идентифицирует форму Ajax, выполняя поиск элементов, которые имеют атрибут data-ajax со значением true.

Другие атрибуты, имена которых начинаются с data-ajax, содержат значения, заданные с использованием класса AjaxOptions. Эти параметры служат для конфигурирования библиотеки jQuery, которая имеет встроенную поддержку для управления запросами Ajax.

Вы не обязаны пользоваться поддержкой инфраструктуры ASP.NET MVC Framework для ненавязчивого Ajax. Доступно множество альтернатив, в том числе работа напрямую с jQuery. Тем не менее, выбрав какой-либо прием, придерживайтесь его - смешивать поддержку ASP.NET MVC Framework ненавязчивого Ajax с другими приемами и библиотеками в рамках одного представления не рекомендуется, поскольку могут возникать нежелательные взаимодействия, такие как дублированные или отброшенные запросы Ajax.

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