К книге
ASP.NET MVC FrameworkГЛАВА 5 Представление и интерфейс приложения. Генерация разметки представлением. Вспомогательные методы
49%
ГЛАВА 5 Представление и интерфейс приложения. Генерация разметки представлением. Вспомогательные методы
73

Вспомогательные методы класса 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="&lt;b>Teкст&lt;/b>"></div>

Пример использования Html.Encode():

<div><%= Html.Encode("<b>Текст</b>")</div>

Результирующая разметка:

<div>&lt;b&gt;TeKCT&lt;/b&gt;"</div>

Примечание

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

**********************************

Гиперссылки на действия контроллеров

Для создания гиперссылок на действия контроллеров используются два основных вспомогательных метода — Html.ActionLink() и Html.RouteLink().

Html.ActionLink()

Метод 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() используется для создания ссылок на основании определенных для приложения маршрутов, о которых подробнее можно узнать в следующей главе. Так, например, можно сослаться на определенный именованный маршрут, передав параметры в виде анонимного объекта.

Html.RouteLink("Ссылка", "MyRoute",

  new { action = "hello", id = 1 });

В результате будет создана следующая ссылка, при условии, что маршрут SomeRoute определен как mycontroller/{action}/sometext/{id}.

<a href="/mycontroller/hello/sometext/1">Сcылка</a>

Элементы управления HTML-страницы

Большая часть вспомогательных методов, приведенных в табл. 5.1, связаны с созданием HTML-разметки для элементов ввода данных 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&amp;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.

***************************

Предыдущая главаГлава 73 из 150Следующая глава