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

149

Инфраструктура ASP.NET MVC Framework включает набор встроенных вспомогательных методов, которые помогают управлять созданием HTML-элементов <form>. В этой статье будет показано, как применять такие вспомогательные методы. Мы продолжим рассматривать пример проекта HelperMethods, который создали в предыдущей статье.

Создание элементов формы

Одним из наиболее часто используемых видов взаимодействия в веб-приложении является HTML-форма, и для ее поддержки предназначено несколько разных вспомогательных методов. Для демонстрации работы вспомогательных методов, связанных с формами, в пример проекта внесен ряд дополнений. Первым делом в папке Models был создан новый файл класса по имени User.cs. Содержимое этого файла приведено в примере ниже:

using System;

namespace HelperMethods.Models
{
    public class User
    {
        public int UserId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
        public Address HomeAddress { get; set; }
        public bool IsApproved { get; set; }
        public Role Role { get; set; }
    }

    public class Address
    {
        public string Line1 { get; set; }
        public string Line2 { get; set; }
        public string City { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
    }

    public enum Role
    {
        Admin,
        User,
        Guest
    }
}

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

Кроме того, в контроллер Home были добавлены новые методы действий для работы с этими объектами моделей:

using System.Web.Mvc;
using HelperMethods.Models;

namespace HelperMethods.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            // ...
        }

        public ActionResult CreateUser()
        {
            return View(new User());
        }

        [HttpPost]
        public ActionResult CreateUser(User User)
        {
            return View(User);
        }
    }
}

Это стандартный подход с двумя методами к работе с HTML-формами, при котором мы полагаемся на привязку моделей ASP.NET MVC Framework в создании объекта User из данных формы и передаче его методу действия с атрибутом HttpPost.

Мы никак не обрабатываем данные из формы, поскольку хотим сосредоточиться на том, как генерировать элементы в представлении. Метод действия с атрибутом HttpPost просто вызывает метод View() и передает ему объект User, получаемый в качестве параметра, что в результате приводит к отображению данных формы пользователю.

Мы собираемся начать со стандартной построенной вручную HTML-формы и продемонстрировать замену различных ее частей с применением вспомогательных методов. Начальная версия формы показана в примере ниже; она находится в файле представления CreateUser.cshtml внутри папки /Views/Home:

@model HelperMethods.Models.User

@{
    ViewBag.Title = "CreateUser";
}

<h2>Создать пользователя</h2>
<form action="/Home/CreateUser" method="post">
    <div class="item">
        <label>UserId</label>
        <input name="userId" value="@Model.UserId" />
    </div>
    <div class="item">
        <label>Имя</label>
        <input name="FirstName" value="@Model.FirstName" />
    </div>
    <div class="item">
        <label>Фамилия</label>
        <input name="LastName" value="@Model.LastName" />
    </div>
    <input type="submit" value="Отправить" />
</form>

Это представление содержит стандартную вручную созданную форму, в которой значения атрибута value элементов <input> устанавливаются с использованием объекта модели.

Обратите внимание, что атрибуты name всех элементов <input> установлены в соответствие со свойствами модели, которые эти элементы отображают. Атрибут name применяется стандартным связывателем моделей ASP.NET MVC Framework для выяснения того, какие элементы <input> содержат значения для свойств типа модели, при обработке запроса POST. Если опустить атрибут name, форма не будет корректно функционировать.

Далее создается папка 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>
        label { display: inline-block; width: 100px; } 
        .item { margin: 5px; }
    </style>
</head>
<body>
    @RenderBody()
</body>
</html>

Это простая компоновка с несколькими стилями CSS, предназначенными для элементов <input> формы. Запустив приложение и перейдя на URL вида /Home/CreateUser, можно наблюдать базовую функциональность формы:

Применение простой HTML-формы в примере приложения

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

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CreateUser</title>
    <style>
        label { display: inline-block; width: 100px; } 
        .item { margin: 5px; }
    </style>
</head>
<body>

<h2>Создать пользователя</h2>
<form action="/Home/CreateUser" method="post">
    <div class="item">
        <label>UserId</label>
        <input name="userId" value="0" />
    </div>
    <div class="item">
        <label>Имя</label>
        <input name="FirstName" />
    </div>
    <div class="item">
        <label>Фамилия</label>
        <input name="LastName" />
    </div>
    <input type="submit" value="Отправить" />
</form>
</body>
</html>

Применение вспомогательных методов для генерации HTML-элементов вроде <form> и <input> является необязательным. При желании их можно закодировать с помощью статических HTML-дескрипторов и заполнить значения с использованием данных представления или объектов модели представления, как это делалось в настоящем разделе.

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

Создание элементов form

Двумя наиболее полезными (и часто используемыми) вспомогательными методами являются BeginForm() и EndForm(). Они создают HTML-дескрипторы <form> и генерируют для формы допустимый атрибут action, который основан на механизме маршрутизации в приложении.

Существуют 13 разных версий метода BeginForm(), которые позволяют последовательно указывать характеристики результирующего элемента <form>. В рассматриваемом примере приложения нам нужна только самая базовая версия, не принимающая аргументов и создающая элемент <form>, атрибут action которого обеспечивает отправку формы тому же самому методу действия, что привел к генерации текущего представления.

В примере ниже показано, как применяется эта перегруженная версия BeginForm() и вспомогательный метод EndForm(). Вспомогательный метод EndForm() имеет только одно определение и просто закрывает элемент <form>, добавляя в представление дескриптор </form>:

@model HelperMethods.Models.User

@{
    ViewBag.Title = "CreateUser";
}

<h2>Создать пользователя</h2>
@{Html.BeginForm();}
    <div class="item">
        <label>UserId</label>
        <input name="userId" value="@Model.UserId" />
    </div>
    <div class="item">
        <label>Имя</label>
        <input name="FirstName" value="@Model.FirstName" />
    </div>
    <div class="item">
        <label>Фамилия</label>
        <input name="LastName" value="@Model.LastName" />
    </div>
    <input type="submit" value="Отправить" />
@{Html.EndForm();}

Обратите внимание, что вызовы этих вспомогательных методов должны трактоваться как операторы C#. Причина в том, что указанные вспомогательные методы записывают свои дескрипторы напрямую в вывод. Хотя проектное решение выглядит довольно неуклюжим, это не имеет особого значения, поскольку вспомогательные методы BeginForm() и EndForm() редко применяются подобным образом. Намного более распространенный подход, продемонстрированный в примере ниже, предусматривает помещение вызова вспомогательного метода BeginForm() внутрь выражения using. В конце блока using исполняющая среда .NET вызовет метод Dispose() объекта, возвращенного методом BeginForm(), который, в свою очередь, вызовет метод EndForm():

@model HelperMethods.Models.User

@{
    ViewBag.Title = "CreateUser";
}

<h2>Создать пользователя</h2>
@using (Html.BeginForm()) { 
    ...
}

Такой подход называется самозакрывающейся формой и ему следует отдавать предпочтение. Удобно, когда блок кода содержит форму и дает возможность легко понять, какие элементы будут находиться между открывающим и закрывающим дескрипторами <form>.

Остальные 12 вариаций метода BeginForm() позволяют изменять различные аспекты создаваемого элемента <form>. Среди этих перегруженных версий встречается много повторений, т.к. они позволяют последовательно указывать детали формы. В таблице ниже перечислены наиболее важные перегруженные версии, которые регулярно используются в приложениях MVC. Остальные не показанные здесь перегруженные версии метода BeginForm() предназначены для совместимости с версиями инфраструктуры ASP.NET MVC Framework, которые выходили до появления в языке C# поддержки создания динамических объектов.

Часто применяемые перегруженные версии вспомогательного метода HtmlHelper.BeginForm()
Перегруженная версия Описание
BeginForm()

Создает форму, осуществляющую обратную отправку методу действия, который привел к визуализации представления

BeginForm(action, controller)

Создает форму, которая осуществляет обратную отправку методу действия и контроллеру, указанным в виде строк

BeginForm(action, controller, method)

Подобна предыдущей перегруженной версии, но позволяет указывать значение для атрибута method, используя перечисление System.Web.Mvc.FormMethod (POST, GET и т.п.)

BeginForm(action, controller, method, attributes)

Подобна предыдущей перегруженной версии, но позволяет указывать в качестве атрибутов для элемента <form> объект, свойства которого используются в качестве имен атрибутов

BeginForm(action, controller, routeValues, method, attributes)

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

Ранее демонстрировалась простейшая версия метода BeginForm(), которой было вполне достаточно для примера приложения, а в примере ниже можно видеть, как используется наиболее сложная версия, позволяющая указывать дополнительную информацию относительно конструирования элемента <form>:

...
@using (Html.BeginForm("CreateUser", "Home",
    new { id = "MyIdValue" },
    FormMethod.Post,
    new { @class = "userCssClass", data_formType="user" }))
{ 
    ...
}

В этом примере явно указывается набор деталей, которые иначе были бы выведены инфраструктурой ASP.NET MVC Framework автоматически, такие как имя действия и контроллер. Мы также указали, что форма должна быть отправлена с применением HTTP-метода POST, который использовался бы в любом случае.

Более интересными аргументами являются те, которые устанавливают значения для переменной маршрута и атрибуты для элемента <form>. Аргументы значений маршрутов применяются для указания значения переменной сегмента id в стандартном маршруте, добавленном Visual Studio в файл /App_Start/RouteConfig.cs при создании проекта. Кроме того, определяются атрибут class и атрибут данных data_formType. (Атрибуты данных - это специальные атрибуты, которые можно добавлять к элементам для выполнения обработки HTML-содержимого.)

Ниже показан HTML-дескриптор <form>, который порождается этим вызовом BeginForm():

...
<form action="/Home/CreateUser/MyIdValue" class="userCssClass" 
    data-formType="user" method="post"> 
...

Как видите, значение для атрибута id было добавлено к целевому URL, а к элементу были применены атрибут class и атрибут данных. Обратите внимание, что в вызове метода BeginForm() указывался атрибут по имени data_formType, но в выводе он превратился в data-formType. Дело в том, что указывать имена свойств динамического объекта C#, содержащие символы дефиса, не допускается, поэтому использовался символ подчеркивания, который в выводе был автоматически заменен символом дефиса, аккуратно устраняя несоответствие между синтаксисами C# и HTML. (И, разумеется, имя свойства class было снабжено префиксом @, что позволило применить зарезервированное ключевое слово C# в качестве имени свойства для атрибута class.)

Указание маршрута, используемого формой

Когда используется метод BeginForm(), инфраструктура ASP.NET MVC Framework ищет в конфигурации маршрутизации первый маршрут, подходящий для применения при генерации URL, который будет нацелен на требуемое действие и контроллер. В сущности, выбор маршрута оставлен за вами. Если вы хотите обеспечить использование конкретного маршрута, можете применять вместо BeginForm() вспомогательный метод BeginRouteForm(). Чтобы продемонстрировать эту возможность, в файл /App_Start/RouteConfig.cs добавлен новый маршрут, как показано в примере ниже:

using System.Web.Mvc;
using System.Web.Routing;

namespace HelperMethods
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

            routes.MapRoute(
                name: "FormRoute",
                url: "app/forms/{controller}/{action}"
            );
        }
    }
}

Если вызвать метод BeginForm() с такой конфигурацией маршрутизации, получится элемент <form> с атрибутом action, содержащим URL, который создан из стандартного маршрута. В примере ниже видно, как с помощью метода BeginRouteForm() указать на необходимость использования этого нового маршрута:

...
@using (Html.BeginRouteForm("FormRoute", null, FormMethod.Post,
    new { @class = "userCssClass", data_formType="user" }))
{ 
    ...
}

В результате генерируется следующий дескриптор <form>, атрибут action которого соответствует структуре нового маршрута:

...
<form action="/app/forms/Home/CreateUser" class="userCssClass" 
    data-formType="user" method="post">
...

Подобно BeginForm(), метод BeginRouteForm() имеет множество перегруженных версий, которые позволяют указывать разнообразные детали для элемента <form>. Все они следуют той же самой структуре, как их аналоги BeginForm().

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