ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p
.pdfЛистинг 19-5: Содержимое файла CustomerHelpers.cs
using System.Web.Mvc;
namespace HelperMethods.Infrastructure
{
public static class CustomHelpers
{
public static MvcHtmlString ListArrayItems(this HtmlHelper html, string[] list)
{
TagBuilder tag = new TagBuilder("ul"); foreach (string str in list)
{
TagBuilder itemTag = new TagBuilder("li"); itemTag.SetInnerText(str);
tag.InnerHtml += itemTag.ToString();
}
return new MvcHtmlString(tag.ToString());
}
}
}
Данный вспомогательный метод выполняет ту же функцию, что и встроенный вспомогательный метод в предыдущем примере - он принимает массив строк и генерирует HTML-элемент ul, содержащий элемент li для каждой строки в массиве.
Первый параметр этого вспомогательного метода – объект HtmlHelper с ключевым словом this, которое сообщает компилятору C#, что мы определяем метод расширения. Свойства HtmlHelper обеспечивают доступ к информации, которая может быть полезна при создании контента; они описаны в таблице 19-1.
Таблица 19-1: Свойства класса HtmlHelper
Свойство |
|
Описание |
RouteCollectionВозвращает набор маршрутов, определенных в приложении.
Возвращает данные объекта из ViewBag, который был передан методом действия в
ViewBag
представление, вызвавшее данный вспомогательный метод.
Возвращает объект ViewContext, который обеспечивает доступ к информации о
ViewContext
запросе и процессе его обработки (и который мы опишем далее в этой главе).
Свойство ViewContext наиболее полезно для создания контента, который адаптируется к текущему запросу. В таблице 19-2 описаны наиболее часто используемые свойства, определенные классом
ViewContext.
Таблица 19-2: Свойства класса ViewContext
Свойство |
|
Описание |
|
|
|
Controller |
|
Возвращает контроллер, обрабатывающий текущий запрос. |
|
|
|
HttpContext |
|
Возвращает объект HttpContext для текущего запроса. |
|
|
|
IsChildAction |
|
Возвращает true, если вызвавшее вспомогательный метод представление |
|
визуализируется дочерним действием (дочерние действия рассмотрены в главе 18). |
|
|
|
|
|
|
|
RouteData |
|
Возвращает данные маршрутизации для запроса. |
|
|
|
View |
|
Возвращает экземпляр реализации IView, которая вызвала вспомогательный метод. |
|
481 |
Листинг 19-6: Используем пользовательский внешний вспомогательный метод в файле
Index.cshtml
@model string
@using HelperMethods.Infrastructure
@{
Layout = null;
}
<!DOCTYPE html> <html>
<head>
<meta name="viewport" content="width=device-width" /> <title>Index</title>
</head>
<body>
<div>
Here are the fruits: @Html.ListArrayItems((string[])ViewBag.Fruits)
</div>
<div>
Here are the cities: @Html.ListArrayItems((string[])ViewBag.Cities)
</div>
<div>
Here is the message: <p>@Model</p>
</div>
</body>
</html>
Убедитесь, что в файле есть пространство имен, содержащее вспомогательный метод. Чтобы добавить его, мы использовали тег @using, но если вы разрабатываете много пользовательских вспомогательных методов, то можно добавить данное пространство имен в файл /Views/Web.config, чтобы они были доступны во всех представлениях.
Мы ссылаемся на вспомогательный метод с помощью @Html.<helper>, где <helper> - это имя метода расширения, в данном случае Html.ListArrayItems. Часть Html в этом выражении ссылается на свойство, которое определено в базовом классе представления и возвращает объект HtmlHelper, то есть тип, к которому мы применили метод расширения в листинге 19-5.
Мы передаем данные во внешний вспомогательный метод таким же образом, как и во внутренний или любой другой метод C#. Нужно только привести динамических свойства объекта ViewBag к типу, определенному этим внешним методом - в данном случае, к массиву строк. Пусть это и не такой красивый синтаксис, как во внутренних вспомогательных методах, но только так можно создать удобный многоразовый вспомогательный метод.
Когда использовать вспомогательные методы
Теперь, когда вы уже видели вспомогательные методы в действии, вы можете задать вопрос: когда лучше использовать их в предпочтение частичным представлениям или дочерним действиям, тем более, что все эти средства дублируют функциональность друг друга.
Мы используем вспомогательные методы только для того, чтобы уменьшить количество дублированного кода в представлениях, и только для самого простого кода, как в приведенном примере. Для более сложной разметки и кода мы используем частичные представления, а если необходимо выполнить какие-либо манипуляции с моделью данных - дочерние действия. Мы советуем вам придерживаться такой же схемы и использовать вспомогательные методы только для самых простых решений (в тех случаях, когда количество операторов C# будет бОльшим или даже превысит количество элементов HTML, мы будем переключаться на дочерние действия).
483
Управляем кодировкой строк в вспомогательных методах
В MVC Framework для защиты от вредоносного ввода применяется автоматическое кодирование, которое позволяет гарантировать безопасность добавления данных на страницу. Пример кодирования вы можете увидеть в листинге 19-7, где мы передаем потенциально небезопасную строку в представление в качестве модели объекта (в листинге повторяется код контроллера Home).
Листинг 19-7: Контроллер Home
using System.Web.Mvc;
namespace HelperMethods.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Fruits = new string[] {"Apple", "Orange", "Pear"}; ViewBag.Cities = new string[] {"New York", "London", "Paris"};
string message = "This is an HTML element: <input>";
return View((object) message);
}
}
}
Объект модели содержит допустимый элемент HTML, но после обработки Razor мы видим следующий код:
<div>
Here is the message:
<p>This is an HTML element: <input></p> </div>
Это основная мера безопасности, которая запрещает браузеру интерпретировать значения данных как действительную (допустимую) разметку, так как это является основой для распространенной формы атак, при которой злоумышленники пытаются изменить поведение приложения, пытаясь добавлять свой собственный код HTML или разметку JavaScript. Razor автоматически кодирует значения данных, когда они используются в представлении, но так как вспомогательные методы должны генерировать HTML, то они пользуются более высоким уровнем доверия со стороны движка представления - и в силу этого требуют более пристального внимания.
Описание проблемы
Чтобы дать вам представление о проблеме, связанной с кодировкой строк, мы создали новый вспомогательный метод в классе CustomerHelpers, как показано в листинге 19-8. Этот вспомогательный метод принимает в качестве параметра строку и генерирует такой же HTML, который мы включили в представление Index.
Листинг 19-8: Определяем новый вспомогательный метод
using System.Web.Mvc; using System;
namespace HelperMethods.Infrastructure
{
public static class CustomHelpers
{
484
public static MvcHtmlString ListArrayItems(this HtmlHelper html, string[] list)
{
TagBuilder tag = new TagBuilder("ul"); foreach (string str in list)
{
TagBuilder itemTag = new TagBuilder("li"); itemTag.SetInnerText(str);
tag.InnerHtml += itemTag.ToString();
}
return new MvcHtmlString(tag.ToString());
}
public static MvcHtmlString DisplayMessage(this HtmlHelper html, string msg)
{
string result = String.Format("This is the message: <p>{0}</p>", msg); return new MvcHtmlString(result);
}
}
}
Мы используем метод String.Format, чтобы создать разметку HTML и передать результат в качестве аргумента в конструктор MvcHtmlString. В листинге 19-9 показаны изменения в представлении /View/Home/Index.cshtml, с помощью которых мы будем использовать новый вспомогательный метод (а также выделим контент, который генерируется вспомогательным методом).
Листинг 19-9: Используем вспомогательный метод DisplayMessage в представлении Index
@model string
@using HelperMethods.Infrastructure
@{
Layout = null;
}
<!DOCTYPE html> <html>
<head>
<meta name="viewport" content="width=device-width" /> <title>Index</title>
</head>
<body>
<p>This is the content from the view:</p>
<div style="border: thin solid black; padding: 10px"> Here is the message:
<p>@Model</p>
</div>
<p>This is the content from the helper method:</p> <div style="border: thin solid black; padding: 10px">
@Html.DisplayMessage(Model)
</div>
</body>
</html>
Вы можете увидеть результат применения вспомогательного метода, запустив приложение, как показано на рисунке 19-3.
Рисунок 19-3: Сравниваем кодировку значений данных
485
Движок представлений считает безопасным контент, который генерируется вспомогательным методом, что приводит к нежелательным последствиям: браузер отображает элемент input, который можно использовать для взлома приложения.
Кодируем контент, генерируемый вспомогательными методами
Есть несколько способов решения этой проблемы, и выбор между ними зависит от характера контента, который генерируется вспомогательным методом.
Самое простое решение - изменить тип вывода вспомогательного метода на string, как показано в листинге 19-10. Это сообщает движку представления, что контент не является безопасным и должен быть закодирован, прежде чем он будет добавлен в представление.
Листинг 19-10: Razor кодирует контент, созданный вспомогательным методом
using System.Web.Mvc; using System;
namespace HelperMethods.Infrastructure
{
public static class CustomHelpers
{
public static MvcHtmlString ListArrayItems(this HtmlHelper html, string[] list)
{
486
TagBuilder tag = new TagBuilder("ul"); foreach (string str in list)
{
TagBuilder itemTag = new TagBuilder("li"); itemTag.SetInnerText(str);
tag.InnerHtml += itemTag.ToString();
}
return new MvcHtmlString(tag.ToString());
}
public static string DisplayMessage(this HtmlHelper html, string msg)
{
return String.Format("This is the message: <p>{0}</p>", msg);
}
}
}
Таким образом, Razor будет кодировать весь контент, который возвращается вспомогательным методом. В целом это очень удобно, за исключением тех случаев, когда необходимо создать элементы HTML, как в данном примере. Результат показан на рисунке 19-4.
Рисунок 19-4: Движок представлений кодирует ответ вспомогательного метода
Мы решили проблему с элементом input, но наши элементы p также были закодированы, и мы получили не совсем то, что хотели. В таких ситуациях нужно быть более избирательными и кодировать только значения данных. Это показано в листинге 19-11.
487
Листинг 19-11: Применяем выборочное кодирование данных во вспомогательном методе
public static MvcHtmlString DisplayMessage(this HtmlHelper html, string msg)
{
string encodedMessage = html.Encode(msg); string result = String.Format(
"This is the message: <p>{0}</p>", encodedMessage); return new MvcHtmlString(result);
}
Класс HtmlHelper создает экземпляр метода под названием Encode, который решает поставленную задачу и кодирует строковое значение, так что оно может быть безопасно использоваться в представлении. Однако, не забывайте его использовать - в качестве напоминания мы явно кодируем все значения данных в начале метода, предполагая, что вы будете следовать данному подходу.
Результат этого изменения показан на рисунке 19-5, на котором вы можете увидеть, что контент, генерируемый внешним вспомогательным методом, совпадает с контентом, который генерируется с помощью значения модели непосредственно в представлении.
Рисунок 19-5: Эффект выборочного кодирования контента во внешнем вспомогательном методе
488
Использование встроенных вспомогательных методов
MVC Framework включает в себя несколько встроенных вспомогательных методов, предназначенных для создания форм HTML. В следующих разделах мы рассмотрим эти вспомогательные методы в действии и научимся их использовать.
Создаем элементы form
Одним из наиболее распространенных способов взаимодействия с пользователем в веб-приложении являются HTML-формы, которые можно создавать с помощью целого ряда вспомогательных методов. Чтобы продемонстрировать методы, предназначенные для работы с формами, мы внесли некоторые дополнения в наш пример проекта. Для начала мы создали новый класс Person.cs в папке Models. Содержимое этого файла показано в листинге 19-12 – тип Person будет нашей моделью представления, с помощью которой мы продемонстрируем связанные с формами вспомогательные методы, а типы Address и Role помогут нам продемонстрировать некоторые более сложные функции.
Листинг 19-12: Модель Person
using System;
namespace HelperMethods.Models
{
public class Person
{
public int PersonId { 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
}
}
Мы также добавили новые методы действия в контроллер Home, в которых будут использоваться объекты модели и которые показаны в листинге 19-13.
489
Листинг 19-13: Добавляем методы действий в контроллер Home
using System.Web.Mvc; using HelperMethods.Models;
namespace HelperMethods.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Fruits = new string[] {"Apple", "Orange", "Pear"}; ViewBag.Cities = new string[] {"New York", "London", "Paris"}; string message = "This is an HTML element: <input>";
return View((object) message);
}
public ActionResult CreatePerson()
{
return View(new Person());
}
[HttpPost]
public ActionResult CreatePerson(Person person)
{
return View(person);
}
}
}
Это стандартный подход к работе с HTML-формами, который включает два метода. Здесь мы полагаемся на механизм связывания данных, предполагая, что MVC Framework создаст объект Person из данных формы и передаст его в метод действия с помощью атрибута HttpPost. (Атрибут HttpPost рассматривается в главе 16, а связывание данных - в главе 22).
Мы никак не обрабатываем данные формы, потому что хотим сосредоточиться на том, как генерировать элементы в представлении. Наш метод действия HttpPost просто вызывает метод View и передает в него объект Person, который он получил в качестве параметра; таким образом мы отображаем пользователю введенные им в форму данные.
Для начала мы создадим стандартную HTML-форму вручную, а затем покажем вам, как заменить различные ее части с помощью вспомогательных методов. Первоначальную версию формы вы можете увидеть в листинге 19-14; в нем показано содержимое представления CreatePerson.cshtml, которое мы создали в папке /Views/Home.
Листинг 19-14: Первоначальная версия формы HTML
@model HelperMethods.Models.Person
@{
ViewBag.Title = "CreatePerson";
}
<h2>CreatePerson</h2>
<form action="/Home/CreatePerson" method="post"> <div class="dataElem">
<label>PersonId</label>
<input name="personId" value="@Model.PersonId"/> </div>
<div class="dataElem">
490