# Представления
## Введение в представления
Обычно при обращении к веб-приложению пользователь ожидает получить веб-страницу с какими-нибудь данными. В MVC для этого, как правило, используются __представления__, которые определяют внешний вид приложения и на основе которых потом формируется веб-страница. В ASP.NET MVC Core представления - это файлы с расширением `cshtml`, которые содержат код пользовательского интерфейса в основном на языке html, а также конструкции Razor - специального движка представлений, который позволяет переходить от кода html к коду на языке C#.
Для хранения представлений в проекте ASP.NET Core предназначена папка `Views`. Например, если мы возьмем проект по типу ASP.NET Core Web App (Model-View-Controller), то мы увидим, что он содержит ряд представлений.
Структура каталога `Views`:
- Во-первых, как правило, для каждого контроллера в проекте создается подкаталог в папке Views, который называется по имени контроллера и который хранит представления, используемые методами данного контроллера. Так, по умолчанию имеется контроллер __HomeController__ и для него в папке `Views` есть подкаталог `Home` с представлениями для методов контроллера HomeController - в нашем случае это файлы `Index.cshtml` и `Privacy.cshtml`.
- Также здесь есть папка `Shared`, которая хранит общие представления для всех контроллеров. По умолчанию это файлы `_Layout.cshtml` (используется в качестве мастер-страницы), `Error.cshtml` (использутся для отображения ошибок) и `_ValidationScripsPartial.cshtml` (частичное представление, которое подключает скрипты валидации формы).
- И в корне каталога `Views` также можно найти два файла `_ViewImports.cshtml` и `_ViewStart.cshtml`. Эти файлы содержат код, который автоматически добавляется ко всем представлениям. `_ViewImports.cshtml` устанавливает некоторые общие для всех представлений пространства имен, а `_ViewStart.cshtml` устанавливает общую мастер-страницу.
## Вызов представления. ViewResult
Вспомним, как выглядел метод, реализующий действие в контроллере
```cs
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
```
За работу с представлениями отвечает объект __ViewResult__. Он производит рендеринг представления в веб-страницу и возвращает ее в виде ответа клиенту. Чтобы возвратить объект __ViewResult__, в методе контроллера вызывается метод _View_.
Вызов метода _View_ возвращает объект __ViewResult__. Затем уже __ViewResult__ производит рендеринг определенного представления в ответ. По умолчанию контроллер производит поиск представления в проекте по следующим путям:
```
/Views/Имя_контроллера/Имя_представления.cshtml
/Views/Shared/Имя_представления.cshtml
```
Согласно настройкам по умолчанию, если название представления не указано явным образом, то в качестве представления будет использоваться то, имя которого совпадает с именем действия контроллера. Например, вышеопределенное действие __Index__ по умолчанию будет производить поиск представления `Index.cshtml` в папке `/Views/Home/`.
Метод View() имеет четыре перегруженных версии:
- __View()__: для генерации ответа используется представление, которое по имени совпадает с вызывающим методом
- __View(string? viewName)__: в метод передается имя представления, что позволяет переопределить используемое по умолчанию представление
- __View(object? model)__: передает в представление данные в виде объекта model
- __View(string? viewName, object? model)__: переопределяет имя представления и передает в него данные в виде объекта model
Вторая версия метода позволяет переопределить используемое представление. Если представление находится в той же папке, которая предназначена для данного контроллера, то в метод _View()_ достаточно передать название представления без расширения:
```cs
public IActionResult Index()
{
return View("About");
}
```
В этом случае метод _Index_ будет использовать представление по пути `Views/Home/About.cshtml`. Если же представление находится в другой папке, то нам надо передать полный путь к представлению:
```cs
public IActionResult Index()
{
return View("~/Views/Some/About.cshtml");
}
```
В данном случае предполагается, что представление располагается по пути `Views/Some/About.cshtml`
Напомню как выглядит представление `Index.cshtml`:
```html
@{ViewData["Title"] = "Заголовок нашего MVC приложения";}
Привет ЙОТК!
```
А где же теги `` и ``? Внимательно перечитываем текущий параграф и видим, что есть еще какая-то мастер-страница `_ViewStart.cshtml` - посмотрим что в ней:
```cshtml
@{
Layout = "_Layout";
}
```
Блок кода `@{}` содержит вставку кода C#, в которой задается `Layout`. `_Layout` это название файла, который лежит в каталоге `Shared` (не привязан к какому-то контроллеру, так как используется во всех контроллерах приложения) и содержит шаблон для верстки:
```html
@ViewData["Title"] - asp_net_mvc
@RenderBody()
@await RenderSectionAsync("Scripts", required: false)
```
Это уже похоже на нормальный html. Содержимое представления вставляется вместо вызова `@RenderBody()`.
## Движок представлений Razor
Представления в ASP.NET Core MVC может содержать не только стандартный код html, но и также вставки кода на языке C#. Для обработки кода, который содержит как элементы html, так и конструкции языка C#, применяется движок представлений.
В действительности при вызове метода _View_ контроллер не производит рендеринг представления и не генерирует разметку html. Контроллер только готовит данные и выбирает, какое представление надо возвратить в качестве объекта __ViewResult__. Затем уже объект __ViewResult__ обращается к движку представления для рендеринга представления в выходной ответ.
По умолчанию в ASP.NET Core MVC применяется один движок представлений - __Razor__. Хотя при желании мы можем также использовать какие-то другие сторонние движки или создать свой движок представлений самостоятельно. Цель движка представлений __Razor__ - определить переход от разметки html к коду C#.
Синтаксис __Razor__ довольно прост - все его конструкции предваряются символом `@`, после которого происходит переход к коду C#.
Например, в файле `_Layout.cshtml` есть такие вставки:
```cshtml
тут в тег title вставляется содержимое словаря ViewData (мы это значение заполняли в файле Index.cshtml)
@ViewData["Title"] - asp_net_mvc
а тут вызывается функция RenderBody
@RenderBody()
```
## Типы конструкций Razor
Все конструкции Razor можно условно разделить на два вида: однострочные выражения и блоки кода.
Пример применения однострочных выражений:
```
Date: @DateTime.Now.ToLongDateString()
```
В данном случае используется объект __DateTime__ и его метод _ToLongDateString()_
Или еще один пример:
```
@(20 + 30)
```
Так как перед скобками стоит знак `@`, то выражение в скобках будет интерпретироваться как выражение на языке C#. Поэтому браузер выведет число `50`, а не строку "20 + 30".
Но если вдруг мы создаем код html, в котором присутствует символ `@` не как часть синтаксиса Razor, а сам по себе, то, чтобы его отобразить, нам надо его дублировать:
```
@@DateTime.Now = @DateTime.Now.ToLongDateString()
```
Блоки кода могут иметь несколько выражений. Блок кода заключается в фигурные скобки, а каждое выражение завершается точкой с запятой аналогично блокам кода и выражениям на C#:
```
@{
ViewData["Title"] = "Home Page";
}
```
В блоках кода мы можем определить обычные переменные и потом их использовать в представлении.
В Razor могут использоваться комментарии. Они располагаются между символами `@* *@`:
```
@* текст комментария *@
```
### Условные конструкции
Также мы можем использовать условные конструкции:
```
@{
string morning = "Good Morning";
string evening = "Good Evening";
string hello = "Hello";
int hour = DateTime.Now.Hour;
}
@if (hour < 12)
{
@morning
}
else if (hour > 17)
{
@evening
}
else
{
@hello
}
```
Конструкция switch:
```
@{
string language = "german";
}
@switch(language)
{
case "russian":
Привет мир!
break;
case "german":
Hallo Welt!
break;
default:
Hello World!
break;
}
```
### Циклы
Кроме того, мы можем использовать все возможные циклы. Цикл __for__:
```
@{
string[] people = { "Tom", "Sam", "Bob" };
}
@for (var i = 0; i < people.Length; i++)
{
@people[i]
}
```
Цикл __foreach__:
```
@{
string[] people = { "Tom", "Sam", "Bob" };
}
@foreach (var person in people)
{
@person
}
```
Цикл __while__:
```
@{
string[] people = { "Tom", "Sam", "Bob" };
var i = 0;
}
@while ( i < people.Length)
{
@people[i++]
}
```
Цикл __do..while__:
```
@{
var i = 0;
}
@do
{
@(i * i)
}
while ( i++ < 5);
```
### try...catch
Конструкция __try...catch...finally__, как и в C#, позволяет обработать исключение, которое может возникнуть при выполнение кода:
```
@try
{
throw new InvalidOperationException("Something wrong");
}
catch (Exception ex)
{
Exception: @ex.Message
}
finally
{
finally
}
```
Если в блоке __try__ выбрасывается исключение, то выполняется блок __catch__. И в любом случае в конце блока try и catch выполняется блок __finaly__.
### Вывод текста в блоке кода
Обычный текст в блоке кода мы не сможем вывести:
```
@{
bool isEnabled = true;
}
@if (isEnabled)
{
Hello World
}
```
В этом случае Razor будет рассматривать строку "Hello World" как набор операторов языка C#, которых, естественно в C# нет, поэтому мы получим ошибку. И чтобы вывести текст как есть в блоке кода, нам надо использовать выражение `@:`:
```
@{
bool isEnabled = true;
}
@if (isEnabled)
{
@: Hello
}
```
### Функции
Директива `@functions` позволяет определить функции, которые могут применяться в представлении. Например:
```
@functions
{
public int Sum(int a, int b)
{
return a + b;
}
public int Square(int n) => n * n;
}
Sum of 5 and 4: @Sum(5, 4)
Square of 4: @Square(4)
```
## Передача данных в представление
Существуют различные способы передачи данных из контроллера в представление:
- ViewData
- ViewBag
- Модель представления
### ViewData
ViewData представляет словарь из пар ключ-значение:
```cs
public IActionResult Index()
{
ViewData["Message"] = "Hello METANIT.COM";
return View();
}
```
Здесь динамически определяется во ViewData объект с ключом "Message" и значением "Hello METANIT.COM". При этом в качестве значения может выступать любой объект. И после этому мы можем его использовать в представлении:
```
@{
ViewData["Title"] = "ASP.NET Core MVC";
}
@ViewData["Title"]
@ViewData["Message"]
```
Причем не обязательно устанавливать все объекты во ViewData в контроллере. Так, в данном случае объект с ключом "Title" устанавливается непосредственно в представлении.
### ViewBag
ViewBag во многом подобен ViewData. Он позволяет определить различные свойства и присвоить им любое значение. Так, мы могли бы переписать предыдущий пример следующим образом:
```cs
public IActionResult Index()
{
ViewBag.Message = "Hello METANIT.COM";
return View();
}
```
И таким же образом нам надо было бы изменить представление:
```cshtml
@{
ViewBag.Title = "ASP.NET Core MVC";
}
@ViewBag.Title.
@ViewBag.Message
```
И не важно, что изначально объект ViewBag не содержит никакого свойства Message, оно определяется динамически. При этом свойства ViewBag могут содержать не только простые объекты типа string или int, но и сложные данные. Например, передадим список:
```cs
public IActionResult Index()
{
ViewBag.People = new List { "Tom", "Sam", "Bob" };
return View();
}
```
И также в представлении мы можем получить этот список:
```cshtml
Count: @ViewBag.People.Count
@foreach(string person in ViewBag.People)
{
@person
}
```
### Модель представления
Модель представления является во многих случаях более предпочтительным способом для передачи данных в представление. Для передачи данных в представление используется одна из версий метода View:
```cs
public IActionResult Index()
{
var people = new List { "Tom", "Sam", "Bob" };
return View(people);
}
```
В метод View передается список, поэтому моделью представления Index.cshtml будет тип List (либо IEnumerable). И теперь в представлении мы можем написать так:
```cshtml
@model List
Count: @Model.Count
@foreach(string person in Model)
{
@person
}
```
В самом начале представления с помощью директивы `@model` устанавливается модель представления. Тип модели должен совпадать с типом объекта, который передается в метод View() в контроллере.
Установка модели указывает, что объект __Model__ теперь будет представлять объект `List` или список. И мы сможем использовать Model в качестве списка.
## Мастер-страницы (layout)
Мастер-страницы или layout позволяют задать единый шаблон для представлений и применяются для создания единообразного, унифицированного вида сайта. По сути мастер-страницы - это те же самые представления, которе могут включать в себя другие представления. Например, можно определить на мастер-странице общие для всех остальных представлений меню, а также подключить общие стили и скрипты. В итоге нам не придется на каждом отдельном представлении прописывать путь к файлам стилей, а потом при необходимости его изменять. А специальные теги позволяют вставлять в определенное место на мастер-страницах другие представления.
Код мастер-страницы напоминает полноценную веб-страницу: здесь присутствуют основные теги ``, ``, `` и так далее. И также здесь могут использоваться конструкции Razor. Фактически это то же самое представление. Например, через выраженеи `@ViewBag.Title` из каждого отдельного представления будет передаваться значение для заголовка веб-страницы.
Главное же отличие от обычных представлений состоит в использовании метода `@RenderBody()`, который является плейсхолдером и на место которого потом будут подставляться другие представления, использующие данную мастер-страницу. В итоге мы сможем легко установить для всех представлений веб-приложения единообразный стиль оформления.
В каждом представлении через синтаксис Razor доступно свойство _Layout_, которое хранит ссылку на мастер-страницу. В качестве мастер страницы устанавливается файл `_Layout.cshtml`, но мы, при желании, можем поменять файл шаблона, переписав свойство `Layout` в контроллере или представлении.
## Переопределение мастер-страницы
Если вдруг мы хотим глобально по всему проекту поменять мастер-страницу на другой файл, который расположен в какой-то другой папке, например, в корне каталога `Views`, то нам надо использовать полный путь к файлу в `_ViewStart.cshtml`:
```cshtml
@{
Layout = "~/Views/_Layout.cshtml";
}
```
Код из `_ViewStart.cshtml` выполняется до любого кода в представлении. И чтобы переопределить мастер-страницу, в представлении доcтаточно установить свойство _Layout_.
Естественно также мы можем переопределить мастер-страницу в каждом отдельном представлении с помощью свойства _Layout_.
```cshtml
@{
ViewBag.Title = "Home Page";
Layout = "~/Views/Shared/_Layout.cshtml";
}
Представление Index.cshtml
```
Мы можем вообще не использовать мастер-страницу, тогда в представлении свойству _Layout_ надо присвоить значение `null`:
```cshtml
@{
Layout = null;
}
Home Page
Представление Index.cshtml
```
## Секции
Кроме метода _RenderBody()_, который вставляет основное содержимое представлений, мастер-страниц может также использовать специальный метод _RenderSection()_ для вставки секций. Мастер-страница может иметь несколько секций, куда представления могут поместить свое содержимое. Например, добавим к мастер-странице `_Layout.cshtml` секцию с именем "footer":
```html
METANIT.COM | @ViewBag.Title
```
Чтобы использовать тип __Person__ в представлении, мы вынуждены импортировать с помощью директивы __using__ пространство имен, где этот тип определен. В данном случае ничего сложного нет, однако если у нас куча представлений, где мы хотим использовать этот же тип __Person__, то мы будем вынуждены определить то же самое выражение импорта во всех представлениях. Это может создавать некоторые неудобства. Во-первых, мы повторяем один и тот же код. Во-вторых, если пространство имен изменится, то мы вынуждены будем менять все представления. В-третьих, возможно, мы захотим подключить еще какие-то пространства имен, что увеличит работу, если будут какие-то изменения. Файл `_ViewImports.cshtml` решает эту проблему
Содержимое файла `_ViewImports.cshtml` в моем проекте:
```cshtml
@using asp_net_mvc
@using asp_net_mvc.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
```
Для каждой группы представлений в одной папке мы можем определить свой файл `_ViewImports.cshtml`. Например, если мы хотим отдельно подключать ряд пространств имен в только в представления контроллера __HomeController__, тогда нам надо добавить файл `_ViewImports.cshtml` в каталог `Views/Home`
## Частичные представления
Кроме обычных представлений и мастер-страниц в ASP.NET Core MVC также можно использовать частичные представления или __partial views__. Их отличительной особенностью является то, что их можно встраивать в другие обычные представления. Частичные представления могут использоваться также как и обычные, однако наиболее удобной областью их использования является рендеринг результатов AJAX-запроса. По своему действию частичные представления похожи на секции, которые использовались в прошлой теме, только их код выносится в отдельные файлы.
Частичные представления полезны для создания различных панелей веб-страницы, например, панели меню, блока входа на сайт, каких-то других блоков.
За рендеринг частичных представлений отвечает объект __PartialViewResult__, который возвращается методом _PartialView()_. Этот метод имеет четыре версии:
- PartialView(): для генерации ответа используется представление, которое по имени совпадает с вызывающим методом
- PartialView(string? viewName): в метод передается имя представления, что позволяет переопределить используемое по умолчанию представление
- PartialView(object? model): передает в представление данные в виде объекта model
- PartialView(string? viewName, object? model): переопределяет имя представления и передает в него данные в виде объекта model
Итак, определим в контроллере __HomeController__ действие _Hello_, которое будет использовать частичное представление:
```cs
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Hello()
{
return PartialView();
}
}
```
Теперь добавим в папку `Views/Home` новое представление `Hello.cshtml`, в котором будет простенькое содержимое:
```html
Hello View
```
Теперь обратимся к методу Hello как к обычному действию контроллера, и оно нам вернет частичное представление
По своему действию частичное представление похоже на обычное, только для него по умолчанию не определяется мастер-страница (то есть в ответе на запрос не будет ни заголовков ни шаблона, а только тот html, который есть в частичном представлении).
### Встраивание частичного представления в обычное
Теперь рассмотрим, как мы можем встраивать частичные представления в обычные. Для этого изменим представление `Index.cshtml`:
```cshtml
Index View
@await Html.PartialAsync("Hello")
```
Метод _Html.PartialAsync()_ встраивает код частичного представления в обычное. Он является асинхронным и возвращает объект __IHtmlContent__, который представляет html-содержимое и который обернут в объект `Task`.
Обращения к методу _Hello()_ в контроллере при этом не происходит.
Кроме метода _Html.PartialAsync()_ частичное представление можно встроить с помощью другого метода - _Html.RenderPartialAsync_. Этот метод также принимает имя представления, только он используется не в строчных выражениях кода Razor, а в блоке кода, то есть обрамляется фигурными скобками:
```cshtml
@{
await Html.RenderPartialAsync("Hello");
}
```
_Html.RenderPartialAsync_ напрямую пишет вывод в выходной поток в асинхронном режиме, поэтому может работать чуть быстрее, чем _Html.PartialAsync_.
### Передача данных в частичное представление
Одна из перегруженных версий методов _Html.PartialAsync_ / _Html.RenderPartialAsync_ позволяет передать модель в частичное представление. В итоге у нас получится стандартное строго типизированное представление. Например, в качестве второго параметра список строк:
```cshtml
Welcome
@await Html.PartialAsync("Hello", new List{"Hello", "Привет", "Hallo", "Salut"})
```
Поскольку здесь в частичное представление передается список строк, то мы можем использовать модель `List`, чтобы получить этот список. Теперь изменим частичное представление `Hello.cshtml`:
```cshtml
@model IEnumerable
@foreach(string s in Model)
{
@s
}
```
## Работа с формами
Формы html представляют одну из форм передачи наборов данных на сервер. Обычно для создания форм и их элементов в MVC применяются либо html-хелперы, либо tag-хелперы, которые рассматриваются далее. Однако в данном случае мы рассмотрим взаимодействие на примере стандартных тегов html, которые создают элементы формы.
В одной из прошлых тем было рассмотрено, как MVC связывает отправленные значения формы с параметрами метода. Например, у нас есть действие Index:
```cs
public class HomeController : Controller
{
[HttpGet]
public IActionResult Index() => View();
[HttpPost]
public string Index(string username) => $"User Name: {username}";
}
```
Одно действие разделено на два метода: GET-версию, которая отдает представление с формой ввода, и POST-версию, которая принимает введенные в эту форму данные.
Теперь создадим само представление. Создайье в папке `Views/Home` представление `Index.cshtml` со следующим кодом:
```cshtml
User Name Form
```
Чтобы инфраструктура MVC могла автоматически связать данные из формы с параметрами метода, значения атрибутов `name` у полей формы совпадают с именами параметров.
Таким образом, когда форма отправится на сервер, при обработке запроса фреймворк MVC автоматически свяжет значения полей формы с параметрами:

В HTML мы можем использовать ряд встроенных элементов. Для ввода строк используется элементы `` (для однострочного текста) и `textarea` (для многострочного текста).
Для ввода парольной информации можно применять элемент ``
Для ввода чисел (которые соответствуют типа int, short, byte, long, float, double) используется элемент ``
Например, метод принимает ряд значений:
```cs
public class HomeController : Controller
{
[HttpGet]
public IActionResult Index() => View();
[HttpPost]
public string Index(string username, string password, int age, string comment)
{
return $"User Name: {username} Password: {password} Age: {age} Comment: {comment}";
}
}
```
Тогда форма в представлении могла бы выглядеть следующим образом:
```cshtml
User Form
```
### Элемент checkbox и тип bool
Для передачи логических значений `true` или `false` удобно использовать элемент ``. Например, пусть метод принимает объект типа __bool__:
```cs
public class HomeController : Controller
{
[HttpGet]
public IActionResult Index() => View();
[HttpPost]
public string Index(bool isMarried) => $"isMarried: {isMarried}";
}
```
Тогда мы могли бы определить следующий элемент input:
```cshtml
```
Для связи с параметром метода элемент __input__ называется по имени параметра - __isMarried__. И, кроме того, это поле имеет по умолчанию значение `true`.
### Радиокнопки
Радиокнопки или переключатели позволяют выбрать один из нескольких вариантов. Для их создания используется элемент ``:
```html
```
Все радиокнопки имеют одинаковое имя - __color__ и отличаются только значением, которое хранится в атрибуте `value`. В контроллере мы могли бы получить выбранное значение в виде обычной строки:
```cs
public class HomeController : Controller
{
[HttpGet]
public IActionResult Index() => View();
[HttpPost]
public string Index(string color) => $"color: {color}";
}
```
### Список select
Элемент __select__ предназначен для создания двух видов списков - выпадающих и развернутых.
В выпадающем списке мы можем выбрать один элемент:
```html
```
При этом отображаемый текст элемента списка необязательно должен совпадать с его значением. Например:
```html
```
Здесь текст "C#" - это то, что мы увидим в списке, а "csharp" - это значение, которое при выборе данного элемента отправится на сервер.
Чтобы получить выбранный в этом списке элемент, параметр метода контроллера должен называться также, как и элемент __select__:
```cs
[HttpPost]
public string Index(string language) => $"Language: {language}";
```
Для создания списка со множественным выбором к элементу __select__ добавляется атрибут `multiple`:
```html
```
Но теперь, чтобы получить все выбранные значения в этом списке, метод контроллера должен в качестве параметра принимать массив или список:
```cs
public class HomeController : Controller
{
[HttpGet]
public IActionResult Index() => View();
[HttpPost]
public string Index(string[] languages)
{
string result = "Вы выбрали:";
foreach (string lang in languages)
{
result = $"{result} {lang};";
}
return result;
}
}
```
## Задание на дом
Сделать репозиторий - конспект, то есть создать приложение и в конспекте привести блоки кода и скриншоты для всех примеров из лекции.