ASP.NET MVC Framework

Магдануров Гайдар

Юнев Владимир

ГЛАВА 5

Представление и интерфейс приложения

 

 

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

 

Стандартный механизм представлений на базе WebForms

 

Создатели MVC Framework пошли по пути максимального использования существующей инфраструктуры ASP.NET. За счет этого разработчики, знакомые с WebForms, в ASP.NET могут использовать известные концепции пользовательских элементов управления и мастерских страниц для формирования шаблонов оформления приложения. В то же время подход к созданию страниц существенно изменился, о чем подробно было рассказано в главах 1 и 2 этой книги.

До того как рассматривать непосредственно создание представлений, рассмотрим необходимые компоненты инфраструктуры, обеспечивающие работу представлений — использование code-behind-файлов, мастерские страницы, механизм передачи данных между контроллером и представлением через коллекцию viewData и строгая типизация представлений.

 

Code-behind-файлы

Модель использования code-behind-файлов в WebForms является основой разделения логики представления, выполненной в файле разметки ASPX, и бизнес-логики самой страницы, выполненной в файле исходного кода.

Напомним, что непосредственно в файле разметки страницы указана ссылка на code-behind-файл страницы и класс, определенный в code-behind-файле, которому наследует страница:

%@Page Language="C#" Inherits="MyApp.Pg" CodeBehind="Pg.aspx.cs"%

Класс, определенный в code-behind-файле, служит "прослойкой" между страницей и классом System.Web.UI.Page, базовым для всех страниц WebForms, и отвечает за обработку событий жизненного цикла страницы. В случае, если дополнительная обработка событий не является необходимой, файл code-behind может быть смело удален, а страница может непосредственно наследовать классу System.Web.UI.Page:

<% @Page Language="C#" Inherits="System.Web.UI.Page" %>

В MVC Framework не используется жизненный цикл страниц ASP.NET, поэтому для представлений применяется аналогичный подход с исключением code-behind-файла, и страницы наследуют непосредственно классу System.Web.Mvc.ViewPage или обобщающему классу System.Web.Mvc.ViewPage, используемому для строгой типизации представлений, описанному далее.

Файлы code-behind могут быть необходимы для представлений, использующих серверные элементы управления, требующих инициализации в различные моменты жизненного цикла страницы. Однако в подавляющем большинстве случаев такой дизайн приложений приводит к сложностям в поддержке проекта, не говоря уже о грубом нарушении подхода MVC.

 

Мастерские страницы и элементы управления

Из WebForms в механизм представлений в MVC пришел и подход к повторному использованию общей разметки страниц — мастерские страницы (master pages), файлы которых имеют расширение master. Мастерские страницы представляют собой инструмент для определения общего шаблона набора страниц, определяя общую разметку для набора контентных страниц и расположение блоков, содержащихся в контентных страницах.

Принцип построения мастерской страницы следующий:

1. Задается общий дизайн страницы и блоки, общие для набора страниц.

2. В разметке мастерской страницы определяются блоки, которые будут заполнены содержимым контентных страниц с помощью разметки:

Пример мастерской страницы приведен в листинге 5.1.

3. При создании разметки контентных страниц используются блоки:

где ContentPlaceHolderID соответствует тому фрагменту мастерской страницы, который помечен элементом ContentPlaceHolder с соответствующим свойством id. Пример контентной страницы приведен в листинге 5.2.

Листинг 5.1. Пример мастерской страницы

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

          " http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd ">

  </p> <p class="paragraph">     <asp:ContentPlaceHolder ID="TitleContent" runat="server" /> </p> <p class="paragraph">  

 

 

   

     

       

   

 

Листинг 5.2. Пример контентной страницы

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"

           Inherits="System.Web.Mvc.ViewPage" %>

   о проекте

 

    О проекте

 

 

    Тут что-то написано.

 

Мастерские страницы могут быть вложенными, т. е. в свою очередь использовать мастерские страницы. Это может быть полезно, когда существует общая планировка всех страниц на сайте (родительская мастерская страница) и несколько вариантов планировки дизайна вложенных разделов (дочерние мастерские страницы).

На контентной странице могут быть заданы несколько блоков Content, соответствующих блокам ContentPlaceHolder, определенным на мастерской странице.

Еще одним механизмом повторного использования разметки являются пользовательские элементы управления или, в терминах MVC, частичные представления. Частичные представления — это фрагменты разметки, вынесенные в отдельный файл с расширением ascx, которые могут быть использованы на страницах представлений через вызов метода RenderPartial() класса HtmlHelper. О классе Html и его методах подробнее рассказано далее в этой главе. Ранее в листинге 5.1 приведен пример отображения частичного представления LogOnUserControl.ascx (<% Html.RenderPartial("LogCnUserControl"); %>).

Частичное представление содержит ту же самую разметку и строится по тем же принципам, что и представление. Пример разметки упомянутого ранее частичного представления LogOnUserControl.ascx показан в листинге 5.3. Разметка, приведенная в листинге 5.3, подробно рассматривается далее.

 Листинг 5.3. Разметка частичного представления LogOnUserControl.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<%

  if (Request.IsAuthenticated) {

%>

  Добро пожаловать <%= Html.Encode(Page.User.Identity.Name)

%>!

    [ <%= Html.ActionLink("Выход", "LogOff", "Account") %> ]

<%

  }

  else {

%>

    [ <%= Html.ActionLink("Вход", "LogOn", "Account") %> ]

<%

  }

%>

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

 

Файлы представлений в структуре проекта

В главе 1 уже рассматривалось размещение файлов в директории проекта, однако сейчас остановимся на этой теме подробнее.

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

Допустим, что в проекте присутствуют два контроллера: HomeController и AccountController, тогда структура директории Views выглядит следующим образом:

□ Account — в этой директории содержатся представления, используемые действиями контроллера Account.

□ Home — в этой директории содержатся представления, используемые действиями контроллера Home.

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

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

□ В случае, когда нескольким контроллерам может понадобиться использовать одну и ту же разметку представления, стоит подумать о будущем развитии проекта и выбрать один из двух путей.

□ Разметка всегда будет одинакова для обоих контроллеров — в этом случае представление следует разместить в директории Shared.

□ Разметка может в будущем различаться — в этом случае общую разметку следует вынести в файл частичного представления и поместить в директорию Shared, а для каждого из контроллеров создать собственное представление. Не стоит смущаться, если в начале разработки проекта каждое из представлений будет содержать только ссылку на общее частичное представление — в будущем будет значительно проще модифицировать представления для контроллеров, если избежать слияния разметки в самом начале развития проекта.

□ Директория Views предназначена только для файлов представлений, обрабатываемых механизмом генерации представлений, статические файлы, используемые на страницах (js, .css, изображения), следует размещать в других директориях. Например, в шаблонном проекте MVC Application, создаваемом Visual Studio, предлагается использовать для статических файлов директорию Content, а для скриптов .js директорию Scripts. Здесь уже выбор может определяться полностью вашим вкусом. Неплохой идеей может быть создание директории Content с вложенной структурой директорий, повторяющей структуру директории Views, где статические файлы будут размещены по тому же принципу, что и в директории Views. Возможно, что вам будет удобнее создать иную структуру директорий и группировать статические файлы не по принадлежности к представлениям, а по расположению звезд на небосклоне или какой-либо другой логике.

 

Данные для отображения и ViewData

 

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

ViewData — это класс типа viewDataDictionary, из названия типа которого очевидно, что ViewData представляет собой коллекцию типа ключ-значения, называемую словарем.

public class ViewDataDictionary : IDictionary {}

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

public ActionResult ViewDataDemo()

{

  ViewData.Add("Hello", "World");

  ViewData["Date"] = DateTime.Now;

  return View();

}

Объекты, содержащиеся в коллекции ViewData, в свою очередь, могут быть использованы в разметке представления.

<%= ((DateTime)ViewData["Date"]).ToLongTimeString() %>

 

Строгая типизация данных представления

Работа с произвольным набором элементов удобна, когда набор данных, отображаемых представлением, меняется в процессе развития проекта. Однако в случаях, когда все данные представления могут быть описаны одним классом, значительно удобнее использовать строго типизированные представления. Для этого коллекция ViewData предоставляет свойство ViewData.Model и возможность строгой типизации представления путем наследования самого класса представления от класса ViewPage. В листинге 5.4 представлена разметка строго типизированного представления.

Листинг 5.4. Представление ViewDataModelStronglyTyped.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"

  Inherits="System.Web.Mvc.ViewPage" %>

  ViewDataModelStronglyTyped

 

    ViewDataModelStronglyTyped

 

 

    Fields

   

      CompanyName:

      <%= Html.Encode(Model.CompanyName) %>

   

   

      ContactName:

      <%= Html.Encode(Model.ContactName) %>

   

   

      ContactTitle:

      <%= Html.Encode(Model.ContactTitle) %>

   

   

      Address:

      <%= Html.Encode(Model.Address) %>

   

 

С точки зрения контроллера, для работы со строго типизированным представлением достаточно присвоить объект свойству ViewData.Model либо передать объект в качестве параметра методу View ().

public ActionResult ViewDataModelStronglyTyped()

{

  NorthwindDatabase db = new NorthwindDatabase();

  return View(db.GetCustomers().First());

}

В случае нестрого типизированного представления объект также может быть передан в свойство ViewData.Model, однако это не имеет практической пользы, поскольку на стороне представления все равно придется выполнять приведение типов для свойств этого объекта.

Преимущества строго типизированного представления очевидны — поддержка подсказки IntelliSense при разработке представлений в Visual Studio, возможность генерации заготовок представлений средствами Visual Studio, как это было выполнено для представления, приведенного в листинге 5.4.

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

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

 

Поиск элементов в коллекции ViewData

Легко представить себе ситуацию, в которой на стороне представления заранее не определено, где находятся данные — в коллекции viewData или же в свойствах объекта viewData.Model. Для поиска этого элемента доступен специальный метод viewData.Eval(), осуществляющий поиск в коллекции элементов и в свойствах объекта модели, при этом поддерживается синтаксис точки (.) между именами свойств объектов.

Например, в представлении используется метод viewData.Eval("customer. lastname"), тогда будет выполнен поиск элемента по описанному далее алгоритму.

1. Будет проверено наличие значения ViewData["customer. lastname"].

2. Если значение отсутствует для приведенного ранее элемента коллекции, то будет осуществлен поиск элемента ViewData["customer"] .

3. В случае если элемент найдет по ключу "customer", то будет осуществлена попытка получить значение ViewData["customer"].lastname, если же такого свойства у объекта, содержащегося под ключом "customer", найдено не будет, будет осуществлена попытка найти элемент вложенной коллекции ViewData["customer"]["lastname"].

4. В случае провала предыдущих попыток поиска будут проверены свойства объекта Model: ViewData.Model.customer.lastname и ViewData.Model.customer["lastname"].

Описанный ранее метод поиска приводит к нескольким выводам — при поиске элементы коллекции viewData имеют приоритет над свойствами объекта, передаваемого через свойство в Model, поэтому следует не допускать совпадения именований свойств объекта, передаваемого через Model и элементов коллекции ViewData.

Использование метода viewData.Eval несет в себе также и возможность передавать строку для форматирования вывода данных.

<%= ViewData.Eval("customer.regDate", "Клиент с {0:yyyy} года.") %>

Метод viewData используется внутренней инфраструктурой MVC Framework для связывания данных с полями ввода и вывода информации о проверке корректности данных, о чем подробнее будет рассказано далее в этой главе вразд. "Валидация данных форм”.

 

Генерация разметки представлением

 

Для создания логики генерации представления в MVC Framework используются несколько подходов.

□ Вложенный управляющий код. Логика отображения данных описывается непосредственно внутри файла разметки ASPX с использованием стандартного синтаксиса <% %> для управляющих конструкций и синтаксиса <%= %> для вывода строковых значений непосредственно в результирующий код разметки.

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

□ Серверные элементы управления. Несмотря на отсутствие возможности использования событийной модели, серверные элементы управления ASP.NET по-прежнему могут быть использованы для декларативного отображения данных.

Примечание

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

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

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

 

Вложенный управляющий код

 

Рассмотрим простейший пример — в представлении необходимо вывести таблицу, содержащую набор записей, переданных контроллером через коллекцию viewData. В качестве источника данных используется ставшая уже стандартом де-факто для примеров база данных Northwind, для доступа к которой используется LINQ для SQL, подробно описанный в главе 3.

В примере мы будем работать с классом Customer, упрощенное представление которого приведено в листинге 5.5.

Листинг 5.5. Класс Customer

public class Customer {

  public string CustomerlD {get; set;}

  public string CompanyName {get; set;}

  public string ContactName {get; set;}

  public string ContactTitle {get; set;}

  public string Address {get; set;}

  public string City {get; set;}

  public string Region {get; set;}

  public string PostalCode {get; set;}

  public string Country {get; set;}

  public string Phone {get; set;}

  public string Fax {get; set;}

  public EntitySet

       CustomerCustomerDemos {get; set;}

  public EntitySet Orders {get; set;}

}

Данные передаются контроллером HomeController представлению Index.aspx через коллекцию viewData в качестве перечислимой коллекции, возвращаемой вспомогательным методом-оберткой над стандартными классами, созданными LINQ для SQL. Код, отвечающий непосредственно за получение данных, не представляет интереса для целей этого примера и не приводится.

public ActionResult Index()

{

  NorthwindDatabase db = new NorthwindDatabase();

  ViewData["Message"] = "Список сотрудников";

  ViewData["Customers"] =

        db.GetCustomers(c => c.CompanyName, 5);

  return View () ;

}

В результате представлению Index.aspx будет передана коллекция объектов типа Customer, которую требуется представить в виде таблицы. С использованием управляющего кода разметка представления может выглядеть так, как показано в листинге 5.6.

Листинг 5.6. Представление Index.aspx

<%@ Page Language="C#"

         MasterPageFile="~/Views/Shared/Site.Master"

         Inherits="System.Web.Mvc.ViewPage" %>

<%@ Import Namespace="MvcViewsDemo.Models" %>

    ContentPlaceHolderID="TitleContent" runat="server">

      Домашняя страница

 

  <%= Html.Encode(ViewData ["Message"]) %>

 

 

   

     

    

   

 

  <% foreach

       ( Customer c in (IEnumerable) ViewData["Customers"]

     ) {  %>

  

    

    

  

  <% } %>

 

        Название компании

      

        Контактное лицо

      

       <%= c.CompanyName %>

    

       <%= c.ContactName %>

    

В результате выполнения примера приведенное ранее представление будет отображено в виде, показанном на рис. 5.1.

В тексте листинга 5.6 управляющие конструкции выделены полужирным шрифтом.

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

 

<%= значение %>

Конструкция вида <%= значение %> используется для вывода значений в результирующий код HTML-разметки. Тип значения может быть любым, который может быть приведен к строке. Значение будет выведено именно в том месте разметки, в котором оно размещено.

 

<% управляющая конструкция %>

Управляющие инструкции позволяют проверять выполнение некоторых условий для вывода той или иной HTML-разметки (условия) либо многократно повторять фрагменты HTML-разметки (циклы).

Оформляются управляющие конструкции по приведенному далее шаблону.

<% инструкция (условия) { %>

  Произвольный HTML-код, который может

  перемежаться другими

  управляющими инструкциями

<% } %>

Пример использования цикла приведен в листинге 5.6, применение условий выглядит аналогично:

<% if (Request.IsAuthenticated) { %>

  Добро пожаловать

  <%= Html.Encode(Page.User.Identity.Name) %>!

  [ <%= Html.ActionLink("Выход", "LogOff", "Account") %> ]

<%

  } else {

%>

  [ <%= Html.ActionLink("Вход", "LogOn", "Account") %> ]

<% } %>

 

Вспомогательные методы

 

Вспомогательные методы класса 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():

Teкст")">

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

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

<%= Html.Encode("Текст")

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

<b>TeKCT</b>"

Примечание

Важно отметить, что методы 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 })

приведет к генерации такой ссылки:

Ссылка

Если же среди маршрутов определен, например, и такой {controller}/{action}/{id}/{ord}, то будет сгенерирована следующая ссылка:

Сcылка

Для создания абсолютной ссылки, либо ссылки с дополнительными параметрами, такими как протокол, якорь, также можно воспользоваться методом ActionLink().

Html.ActionLink("Сайт microsoft.com", "Express", "VStudio",

  "http", "microsoft.com", "download", new {}, null);

В результате будет создана следующая ссылка:

  Сайт microsoft.com

 

Html. RouteLink()

Метод Html.RouteLink() используется для создания ссылок на основании определенных для приложения маршрутов, о которых подробнее можно узнать в следующей главе. Так, например, можно сослаться на определенный именованный маршрут, передав параметры в виде анонимного объекта.

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

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

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

Сcылка

 

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

 

Большая часть вспомогательных методов, приведенных в табл. 5.1, связаны с созданием HTML-разметки для элементов ввода данных HTML-форм. В этой части главы мы рассмотрим их применение.

 

HTML-форма

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

, для этого существует вспомогательный метод Html.BeginForm(действие контроллера, имя контроллера). Если при вызове метода не указаны параметры, атрибут action тега будет заполнен текущим URL.

<% using(Html.BeginForm("About", "Home")){ %>

<% } %>

В примере используется синтаксис using(){}. Дело в том, что метод Html.BeginForm() возвращает объект, реализующий интерфейс IDisposable. Метод Dispose() этого объекта определен таким образом, чтобы выводить закрывающий тег формы

. Вместо синтаксиса using(){} можно воспользоваться вспомогательным методом Html. EndForm().

<% Html.BeginForm("About", "Home"); %>

<% Html.EndForm (); %>

Синтаксис using(){} удобнее при наличии нескольких независимых форм на странице — в этом случае формы будут выглядеть визуально обособленно.

При отображении представления будет создана соответствующая разметка для тега

.

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

<% using (Html.BeginForm("About", "Home",

   new { hello = "world", answer = 42 }, FormMethod.Get )) { %>

<% } %>

Эти параметры будут использованы при генерации тега

.

 

Текстовые поля, скрытые поля и кнопки

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

<%= Html.TextArea("myText", "Hello world!") %>

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

Для создания дополнительных атрибутов вспомогательным методам необходимо передать анонимный объект.

<%= Html.TextBox("myTextBox", "", new { style="color: yellow;",

  @class="helloWorld" })%>

Поскольку в качестве имени свойства анонимного объекта нельзя использовать зарезервированные ключевые слова языка C#, то перед такими именами свойств нужно добавить символ @. В результирующий вывод свойство @class войдет как class, без символа @.

   name="myTextBox" style="color: yellow;" type="text" value="" />

Особым случаем является вспомогательный метод Html.CheckBox, поскольку он создает два HTML-элемента управления. Непосредственно элемент "флажок" и "скрытое поле".

<%= Html.CheckBox("myCheckBox", true) %>

Поскольку в случае если элемент управления не будет отмечен пользователем, то его значение на сервер не передается, поэтому необходимо использование скрытого поля.

  type="checkbox" value="true" />

Стоит отметить, каким образом присваиваются значения элементам управления. При генерации разметки для элемента управления 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"} )) %>

В результате создается разметка, содержащая теги

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

<%= Html.ListBox("lstName",

  new MultiSelectList( new[] {"John", "Paul", "George", "Rringo"} )) %>

Тогда будет определено свойство multiple тега

 

 

 

 

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

SelectList(коллекция объектов,

  название поля объекта,  содержащее значение,

  название поля объекта, содержащее текст,

  выбранное значение

)

Например, в нашем приложении используются объекты типа Person, определение которых представлено далее.

public class Person {

  public int Id { get; set; }

  public string Name { get; set; }

}

Тогда в методе контроллера для сохранения в коллекцию ViewData коллекцию элементов списка можно передать, используя конструктор SelectList так, как показано далее.

public ActionResult About()

{

  List lst = new List

  {

    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-разметка, приведенная далее.

Таким образом, коллекция бизнес-объектов может быть использована для заполнения списков необходимыми данными.

 

Индикаторы корректности введенных данных

Пользователям свойственно ошибаться при вводе данных, и приложению необходимо уведомлять пользователя о допущенных ошибках и конкретных полях формы, которые заполнены некорректно. Для этого существуют два вспомогательных метода: 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" %>

                    runat="server">

  Edit

 

    Редактирование сведений о товаре

 

  <%= Html.ValidationSummary("npи редактировании сведений

                    о товаре произошли ошибки.") %>

  <% using (Html.BeginForm())

  {%>

   

      Редактирование сведений о товаре

     

       

          Код продукта:

       

        <%= Model.ProductID.ToString() %>

        <%= Html.Hidden("ProductId", Model.ProductID)%>

     

      

        

          Название:

       

        <%= Html.TextBox("ProductName", Model.ProductName) %>

        <%= Html.ValidationMessage("ProductName", "*") %>

     

      

       

          Цена:

        

        <%= Html.TextBox("UnitPrice",

               String.Format("{0:F}", Model.UnitPrice)) %>

        <%= Html.ValidationMessage("UnitPrice", "*") %>

      

     

       

          На складе:

       

        <%= Html.TextBox("UnitsInStock", Model.UnitsInStock) %>

        <%= Html.ValidationMessage("UnitsInStock", "*") %>

     

      

       

          Заказано:

       

        <%= Html.TextBox("UnitsOnOrder", Model.UnitsOnOrder) %>

        <%= Html.ValidationMessage("UnitsOnOrder", "*") %>

      

     

       

     

    

  <% } %>

 

    <%=Html.ActionLink("K списку товаров", "Index") %>

 

В листинге 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" %>

  Edit

 

    Редактирование сведений о товаре

 

  <%= Html.ValidationSummary("при редактировании сведений

                   о товаре произошли ошибки.") %>

  <% using (Html.BeginForm())

  {%>

   

      Редактирование сведений о товаре

     

       

          Код продукта:

        

        <%= ViewData.Eval("ProductID") %>

        <%= Html.Hidden("ProductID")%>

      

     

       

          Название:

       

        <%= Html.TextBox("ProductName") %>

        <%= Html.ValidationMessage("ProductName") %>

     

     

       

          Цена:

       

        <%= Html.TextBox("UnitPrice") %>

        <%= Html.ValidationMessage("UnitPrice") %>

     

      

       

          На складе:

       

        <%= Html.TextBox("UnitsInStock") %>

        <%= Html.ValidationMessage("UnitsInStock") %>

     

     

       

          Заказано:

       

        <%= Html.TextBox("UnitsOnOrder") %>

        <%= Html.ValidationMessage("UnitsOnOrder") %>

     

     

        

      

   

  <% } %>

 

    <%=Html.ActionLink("K списку товаров", "Index") %>

  

Примечание

Важно обратить внимание, что при использовании кода, аналогичного приведенному в листинге 5.9, поиск значений элементов формы будет осуществляться через ViewData.Eval(), и значение ViewData["SomeProperty"] имеет больший приоритет, чем ViewData.Model.SomeProperty. Поэтому, во избежание трудноуловимых ошибок, при создании кода контроллера стоит с особенной тщательностью относиться к тому, как передаются данные — через свойства объекта-модели или через коллекцию ViewData.

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

 

Создание собственного вспомогательного метода

 

Поскольку вспомогательные методы — обычные методы расширения класса HtmlHelper, принимающие произвольный набор параметров и возвращающие строковые значения, то создать свой вспомогательный метод не составляет труда. Например, в листинге 5.10 приведена заготовка вспомогательного метода, отображающего элемент управления для ввода даты.

Листинг 5.10. Вспомогательный метод для отображения элемента для ввода даты

using System.Web.Mvc;

public static class DataPickerHelper {

  public static string DatePicker(this HtmlHelper html,

          string id, string text)

  {

  }

}

Такой метод может быть использован в коде представления через синтаксис

<%= Html.DatePicker("id", "name") %>.

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

 

Конкатенация строк

Самый очевидный способ — сгенерировать разметку конкатенацией строк. Код для этого метода приведен в листинге 5.11.

Листинг 5.11. Реализация метода DatePickerc помощью конкатенации строк

using System.Web.Mvc;

using System.Text;

using System;

using System.Globalization;

public static class DataPickerHelper

{

  public static string DatePicker(this HtmlHelper html, string id)

 {

    return DatePicker(html, id, String.Empty);

  }

  public static string DatePicker(this HtmlHelper html,

                  string id, string text)

  {

    StringBuilder sb = new StringBuilder();

    if (!String.IsNullOrEmpty(text))

    {

      sb.Append("

      sb.Append (id);

      sb.Append("\">");

      sb.Append(text);

    }

    /* Day */

    sb.Append("");

      sb.Append(" ");

    }

    /* Month */

    sb.Append("");

    sb.Append(" ");

    /* Year */

    sb.Append("");

    if (!String.IsNullOrEmpty(text))

    {

      sb.Append("

");

    }

    return sb.ToString();

  }

}

Плюс решения, продемонстрированного в листинге 5.11, — простота реализации "в лоб", копированием кода из макета верстки. Минусы решения очевидны — поддержка такого кода сложна за счет необходимости работы с кодом, перемешанным с большим количеством строковых констант.

 

Использование ресурсов

Упростить модификацию самой разметки и внесение в нее косметических изменений можно за счет размещения статических строковых констант в файлах ресурсов. Генерацию финальной разметки при этом можно выполнять, используя только методы форматирования строк, как это показано в листинге 5.12.

Листинг 5.12. Реализация метода DatePickerc помощью ресурсов

public static string DatePicker(this HtmlHelper html,

                           string id, string text)

{

  return String.Format(Resources.DatePicker, id, text,

                Resources.DaysOptions,

                Resources.MonthsOptions, Resources.YearsOptions);

}

В приведенном фрагменте есть заметное преимущество — централизованное управление разметкой, возможность использования разной разметки для различных культур и разделение самой разметки и кода. Можно пойти дальше и создать дополнительную обертку над ресурсами, которая будет отвечать за небольшую модификацию фрагментов разметки. Кода в случае, приведенном в листинге 5.12, значительно меньше, чем в листинге 5.11, однако гибкость такого решения может быть недостаточной для вспомогательных методов, требующих частой модификации.

 

Использование дополнительных слоев абстракции

Достигнуть большего контроля над логикой и разметкой можно за счет использования дополнительной абстракции над созданием самой разметки, вынесением отдельных методов, генерирующих повторяющие элементы, и созданием тегов с помощью специального класса TagBuilder. В WebForms при создании контролов (Custom Controls) используется похожий подход.

TagBuilder активно применяется в расширениях, входящих в саму сборку System.Web.Mvc, в чем можно убедиться, посмотрев, например, на исходный код System.Web.MVC.SelectExtensions.

В листинге 5.13 приведен код, использующий больше абстракции, нежели предыдущие. Также в листинге 5.13 продемонстрирована простая концепция по восстановлению значений после отправки данных на сервер (метод Getvalue, выполняющий поиск в коллекции viewData, затем в параметрах запроса).

Листинг 5.13. Реализация метода DatePicker с дополнительными слоями абстракции

public static class DataPickerHelper

{

  private static string DAY_PREFIX = "day_";

  private static string MONTH_PREFIX = "month_";

  private static string YEAR_PREFIX = "year_";

  private static string ListItemToOption(SelectListItem item)

  {

    TagBuilder builder = new TagBuilder("option")

    {

      InnerHtml = HttpUtility.HtmlEncode(item.Text)

    };

    if (item.value != null)

      builder.Attributes["value"] = item.Value;

    if (item.Selected)

      builder.Attributes["selected"] = "selected";

    return builder.ToString(TagRenderMode.Normal);

}

private static string SelectList(string id,

                             List items)

{

  StringBuilder listItemBuilder = new StringBuilder();

  foreach (var item in items)

  {

    listItemBuilder.AppendLine(ListItemToOption(item));

  }

  TagBuilder tagBuilder = new TagBuilder("select")

  {

    InnerHtml = listItemBuilder.ToString()

  };

  tagBuilder.Attributes.Add("id", id);

  tagBuilder.Attributes.Add("name", id);

  return tagBuilder.ToString(TagRenderMode.Normal);

}

  public static string DatePicker(this HtmlHelper html, string id)

  {

    return DatePicker(html, id, String.Empty);

  }

  public static string DatePicker(this HtmlHelper html,

                                string id, string text)

  {

    // buffer

    StringBuilder sb = new StringBuilder();

    // generate days

    List days = new List();

    string dayValue = GetValue(html, DAY_PREFIX + id);

    for (int i = 0; i <= 31; i++)

    {

      days.Add(new SelectListItem

      {

        Text = (i == 0) ? String.Empty : i.ToString(),

        Value = i.ToString(),

        Selected = (dayValue == i.ToString())

      });

    }

    sb.AppendLine(SelectList(DAY_PREFIX + id, days));

    // generate months

    List months = new List();

    string monthValue = GetValue(html, MONTH_PREFIX + id);

    for (int i = 0; i <= 12; i++)

    {

      months.Add(new SelectListItem

      {

        Text = (i == 0) ? String.Empty :

           DateTimeFormatInfo.CurrentInfo.MonthNames[i — 1],

        Value = i.ToString(),

        Selected = (monthValue == i.ToString())

      });

    }

    sb.AppendLine(SelectList(MONTH_PREFIX + id, months));

    // generate years

    List years = new List();

    string yearValue = GetValue(html, YEAR_PREFIX + id);

    for (int i = 1900; i <= DateTime.Now.Year; i++)

    {

      years.Add(new SelectListItem

      {

        Text = (i == 1900) ? String.Empty : i.ToString(),

           Value = i.ToString(),

        Selected = (yearValue == i.ToString())

      });

    }

    sb.AppendLine(SelectList(YEAR_PREFIX + id, years));

    // parent tag

    if (!String.IsNullOrEmpty(text))

    {

      TagBuilder div = new TagBuilder("div");

      div.Attributes.Add("id", id);

      div.InnerHtml = text + sb.ToString();

      sb = new StringBuilder(

              div.ToString(TagRenderMode.Normal));

    }

    return sb.ToString();

  }

  private static string GetValue(HtmlHelper html, string id)

  {

    object o = null;

    if (html.ViewData != null)

      o = html.ViewData.Eval(id);

    if (o == null)

      o = html.ViewContext.RequestContext.HttpContext.Request.Params[id];

    return (o == null) ? String.Empty : o.ToString();

  }

}

 

Использование серверных элементов управления WebForms

В главе 2, в разд. "Использование элементов управления WebForms в MVC-приложениях” уже была продемонстрирована возможность применения серверных элементов управления в представлениях MVC. Как уже было сказано ранее, использование серверных элементов управления может быть оправдано, если не требуется интерактивное взаимодействие с соответствующим элементом, и он используется только для генерации разметки.

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

Сделать это можно просто, учитывая, что класс Webcontrol, которому наследуют классы конкретных элементов управления, содержит метод Render(), генерирующий разметку. Как раз метод Render() применяет инфраструктура WebForms для всех элементов управления на страницах. Пример использования элемента управления WebForms приведен в листинге 5.14.

Листинг 5.14. Пример использования класса Button во вспомогательном методе

public static class WebFormsHelper {

  public static string WebFormsButton(this HtmlHelper html)

  {

    Button control = new Button { Text = "Web Forms Button" };

    StringBuilder sb = new StringBuilder();

    HtmlTextWriter htmlWriter = new HtmlTextWriter(

                                 new StringWriter(sb));

    control.RenderControl(htmlWriter);

    return sb.ToString();

  }

}

Листинг 5.14 демонстрирует концепцию использования серверных элементов управления: создать экземпляр класса элемента управления, задать его свойства, вызвать при необходимости методы, а затем сгенерировать разметку, которая будет возвращена вспомогательным методом.

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

 

Частичные представления

 

Ранее в этой главе уже говорилось об использовании частичных представлений. Механизм частичных представлений позволяет многократно использовать разметку в разных методах контроллеров, аналогично примеру из листингов 5.1—5.3. Кроме того, частичные представления могут быть использованы для отображения списков элементов. Так, в листинге 5.15 приведено частичное представление, которое используется для отображения строк таблицы товаров в представлении Index.aspx, код которого приведен в листинге 5.16.

Листинг 5.15. Частичное представление ProductListItem.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<

                        MvcViewsDemo.Models.Product>" %>

 

    <%= Html.ActionLink("Изменить", "Edit", new { id=Model.ProductID }) %>

  

 

    <%= Html.Encode(Model.ProductID)%>

 

 

    <%= Html.Encode(Model.ProductName)%>

 

 

    <%= Html.Encode(String.Format("{0:F}", Model.UnitPrice))%>

 

 

    <%= Html.Encode(Model.UnitsInStock)%>

 

 

    <%= Html.Encode(Model.UnitsOnOrder)%>

 

Листинг 5.16. Представление Index.aspx

<%@ Page Title="" Language="C#"

         MasterPageFile="~/Views/Shared/Site.Master"

         Inherits="System.Web.Mvc.ViewPage

                      MvcViewsDemo.Models. Product»" %>

             runat="server">

  Список товаров

             runat="server">

 

    Список товаров

 

 

   

     

     

     

     

     

     

   

    <% foreach (var item in Model)

    { %>

      <% Html.RenderPartial("ProductListItem", item); %>

    <% } %>

 

     

        Код товара

        Название

     

        Цена

     

        На складе

      

        Заказано

     

Вынесение оформления элемента списка в частичное представление позволяет разгрузить код разметки самого представления, а также повторно использовать оформление элемента представления на других представлениях, если это разумно с точки зрения логики приложения.

 

Создание гаджетов

Частичные представления также могут быть использованы для создания гаджетов — элементов страницы, содержащих данные, не связанные с основным представлением. Гаджеты находят широкое применение на страницaх всевозможных порталов — когда есть основная страница, с которой работает пользователь, и куда выводятся дополнительные независимые информационные блоки, например, баннеры или текстовые ссылки рекламных сетей.

Разумеется, можно реализовать похожую функциональность так, как это показано в листингах 5.1—5.3, разместив частичные представления на мастерской странице. Однако часто оказывается удобным вынести функциональность гаджета в отдельное представление и полностью отделить логику гаджета от основных представлений. В поставку MVC Framewrok функциональность по созданию гаджетов не входит, однако она доступна в проекте MVC Futures. Скачать код и сборку MVC Futures () можно на странице проекта MVC Framework на портале CodePlex. После подключения этой сборки к проекту функциональность по созданию гаджетов будет доступна в проекте.

Примечание

В проект MVC Futures входит код, не вошедший по каким-либо причинам в основную поставку MVC Framework. Причины могут быть разными — от недостаточной стабильности кода до желания разработчиков придумать лучшее решение в следующей версии. Однако вас не должна страшить потенциальная возможность того, что поддержка гаджетов никогда не будет включена в том виде, в котором она представлена в MVC Futures, в сам MVC Framework, поскольку вам доступен полный исходный MVC Futures, и вы можете использовать его непосредственно в ваших проектах. Более того, вы можете выделить только ту часть исходного кода, которая используется в ваших проектах, и перенести его из сборки MVC Futures Microsoft.Web.Mvc.dll в сборку вашего проекта.

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

Собственно все, что необходимо для создания гаджетов — это вспомогательный метод Html.RenderAction(), который позволяет включить на страницу вывод произвольного действия произвольного контроллера. Например, на некоторых страницах сайта нужно добавить независимый блок, выводящий информацию о текущем курсе доллара. Для этого создается частичное представление, разметка которого приведена в листинге 5.17, и контроллер, код которого приведен в листинге 5.18.

Листинг 5.17. Частичное представление ExchangeRates.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

 

   

   

 

      1 USD =

   

      <%= ViewData["USD"] %> RUB

      RUB

   

Листинг 5.18. Контроллер GadgetsController

using System.Web.Mvc;

using MvcViewsDemo.Services;

namespace MvcViewsDemo.Controllers

{

  public class GadgetsController : Controller

  {

    public ActionResult ExchangeRates()

    {

      ViewData["USD"] = ExchangeRatesService.GetRate("USD");

      return View();

    }

  }

}

Использовать гаджет ExchangeRates можно в любом представлении. Например, при использовании в представлении Index, код которого показан в листинге 5.19, получается результат, приведенный на рис. 5.4.

Листинг 5.19. Представление Index.aspx, использующее гаджет ExchangeRates

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"

                  Inherits="System.Web.Mvc.ViewPage" %>

<%@ Import Namespace="Microsoft.Web.Mvc" %>

<%@ Import Namespace="MvcViewsDemo.Models" %>

               runat="server">

  Домашняя страница

               runat="server">

 

    <%= Html.Encode(ViewData["Message"]) %>

 

 

   

     

       

        

      

   

    <% foreach (

         Customer c in (IEnumerable)ViewData["Customers"])

    { %>

     

       

       

     

    <% } %>

 

          Название компании

        

          Контактное лицо

        

          <%= c.CompanyName %>

       

          <%= c.ContactName %>

       

  <% Html.RenderAction("ExchangeRates", "Gadgets"); %>

В листинге 5.19 отмечено, что для использования метода Html.RenderAction() необходимо подключить пространство имен Microsoft.Web.Mvc. Также из листинга 5.19 видно, что гаджету не передается никаких дополнительных данных, поскольку он полностью независим от страницы, на которой отображается.

Примечание

Для того чтобы не добавлять директиву Import в разметку представления, можно подключить пространство имен Microsoft.Web.Mvc для всех файлов проекта в разделе namespaces файла web.config.

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

#i_039.jpg

Удобство использования гаджетов для независимых блоков на страницах в том, что в процессе развития проекта могут меняться контроллеры, действия и представления, используемые веб-приложением, однако код независимых блоков легко может быть перенесен на другие страницы и модифицирован, просто лишь перестановкой вызова Html.RenderAction(). Кроме того, появляется дополнительная степень свободы в модификации самих гаджетов без необходимости затрагивать код основных контроллеров и представлений. Например, код гаджета может быть доработан так, как показано в листингах 5.20, 5.21, чтобы получить результат, представленный на рис. 5.5.

Листинг 5.20. Доработанное частичное представление ExchangeRates

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

  <% foreach (var rate in (Dictionary)ViewData["rates"])

  { %>

   

     

     

    

  <% } %>

        1

        <%= rate.Key %>

     

        <%= rate.Value %> RUB

      

Листинг 5.21. Доработанный контроллер Gadgets

using System.Web.Mvc;

using MvcViewsDemo.Services;

using System. Collections.Generic;

namespace MvcViewsDemo.Controllers

{

  public class GadgetsController : Controller

  {

    public ActionResult ExchangeRates()

    {

      Dictionary rates =

        new Dictionary();

      rates.Add("USD", ExchangeRatesService.GetRate("USD"));

      rates.Add("EUR", ExchangeRatesService.GetRate("EUR"));

      ViewData["Rates"] = rates;

      return View();

    }

  }

}

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

 

Заключение

Материала, представленного в главах 1—5, достаточно для того, чтобы начать создавать базовые веб-приложения с использованием MVC Framework. В следующих главах вы познакомитесь ближе с механизмами маршрутизации и разработкой клиентской части веб-приложений и будете готовы к разработке веб-приложений любой сложности.