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


Большая часть вспомогательных методов реализованы как методы-расширения C# 3.0 для класса HtmlHelper (статические методы, определенные вне класса HtmlHelper). Такой подход был избран для того, чтобы разработчики легко могли расширять набор вспомогательных методов в собственных классах и не перегружать сам класс HtmlHelper.
Далее рассмотрено применение каждого из методов, описанных в табл. 5.1. Методы сгруппированы по сходству решаемых задач.
При выводе любого текста на страницу, либо в качестве значений атрибутов тегов необходимо обеспечить соответствие этого текста HTML-формату, заменив HTML-символы на их коды, чтобы браузер не интерпретировал выводимый текст как инструкции разметки страницы.
Кодирование текстов, полученных из неблагонадежных источников (добавленных пользователями, полученных от удаленных веб-служб и т. п.), необходимо для обеспечения безопасности пользователя, работающего со страницей, на которую выводятся эти тексты.
Для кодирования предназначены два вспомогательных метода — Html.AttributeEncode() и Html.Encode().
Пример использования Html.AttributeEncode():
<div someattr="<%= Html.AttributeEncode("<b>Teкст</b>")"></div>
Результирующая разметка:
<div someattr="<b>Teкст</b>"></div>
Пример использования Html.Encode():
<div><%= Html.Encode("<b>Текст</b>")</div>
Результирующая разметка:
<div><b>TeKCT</b>"</div>
Важно отметить, что методы Html.AttributeEncode() и Html.Encode() не заменяют символ апострофа (') на соответствующий HTML-код, поскольку не рекомендуется использовать апострофы для атрибутов тегов на HTML-страницах, хотя это и допустимо с точки зрения HTML-стандарта.
Для создания гиперссылок на действия контроллеров используются два основных вспомогательных метода — Html.ActionLink() и Html.RouteLink().
Метод Html.ActionLink() применяется для ссылок с использованием строковых значений. Например, для ссылки на действие Index, контроллера Home, именуемой "Главная страница", метод используется следующим образом:
Hnml.ActionLink("Главная страница", "Index", "Home")
Результирующая разметка:
<а href="/Home/Index">Главная страница</а>
Для того чтобы передать параметры в строке запроса, методу ActionLink() необходимо передать анонимный объект, содержащий значения параметров в свойствах объекта. При генерации гиперссылки будут учтены параметры маршрутов, зарегистрированных для приложения (подробная информация о маршрутизации приведена в главе 6).
Так, например, если для приложения определен только маршрут
{controller}/{action}/{id}, то следующий вызов метода ActionLink()
Html.ActionLink("Ссылка", "Data", "Home", new { id = 1, ord = 2 })
приведет к генерации такой ссылки:
<a href="/Home/Data/1?ord=2">Ссылка</a>
Если же среди маршрутов определен, например, и такой {controller}/{action}/{id}/{ord}, то будет сгенерирована следующая ссылка:
<a href="/Home/Data/1/2">Сcылка</a>
Для создания абсолютной ссылки, либо ссылки с дополнительными параметрами, такими как протокол, якорь, также можно воспользоваться методом ActionLink().
Html.ActionLink("Сайт microsoft.com", "Express", "VStudio",
"http", "microsoft.com", "download", new {}, null);
В результате будет создана следующая ссылка:
<a href="http://microsoft.com/VStudio/Express#download">
Сайт microsoft.com</a>
Метод Html.RouteLink() используется для создания ссылок на основании определенных для приложения маршрутов, о которых подробнее можно узнать в следующей главе. Так, например, можно сослаться на определенный именованный маршрут, передав параметры в виде анонимного объекта.
Html.RouteLink("Ссылка", "MyRoute",
new { action = "hello", id = 1 });
В результате будет создана следующая ссылка, при условии, что маршрут SomeRoute определен как mycontroller/{action}/sometext/{id}.
<a href="/mycontroller/hello/sometext/1">Сcылка</a>
Большая часть вспомогательных методов, приведенных в табл. 5.1, связаны с созданием HTML-разметки для элементов ввода данных HTML-форм. В этой части главы мы рассмотрим их применение.
Для того чтобы данные, введенные пользователем в элементы управления, были корректно отправлены на сервер и обработаны ожидающим этих данных действием контроллера, необходимо создать тег <form>, для этого существует вспомогательный метод Html.BeginForm(действие контроллера, имя контроллера). Если при вызове метода не указаны параметры, атрибут action тега <form> будет заполнен текущим URL.
<% using(Html.BeginForm("About", "Home")){ %>
<% } %>
В примере используется синтаксис using(){}. Дело в том, что метод Html.BeginForm() возвращает объект, реализующий интерфейс IDisposable. Метод Dispose() этого объекта определен таким образом, чтобы выводить закрывающий тег формы </form>. Вместо синтаксиса using(){} можно воспользоваться вспомогательным методом Html. EndForm().
<% Html.BeginForm("About", "Home"); %>
<% Html.EndForm (); %>
Синтаксис using(){} удобнее при наличии нескольких независимых форм на странице — в этом случае формы будут выглядеть визуально обособленно.
При отображении представления будет создана соответствующая разметка для тега <form>.
<form action="/Home/About" method="post">
</form>
В случае необходимости задать дополнительные параметры URL, по которому будут отправлены данные формы, необходимо передать анонимный объект, свойства которого будут преобразованы в пары ключ-значение. Для определения метода отправки формы, в качестве параметра нужно передать значение перечислимого FormMethod.
<% using (Html.BeginForm("About", "Home",
new { hello = "world", answer = 42 }, FormMethod.Get )) { %>
<% } %>
Эти параметры будут использованы при генерации тега <form>.
<form action="/Home/About?hello=world&answer=42" method="get">
</form>
Вспомогательные методы для генерации текстовых полей, скрытых полей и кнопок работают по общему принципу — первым параметром передается идентификатор, который будет использован для создаваемого HTML-элемента и на основании которого сопоставляется значение элемента управления и создается пара ключ-значение при отправке данных формы на сервер.
<%= Html.TextArea("myText", "Hello world!") %>
В отличие от метода Html.BeginForm, выводящего строковые значения непосредственно в выходной буфер ответа пользователю, вспомогательные методы элементов управления возвращают строку, поэтому для них нужно использовать синтаксис <%= %>. Тогда в разметку страницы будет выведена соответствующая строка.
<textarea cols="20" id="myText" name="myText" rows="2">
la-la-la-la-la-la-la</textarea>
Для создания дополнительных атрибутов вспомогательным методам необходимо передать анонимный объект.
<%= Html.TextBox("myTextBox", "", new { style="color: yellow;",
@class="helloWorld" })%>
Поскольку в качестве имени свойства анонимного объекта нельзя использовать зарезервированные ключевые слова языка C#, то перед такими именами свойств нужно добавить символ @. В результирующий вывод свойство @class войдет как class, без символа @.
<input class="helloWorld" id="myTextBox"
name="myTextBox" style="color: yellow;" type="text" value="" />
Особым случаем является вспомогательный метод Html.CheckBox, поскольку он создает два HTML-элемента управления. Непосредственно элемент "флажок" и "скрытое поле".
<%= Html.CheckBox("myCheckBox", true) %>
Поскольку в случае если элемент управления не будет отмечен пользователем, то его значение на сервер не передается, поэтому необходимо использование скрытого поля.
<input checked="checked" id="myCheckBox" name="myCheckBox"
type="checkbox" value="true" />
<input name="myCheckBox" type="hidden" value="false" />
Стоит отметить, каким образом присваиваются значения элементам управления. При генерации разметки для элемента управления myTextBox механизм представления сначала проверит наличие значения в коллекции ViewData.ModelState для элемента с именем myTextBox: ViewData.ModelState["myTextBox"].Value.RawValue, затем будет использовано значение, переданное в качестве параметра вспомогательному методу. В случае если вспомогательному методу не было передано параметров, то будет осуществлена попытка найти значение в коллекции ViewData: ViewData.Eval("myTextBox"). Подробнее о коллекции ModelState рассказано в разделе, посвященном проверке значений элементов управления.
Для генерации списков используются два метода Html.DropDownList() и Html.ListBox(), генерирующие разметку для элементов: управления "выпадающий список" и "список" соответственно. Оба этих вспомогательных метода принимают в качестве параметра коллекцию SelectList.
<%= Html.DropDownList("lstName",
new SelectList( new[] {"John", "Paul", "George", "Rringo"} )) %>
В результате создается разметка, содержащая теги <option>.
<select id="lstName" name="lstName">
<option>John</option>
<option>Paul</option>
<option>George</option>
<option>Rringo</option>
</select>
Для обеспечения возможности выбора нескольких элементов в списках, генерируемых методом ListBox, в качестве параметра следует передать коллекцию MultiSelectList.
<%= Html.ListBox("lstName",
new MultiSelectList( new[] {"John", "Paul", "George", "Rringo"} )) %>
Тогда будет определено свойство multiple тега <select>.
<select id="lstName" multiple="multiple" name="lstName">
<option>John</option>
<option>Paul</option>
<option>George</option>
<option>Rringo</option>
</select>
Разумеется, в реальных приложениях не всегда удобно создавать отдельно список для элементов форм и хотелось бы использовать коллекцию бизнесобъектов, применяемых в логике приложения. Для этого может быть использован конструктор коллекции SelectList.
SelectList(коллекция объектов,
название поля объекта, содержащее значение,
название поля объекта, содержащее текст,
выбранное значение
)
Например, в нашем приложении используются объекты типа Person, определение которых представлено далее.
public class Person {
public int Id { get; set; }
public string Name { get; set; }
}
Тогда в методе контроллера для сохранения в коллекцию ViewData коллекцию элементов списка можно передать, используя конструктор SelectList так, как показано далее.
public ActionResult About()
{
List<Person> lst = new List<Person>
{
new Person { Id = 1, Name = "John"},
new Person { Id = 2, Name = "Paul"},
new Person { Id = 3, Name = "George"},
new Person { Id = 4, Name = "Ringo"}
};
ViewData["lstName"] = new SelectList(lst, "Id", "Name", 2);
return View () ;
}
В результате будет создана HTML-разметка, приведенная далее.
<select id="lstName" multiple="multiple" name="lstName">
<option value="1">John</option>
<option selected="selected" value="2">Paul</option>
<option value="3">George</option>
<option value="4">Ringo</option>
</select>
Таким образом, коллекция бизнес-объектов может быть использована для заполнения списков необходимыми данными.
Пользователям свойственно ошибаться при вводе данных, и приложению необходимо уведомлять пользователя о допущенных ошибках и конкретных полях формы, которые заполнены некорректно. Для этого существуют два вспомогательных метода: Html.validationMessage(), который выводит сообщение, относящееся к определенному полю на форме, и Html.validationSummary (), который выводит общую информацию по ошибкам, допущенным при заполнении формы.
Работа этих вспомогательных методов основана на коллекции ModelState, которая упоминалась ранее. В этой коллекции на этапе проверки корректности данных сохраняется информация об ошибках, связанных с конкретными полями формы. Пример действия контроллера, выполняющего проверку корректности введенных данных (валидацию), приведен в листинге 5.7.
Листинг 5.7. Пример валидации данных
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Product obj, int id)
{
if (obj.UnitsOnOrder < 0)
ModelState.AddModelError("UnitsOnOrder",
"Количество заказанных единиц товара не может
быть отрицательным.");
if (obj.UnitsInStock < 0)
ModelState.AddModelError("UnitsInStock",
"Количество единиц товара на складе должно быть не
отрицательным.");
if (obj.UnitPrice <= 0)
ModelState.AddModelError("UnitPrice",
"Цена должна быть больше нуля.");
if (!ModelState.IsValid)
{
// есть ошибки, еще раз
// показать форму редактирования
return View(obj);
}
else
{
// ошибок нет, сохранить
db.SaveProduct(obj);
return RedirectToAction("Index");
}
}
Процесс валидации прост — выполняется проверка условий и в случае наличия ошибок в коллекцию Modelstate добавляется информация в виде пары "идентификатор элемента — описание допущенной ошибки". Если в коллекцию Modelstate добавлена хотя бы одна такая пара, то значение свойства Modelstate.isValid будет установлено в false. В случае если ошибки допущены, то необходимо снова отобразить то же представление, которое использовалось для радактирования данных, и передать ему те данные, которые были введены пользователем на предыдущем шаге. Пример такого представления, работающего с кодом, описанным в листинге 5.7, показан в листинге 5.8, там же приведено строго типизированное представление, в качестве модели использующее класс Product.
Листинг 5.8. Представление Edit.aspx
<%@ Page Title="" Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<MvcViewsDemo.Models.Product>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent"
runat="server">
Edit
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Редактирование сведений о товаре
</h2>
<%= Html.ValidationSummary("npи редактировании сведений
о товаре произошли ошибки.") %>
<% using (Html.BeginForm())
{%>
<fieldset>
<legend>Редактирование сведений о товаре</legend>
<p>
<label for="ProductID">
Код продукта:
</1аЬе1>
<%= Model.ProductID.ToString() %>
<%= Html.Hidden("ProductId", Model.ProductID)%>
</p>
<p>
<label for="ProductName">
Название:
</label>
<%= Html.TextBox("ProductName", Model.ProductName) %>
<%= Html.ValidationMessage("ProductName", "*") %>
</p>
<p>
<label for="UnitPrice">
Цена:
</label>
<%= Html.TextBox("UnitPrice",
String.Format("{0:F}", Model.UnitPrice)) %>
<%= Html.ValidationMessage("UnitPrice", "*") %>
</p>
<p>
<label for="UnitsInStock">
На складе:
</label>
<%= Html.TextBox("UnitsInStock", Model.UnitsInStock) %>
<%= Html.ValidationMessage("UnitsInStock", "*") %>
</p>
<p>
<label for="UnitsOnOrder">
Заказано:
</label>
<%= Html.TextBox("UnitsOnOrder", Model.UnitsOnOrder) %>
<%= Html.ValidationMessage("UnitsOnOrder", "*") %>
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%=Html.ActionLink("K списку товаров", "Index") %>
</div>
</asp:Content>
В листинге 5.8 методы Html.ValidationMessage() вызываются co вторым строковым параметром, указывающим сообщение, которое должно быть отображено пользователю в случае наличия ошибки в коллекции ModelState. В результате форма, заполненная с ошибками, будет выглядеть так, как показано на рис. 5.2.
Для того чтобы сообщение об ошибке было выведено непосредственно в месте вызова метода Html.ValidationMessage(), метод нужно вызывать без указания второго параметра Html.ValidationMessage("UnitPrice"). Результат приведен на рис. 5.3.
Стоит отметить, что если в коде представления не используется строготипизированная привязка к свойствам модели, то привязка к данным осуществляется автоматически, и в этом случае при возникновении ошибок нет необходимости передавать объект модели представлению через метод View(), как это было сделано в листинге 5.7. То есть фрагмент кода из листинга 5.7 может быть написан так, как указано далее. Листинг 5.9 демонстрирует код представления, не использующего привязку к свойствам объекта модели Model.
if (IModelState.IsValid)
{
// есть ошибки, еще раз
// показать форму редактирования
return View();
}


Листинг 5.9. Представление Edit.aspx без привязки к свойствам объекта Model
<%@ Page Title="" Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<MvcViewsDemo.Models.Product>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Edit
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Редактирование сведений о товаре
</h2>
<%= Html.ValidationSummary("при редактировании сведений
о товаре произошли ошибки.") %>
<% using (Html.BeginForm())
{%>
<fieldset>
<legend>Редактирование сведений о товаре</legend>
<p>
<label for="ProductID">
Код продукта:
</label>
<%= ViewData.Eval("ProductID") %>
<%= Html.Hidden("ProductID")%>
</p>
<p>
<label for="ProductName">
Название:
</label>
<%= Html.TextBox("ProductName") %>
<%= Html.ValidationMessage("ProductName") %>
</p>
<p>
<label for="UnitPrice">
Цена:
</label>
<%= Html.TextBox("UnitPrice") %>
<%= Html.ValidationMessage("UnitPrice") %>
</p>
<p>
<label for="UnitsInStock">
На складе:
</label>
<%= Html.TextBox("UnitsInStock") %>
<%= Html.ValidationMessage("UnitsInStock") %>
</p>
<p>
<label for="UnitsOnOrder">
Заказано:
</label>
<%= Html.TextBox("UnitsOnOrder") %>
<%= Html.ValidationMessage("UnitsOnOrder") %>
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%=Html.ActionLink("K списку товаров", "Index") %>
</div>
</asp:Content>
Важно обратить внимание, что при использовании кода, аналогичного приведенному в листинге 5.9, поиск значений элементов формы будет осуществляться через ViewData.Eval(), и значение ViewData["SomeProperty"] имеет больший приоритет, чем ViewData.Model.SomeProperty. Поэтому, во избежание трудноуловимых ошибок, при создании кода контроллера стоит с особенной тщательностью относиться к тому, как передаются данные — через свойства объекта-модели или через коллекцию ViewData.