Евгений Колесников vor 3 Monaten
Ursprung
Commit
fc3f881c02
5 geänderte Dateien mit 1591 neuen und 191 gelöschten Zeilen
  1. 348 0
      articles/web_cs_01.md
  2. 786 190
      articles/web_cs_02.md
  3. 455 0
      articles/web_cs_03.md
  4. BIN
      img/web_cs_08.png
  5. 2 1
      readme.md

+ 348 - 0
articles/web_cs_01.md

@@ -579,6 +579,354 @@ public string PersonData()
 
 Обратите внимание, у формы определен атрибут `action`, который содержит урл. То есть при нажатии кнопки "Send" запрос будет послан не на метод _Index_, а на урл `/Home/PersonData`, которому соответствует одноименный метод контроллера
 
+## Результаты действий
+
+При обращении к веб-приложению, как правило, пользователь ожидает получить некоторый ответ, например, в виде веб-страницы, которая наполнена данными. На стороне сервера метод контроллера, получая параметры и данные запроса, обрабатывает их и формирует ответ в виде результата действия. Результат действия - это тот объект, который возвращается методом после обработки запроса.
+
+Результатом действия может быть практически что угодно. Например, в прошлых темах использовался объект __string__, например:
+
+```cs
+public string Index()
+{
+    return "Hello METANIT.COM";
+}
+```
+
+Пользователь передает методу некоторые значения и в ответ на запрос видит в своем браузере строку ответа.
+
+Результатом действия может быть какой-нибудь сложный объект:
+
+```cs
+public class HomeController : Controller
+{
+    public Message Index() => new Message("Hello METANIT.COM");
+}
+public record class Message(string Text);
+```
+
+Результатом может быть даже __void__, то есть по сути ничего:
+
+```cs
+public void GetVoid()
+{
+ 
+}
+```
+
+В данном случае метод _GetVoid_ представляет вполне реальное действие контроллера, к которому можно обращаться из адресной строки браузера, передавать параметры, и который может содержать сложную логику обработки запроса. Только после обращения к этому методу пользователь увидит в своем веб-браузере одну пустоту, так как метод ничего не возвращает.
+
+Но в большинстве случаев мы будем иметь дело не с __void__ и даже не с типом __string__, а с объектами типа __IActionResult__, которые непосредственно предназначены для генерации результата действия. Интерфейс __IActionResult__ находится в пространстве имен Microsoft.AspNetCore.Mvc и определяет один метод:
+
+```cs
+public interface IActionResult
+{
+    Task ExecuteResultAsync(ActionContext context);
+}
+```
+
+Метод `ExecuteResultAsync` принимает контекст действия и выполняет генерацию результата.
+
+Этот интерфейс затем реализуется абстрактным базовым классом __ActionResult__:
+
+```cs
+public abstract class ActionResult : IActionResult
+{
+    public virtual Task ExecuteResultAsync(ActionContext context)
+    {
+            ExecuteResult(context);
+            return Task.FromResult(true);
+    }
+ 
+    public virtual void ExecuteResult(ActionContext context)
+    {
+    }
+}
+```
+
+__ActionResult__ добавляет синхронный метод, который выполняется в асинхронном. И если мы вдруг захотим создать свой класс результата действий, то как раз можем либо унаследовать его от ActionResult, либо реализовать интерфейс __IActionResult__.
+
+Итак, создадим свой класс результата действий. Для этого вначале добавим в проект новый класс, который назовем __HtmlResult__
+
+Определим в нем следующий код:
+
+```cs
+public class HtmlResult : IActionResult
+{
+    string htmlCode;
+
+    // конструктор
+    public HtmlResult(string html) => htmlCode = html;
+
+    public async Task ExecuteResultAsync(ActionContext context)
+    {
+        string fullHtmlCode = @$"<!DOCTYPE html>
+        <html>
+            <head>
+                <title>METANIT.COM</title>
+                <meta charset=utf-8 />
+            </head>
+            <body>{htmlCode}</body>
+        </html>";
+        await context.HttpContext.Response.WriteAsync(fullHtmlCode);
+    }
+}
+```
+
+Данный класс будет реализовать интерфейс __IActionResult__. В конструкторе он принимает html-код, который затем будет выводиться на веб-страницу. Для вывода используется асинхронный метод context.HttpContext.Response.WriteAsync()
+
+Теперь используем этот класс в контроллере:
+
+```cs
+public class HomeController : Controller
+{
+    public IActionResult Index()
+    {
+        return new HtmlResult("<h2>Hello METANIT.COM!</h2>");
+    }
+}
+```
+
+Здесь определен метод `Index()`, который возвращает объект __HtmlResult__. При обращении к этому объекту будет срабатывать его метод `ExecuteResultAsync()`, в котором будет происходить генерация html-страницы
+
+Однако в большинстве случаев нам не придется создавать свои классы результатов, потому что фреймворк ASP.NET Core MVC итак предоставляет довольно большую палитру классов результатов для самых различных ситуаций:
+
+- __ContentResult__: отправляет ответ в виде строки
+- __EmptyResult__: отправляет пустой ответ в виде статусного кода 200
+
+    ```cs
+    public IActionResult GetVoid()
+    {
+        return new EmptyResult();
+    }
+    ```
+
+    Аналогичен следующему методу:
+
+    ```cs
+    public void GetVoid()
+    {
+    }
+    ```
+
+- __NoContentResult__: во многом похож на __EmptyResult__, также отправляет пустой ответ, только в виде статусного кода 204
+
+    ```cs
+    public IActionResult GetVoid()
+    {
+        return new NoContentResult();
+    }
+    ```
+
+- __FileResult__: является базовым классом для всех объектов, которые пишут набор байтов в выходной поток. Предназначен для отправки файлов
+- __FileContentResult__: класс, производный от FileResult, пишет в ответ массив байтов
+- __VirtualFileResult__: также производный от FileResult класс, пишет в ответ файл, находящийся по заданному пути
+- __PhysicalFileResult__: также производный от FileResult класс, пишет в ответ файл, находящийся по заданному пути. Только в отличие от предыдущего класса использует физический путь, а не виртуальный.
+- __FileStreamResult__: класс, производный от FileResult, пишет бинарный поток в выходной ответ
+- __ObjectResult__: возвращает произвольный объект, как правило, применяется в качестве базового класса для других классов результатов. Но можно применять и самостоятельно:
+
+    ```cs
+    public class HomeController : Controller
+    {
+        public IActionResult Index()
+        {
+            return new ObjectResult(new Person("Tom", 37));
+        }
+    }
+    record class Person(string Name, int Age);
+    ```
+
+- __StatusCodeResult__: результат действия, который возвращает клиенту определенный статусный код HTTP
+- __UnauthorizedResult__: класс, производный от StatusCodeResult. Возвращает клиенту ответ в виде статусного кода HTTP `401`, указывая, что пользователь не прошел авторизацию и не имеет прав доступа к запрошенному ресурсу.
+- __NotFoundResult__: производный от StatusCodeResult. Возвращает клиенту ответ в виде статусного кода HTTP `404`, указывая, что запрошенный ресурс не найден
+- __NotFoundObjectResult__: производный от ObjectResult. Также возвращает клиенту ответ в виде статусного кода HTTP `404` с дополнительной информацией
+- __BadRequestResult__: производный от StatusCodeResult. Возвращает статусный код `400`, тем самым указывая, что запрос некорректен
+- __BadRequestObjectResult__: производный от ObjectResult. Возвращает статусный код `400` с некоторой дополнительной информацией
+- __OkResult__: производный от StatusCodeResult. Возвращает статусный код `200`, который уведомляет об успешном выполнении запроса
+- __OkObjectResult__: производный от ObjectResult. Возвращает статусный код `200` с некоторой дополнительной информацией
+- __CreatedResult__: возвращает статусный код `201`, который уведомляет о создании нового ресурса. В качестве параметра принимает адрес нового ресурса
+- __ChallengeResult__: используется для проверки аутентификации пользователя
+- __JsonResult__: возвращает в качестве ответа объект или набор объектов в формате JSON
+- __PartialViewResult__: производит рендеринг частичного представления в выходной поток
+- __RedirectResult__: перенаправляет пользователя по другому адресу URL, возвращая статусный код 302 для временной переадресации или код 301 для постоянной переадресации зависимости от того, установлен ли флаг Permanent.
+- __RedirectToRouteResult__: класс работает подобно RedirectResult, но перенаправляет пользователя по определенному адресу URL, указанному через параметры маршрута
+- __RedirectToActionResult__: выполняет переадресацию на определенный метод контроллера
+- __LocalRedirectResult__: перенаправляет пользователя по определенному адресу URL в рамках веб-приложения
+- __ViewComponentResult__: возвращает в ответ сущность ViewComponent
+- __ViewResult__: производит рендеринг представления и отправляет результаты рендеринга в виде html-страницы клиенту
+
+Рассмотрим некоторые из этих классов.
+
+## ContentResult и JsonResult
+
+### ContentResult
+
+ContentResult отправляет клиенту ответ в виде строки. Так, следующий пример:
+
+```cs
+public string Index()
+{
+    return "Hello METANIT.COM";
+}
+```
+
+Можно переписать с использованием ContentResult:
+
+```cs
+public IActionResult Index()
+{
+    return Content("Hello METANIT.COM");
+}
+```
+
+Для отправки ContentResult не надо использовать конструктор, так как в контроллере уже определен специальный метод Content(), который принимает отправляемую строку и создает объект ContentResult.
+
+### JsonResult
+
+Одним из наиболее популярных в наше время форматов хранения и передачи данных является формат JSON (JavaScript Object Notation). JSON не зависит от языка программирования, он более удобен и легче обрабатывается.
+
+В JSON каждый отдельный объект заключается в фигурные скобки и представляет собой набор пар ключ-значение, разделенных запятыми, где ключом является название свойства объекта, а значением соответственно значение этого свойства. Например: {"name":"Tom"}. Здесь "name" является ключом, а "Tom" - значением.
+
+Для отправки объекта в формате json в контроллере имеется метод Json(object obj), который в качестве параметра принимает отправляемый объект. Например:
+
+```cs
+public JsonResult GetName()
+{
+    return Json("Tom");
+}
+```
+
+В данном случае на сторону клиента отправляется строка "Tom".
+
+Допустим, у нас есть следующий класс Person:
+
+```cs
+record class Person(string Name, int Age);
+```
+
+И тогда для отправки клиенту объекта Person мы можем написать следующий метод:
+
+```cs
+public IActionResult Index()
+{
+    Person tom = new Person("Tom", 37);
+    return Json(tom);
+}
+```
+
+При обращении к методу веб-браузер выведет полное описание объекта в формате json
+
+Дополнительная версия метода Json() в качестве второго параметра принимает объект, который задает настройки сериализации в формат json. В качестве такого объекта выступает объект типа JsonSerializerOptions:
+
+```cs
+public class HomeController : Controller
+{
+    public IActionResult Index()
+    {
+        var tom = new Person("Tom", 37);
+        var jsonOptions = new System.Text.Json.JsonSerializerOptions 
+        { 
+            PropertyNameCaseInsensitive = true, // не учитываем регистр
+            WriteIndented = true                // отступы для красоты
+        };
+        return Json(tom, jsonOptions);
+    }
+}
+record class Person(string Name, int Age);
+```
+
+В данном случае объект JsonSerializerOptions задает с помощью свойств параметры сериализации в json. В частности, значение свойства `PropertyNameCaseInsensitive = true` говорит, что регистр названий свойств не учитывается. А свойство `WriteIndented = true` задает установку отступов перед свойствами для более человекопонятного вывода
+
+## Отправка статусных кодов
+
+Нередко возникает необходимость отправить в ответ на запрос какой-либо статусный код. Например, если пользователь пытается получить доступ к ресурсу, который недоступен, или для которого у пользователя нету прав. Либо если просто нужно уведомить браузер пользователя с помощью статусного кода об успешном выполнении операции, как иногда применяется в ajax-запросах. Для этого в ASP.NET Core MVC определена богатая палитра классов, которые можно использовать для отправки статусного кода.
+
+### StatusCodeResult
+
+StatusCodeResult позволяет отправить любой статусный код клиенту:
+
+```cs
+public IActionResult Index()
+{
+    return StatusCode(401);
+}
+```
+
+Для создания этого результата используется метод StatusCode(), в который передается отправляемый код статуса.
+
+Подобным образом мы можем послать браузеру любой другой статусный код. Но для отдельных кодов статуса предназначены свои отдельные классы.
+
+### HttpNotFoundResult и HttpNotFoundObjectResult
+
+NotFoundResult и NotFoundObjectResult посылает код `404`, уведомляя браузер о том, что ресурс не найден. Второй класс в дополнении к статусному коду позволяет отправить доплнительную информацию, которая потом отобразится в браузере.
+
+Объекты обоих классов создаются методом NotFound. Для первого класса - это метод без параметров, для второго класса - метод, который в качестве параметра принимает отправляемую информацию. Например, используем NotFoundObjectResult:
+
+```cs
+public IActionResult Index()
+{
+    return NotFound("Resource not found");
+}
+```
+
+### UnauthorizedResult и UnauthorizedObjectResult
+
+UnauthorizedResult посылает код `401`, уведомляя пользователя, что он не автризован для доступа к ресурсу:
+
+```cs
+public IActionResult Index(int age)
+{
+    if (age < 18) return Unauthorized();
+    return Content("Access is available");
+}
+```
+
+Для создания ответа используется метод Unauthorized().
+
+UnauthorizedObjectResult также посылает код `401`, только позволяет добавить в ответ некоторый объект с информацией об ошибке:
+
+```cs
+public class HomeController : Controller
+{
+    public IActionResult Index(int age)
+    {
+        if (age < 18) return Unauthorized(new Error("Access is denied"));
+        return Content("Access is available");
+    }
+}
+record class Error(string Message);
+```
+
+### BadResult и BadObjectResult
+
+BadResult и BadObjectResult посылают код `400`, что говорит о том, что запрос некорректный. Второй класс в дополнении к статусному коду позволяет отправить доплнительную информацию, которая потом отобразится в браузере.
+
+Эти классы можно применять, например, если в запросе нет каких-то параметров или данные представляют совсем не те типы, которые мы ожидаем получить, и т.д.
+
+Объекты обоих классов создаются методом BadRequest. Для первого класса - это метод без параметров, для второго класса - метод, который в качестве параметра принимает отправляемую информацию:
+
+```cs
+public IActionResult Index(string? name)
+{
+    if (string.IsNullOrEmpty(name)) return BadRequest("Name undefined");
+    return Content($"Name: {name}");
+}
+```
+
+### OkResult и OkObjectResult
+
+OkResult и OkObjectResult посылают код `200`, уведомляя об успешном выполнении запроса. Второй класс в дополнении к статусному коду позволяет отправить дополнительную информацию, которая потом отправляется клиенту в формате json.
+
+Объекты обоих классов создаются методом Ok(). Для первого класса - это метод без параметров, для второго класса - метод, который в качестве параметра принимает отправляемую информацию:
+
+```cs
+public IActionResult Index()
+{
+    return Ok("Don't worry. Be happy");
+}
+```
+
+---
+
 ## Задание на дом
 
 Сделать репозиторий - конспект, то есть создать приложение и в конспекте привести блоки кода и скриншоты для всех примеров из лекции.

+ 786 - 190
articles/web_cs_02.md

@@ -1,347 +1,943 @@
-# MVC. Контроллер: результат действий
+# Представления
 
-## Результаты действий
+## Введение в представления
 
-При обращении к веб-приложению, как правило, пользователь ожидает получить некоторый ответ, например, в виде веб-страницы, которая наполнена данными. На стороне сервера метод контроллера, получая параметры и данные запроса, обрабатывает их и формирует ответ в виде результата действия. Результат действия - это тот объект, который возвращается методом после обработки запроса.
+Обычно при обращении к веб-приложению пользователь ожидает получить веб-страницу с какими-нибудь данными. В MVC для этого, как правило, используются __представления__, которые определяют внешний вид приложения и на основе которых потом формируется веб-страница. В ASP.NET MVC Core представления - это файлы с расширением `cshtml`, которые содержат код пользовательского интерфейса в основном на языке html, а также конструкции Razor - специального движка представлений, который позволяет переходить от кода html к коду на языке C#.
 
-Результатом действия может быть практически что угодно. Например, в прошлых темах использовался объект __string__, например:
+Для хранения представлений в проекте 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 string Index()
+public class HomeController : Controller
 {
-    return "Hello METANIT.COM";
+    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 class HomeController : Controller
+public IActionResult Index()
 {
-    public Message Index() => new Message("Hello METANIT.COM");
+    return View("About");
 }
-public record class Message(string Text);
 ```
 
-Результатом может быть даже __void__, то есть по сути ничего:
+В этом случае метод _Index_ будет использовать представление по пути `Views/Home/About.cshtml`. Если же представление находится в другой папке, то нам надо передать полный путь к представлению:
 
 ```cs
-public void GetVoid()
+public IActionResult Index()
 {
- 
+    return View("~/Views/Some/About.cshtml");
 }
 ```
 
-В данном случае метод _GetVoid_ представляет вполне реальное действие контроллера, к которому можно обращаться из адресной строки браузера, передавать параметры, и который может содержать сложную логику обработки запроса. Только после обращения к этому методу пользователь увидит в своем веб-браузере одну пустоту, так как метод ничего не возвращает.
+В данном случае предполагается, что представление располагается по пути `Views/Some/About.cshtml`
 
о в большинстве случаев мы будем иметь дело не с __void__ и даже не с типом __string__, а с объектами типа __IActionResult__, которые непосредственно предназначены для генерации результата действия. Интерфейс __IActionResult__ находится в пространстве имен Microsoft.AspNetCore.Mvc и определяет один метод:
апомню как выглядит представление `Index.cshtml`:
 
-```cs
-public interface IActionResult
-{
-    Task ExecuteResultAsync(ActionContext context);
+```html
+@{ViewData["Title"] = "Заголовок нашего MVC приложения";}
+
+<div class="text-center">
+    <h1 class="display-4">Привет ЙОТК!</h1>
+</div>
+```
+
+А где же теги `<head>` и `<body>`? Внимательно перечитываем текущий параграф и видим, что есть еще какая-то мастер-страница `_ViewStart.cshtml` - посмотрим что в ней:
+
+```cshtml
+@{
+    Layout = "_Layout";
 }
 ```
 
-Метод `ExecuteResultAsync` принимает контекст действия и выполняет генерацию результата.
+Блок кода `@{}` содержит вставку кода C#, в которой задается `Layout`. `_Layout` это название файла, который лежит в каталоге `Shared` (не привязан к какому-то контроллеру, так как используется во всех контроллерах приложения) и содержит шаблон для верстки:
+
+```html
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>@ViewData["Title"] - asp_net_mvc</title>
+    <script type="importmap"></script>
+    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
+    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
+    <link rel="stylesheet" href="~/asp_net_mvc.styles.css" asp-append-version="true" />
+</head>
+<body>
+    <header>
+        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
+            <div class="container-fluid">
+                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">asp_net_mvc</a>
+                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
+                        aria-expanded="false" aria-label="Toggle navigation">
+                    <span class="navbar-toggler-icon"></span>
+                </button>
+                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
+                    <ul class="navbar-nav flex-grow-1">
+                        <li class="nav-item">
+                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
+                        </li>
+                        <li class="nav-item">
+                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
+                        </li>
+                    </ul>
+                </div>
+            </div>
+        </nav>
+    </header>
+    <div class="container">
+        <main role="main" class="pb-3">
+            <!-- метод RenderBody вернет текстовое представление текущего представления -->
+            @RenderBody()
+        </main>
+    </div>
+
+    <footer class="border-top footer text-muted">
+        <div class="container">
+            &copy; 2025 - asp_net_mvc - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
+        </div>
+    </footer>
+    <script src="~/lib/jquery/dist/jquery.min.js"></script>
+    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
+    <script src="~/js/site.js" asp-append-version="true"></script>
+    @await RenderSectionAsync("Scripts", required: false)
+</body>
+</html>
+```
 
-Этот интерфейс затем реализуется абстрактным базовым классом __ActionResult__:
+Это уже похоже на нормальный html. Содержимое представления вставляется вместо вызова `@RenderBody()`.
 
-```cs
-public abstract class ActionResult : IActionResult
-{
-    public virtual Task ExecuteResultAsync(ActionContext context)
-    {
-            ExecuteResult(context);
-            return Task.FromResult(true);
-    }
- 
-    public virtual void ExecuteResult(ActionContext context)
-    {
-    }
-}
+## Движок представлений 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)
+<title>@ViewData["Title"] - asp_net_mvc</title>
+
+а тут вызывается функция RenderBody
+<main role="main" class="pb-3">
+    @RenderBody()
+</main>
 ```
 
-__ActionResult__ добавляет синхронный метод, который выполняется в асинхронном. И если мы вдруг захотим создать свой класс результата действий, то как раз можем либо унаследовать его от ActionResult, либо реализовать интерфейс __IActionResult__.
+## Типы конструкций Razor
 
-Итак, создадим свой класс результата действий. Для этого вначале добавим в проект новый класс, который назовем __HtmlResult__
+Все конструкции Razor можно условно разделить на два вида: однострочные выражения и блоки кода.
 
-Определим в нем следующий код:
+Пример применения однострочных выражений:
 
-```cs
-public class HtmlResult : IActionResult
-{
-    string htmlCode;
+```
+<p>Date: @DateTime.Now.ToLongDateString()</p>
+```
 
-    // конструктор
-    public HtmlResult(string html) => htmlCode = html;
+В данном случае используется объект __DateTime__ и его метод _ToLongDateString()_
 
-    public async Task ExecuteResultAsync(ActionContext context)
-    {
-        string fullHtmlCode = @$"<!DOCTYPE html>
-        <html>
-            <head>
-                <title>METANIT.COM</title>
-                <meta charset=utf-8 />
-            </head>
-            <body>{htmlCode}</body>
-        </html>";
-        await context.HttpContext.Response.WriteAsync(fullHtmlCode);
-    }
+Или еще один пример:
+
+```
+<p>@(20 + 30)</p>
+```
+
+Так как перед скобками стоит знак `@`, то выражение в скобках будет интерпретироваться как выражение на языке C#. Поэтому браузер выведет число `50`, а не строку "20 + 30".
+
+Но если вдруг мы создаем код html, в котором присутствует символ `@` не как часть синтаксиса Razor, а сам по себе, то, чтобы его отобразить, нам надо его дублировать:
+
+```
+<p>@@DateTime.Now = @DateTime.Now.ToLongDateString()</p>
+```
+
+Блоки кода могут иметь несколько выражений. Блок кода заключается в фигурные скобки, а каждое выражение завершается точкой с запятой аналогично блокам кода и выражениям на C#:
+
+```
+@{
+    ViewData["Title"] = "Home Page";
 }
 ```
 
-Данный класс будет реализовать интерфейс __IActionResult__. В конструкторе он принимает html-код, который затем будет выводиться на веб-страницу. Для вывода используется асинхронный метод context.HttpContext.Response.WriteAsync()
+В блоках кода мы можем определить обычные переменные и потом их использовать в представлении.
 
-Теперь используем этот класс в контроллере:
+В Razor могут использоваться комментарии. Они располагаются между символами `@*  *@`:
 
-```cs
-public class HomeController : Controller
+```
+@* текст комментария *@
+```
+
+### Условные конструкции
+
+Также мы можем использовать условные конструкции:
+
+```
+@{
+    string morning = "Good Morning";
+    string evening = "Good Evening";
+    string hello = "Hello";
+    int hour = DateTime.Now.Hour;
+}
+@if (hour < 12)
 {
-    public IActionResult Index()
-    {
-        return new HtmlResult("<h2>Hello METANIT.COM!</h2>");
-    }
+    <h2>@morning</h2>
+}
+else if (hour > 17)
+{
+    <h2>@evening</h2>
+}
+else
+{
+    <h2>@hello</h2>
 }
 ```
 
-Здесь определен метод `Index()`, который возвращает объект __HtmlResult__. При обращении к этому объекту будет срабатывать его метод `ExecuteResultAsync()`, в котором будет происходить генерация html-страницы
+Конструкция switch:
+
+```
+@{
+    string language = "german";
+}
+@switch(language)
+{
+    case "russian":
+        <h3>Привет мир!</h3>
+        break;
+    case "german":
+        <h3>Hallo Welt!</h3>
+        break;
+    default:
+        <h3>Hello World!</h3>
+        break;
+}
+```
 
-Однако в большинстве случаев нам не придется создавать свои классы результатов, потому что фреймворк ASP.NET Core MVC итак предоставляет довольно большую палитру классов результатов для самых различных ситуаций:
+### Циклы
 
-- __ContentResult__: отправляет ответ в виде строки
-- __EmptyResult__: отправляет пустой ответ в виде статусного кода 200
+Кроме того, мы можем использовать все возможные циклы. Цикл __for__:
 
-    ```cs
-    public IActionResult GetVoid()
+```
+@{
+    string[] people = { "Tom", "Sam", "Bob" };
+}
+<ul>
+    @for (var i = 0; i < people.Length; i++)
     {
-        return new EmptyResult();
+        <li>@people[i]</li>
     }
-    ```
+</ul>
+```
 
-    Аналогичен следующему методу:
+Цикл __foreach__:
 
-    ```cs
-    public void GetVoid()
+```
+@{
+    string[] people = { "Tom", "Sam", "Bob" };
+}
+<ul>
+    @foreach (var person in people)
     {
+        <li>@person</li>
     }
-    ```
+</ul>
+```
 
-- __NoContentResult__: во многом похож на __EmptyResult__, также отправляет пустой ответ, только в виде статусного кода 204
+Цикл __while__:
 
-    ```cs
-    public IActionResult GetVoid()
+```
+@{
+    string[] people = { "Tom", "Sam", "Bob" };
+    var i = 0;
+}
+<ul>
+    @while ( i < people.Length)
     {
-        return new NoContentResult();
+        <li>@people[i++]</li>
     }
-    ```
+</ul>
+```
 
-- __FileResult__: является базовым классом для всех объектов, которые пишут набор байтов в выходной поток. Предназначен для отправки файлов
-- __FileContentResult__: класс, производный от FileResult, пишет в ответ массив байтов
-- __VirtualFileResult__: также производный от FileResult класс, пишет в ответ файл, находящийся по заданному пути
-- __PhysicalFileResult__: также производный от FileResult класс, пишет в ответ файл, находящийся по заданному пути. Только в отличие от предыдущего класса использует физический путь, а не виртуальный.
-- __FileStreamResult__: класс, производный от FileResult, пишет бинарный поток в выходной ответ
-- __ObjectResult__: возвращает произвольный объект, как правило, применяется в качестве базового класса для других классов результатов. Но можно применять и самостоятельно:
+Цикл __do..while__:
 
-    ```cs
-    public class HomeController : Controller
+```
+@{
+    var i = 0;
+}
+<ul>
+    @do
     {
-        public IActionResult Index()
-        {
-            return new ObjectResult(new Person("Tom", 37));
-        }
+        <li>@(i * i)</li>
     }
-    record class Person(string Name, int Age);
-    ```
-
-- __StatusCodeResult__: результат действия, который возвращает клиенту определенный статусный код HTTP
-- __UnauthorizedResult__: класс, производный от StatusCodeResult. Возвращает клиенту ответ в виде статусного кода HTTP `401`, указывая, что пользователь не прошел авторизацию и не имеет прав доступа к запрошенному ресурсу.
-- __NotFoundResult__: производный от StatusCodeResult. Возвращает клиенту ответ в виде статусного кода HTTP `404`, указывая, что запрошенный ресурс не найден
-- __NotFoundObjectResult__: производный от ObjectResult. Также возвращает клиенту ответ в виде статусного кода HTTP `404` с дополнительной информацией
-- __BadRequestResult__: производный от StatusCodeResult. Возвращает статусный код `400`, тем самым указывая, что запрос некорректен
-- __BadRequestObjectResult__: производный от ObjectResult. Возвращает статусный код `400` с некоторой дополнительной информацией
-- __OkResult__: производный от StatusCodeResult. Возвращает статусный код `200`, который уведомляет об успешном выполнении запроса
-- __OkObjectResult__: производный от ObjectResult. Возвращает статусный код `200` с некоторой дополнительной информацией
-- __CreatedResult__: возвращает статусный код `201`, который уведомляет о создании нового ресурса. В качестве параметра принимает адрес нового ресурса
-- __ChallengeResult__: используется для проверки аутентификации пользователя
-- __JsonResult__: возвращает в качестве ответа объект или набор объектов в формате JSON
-- __PartialViewResult__: производит рендеринг частичного представления в выходной поток
-- __RedirectResult__: перенаправляет пользователя по другому адресу URL, возвращая статусный код 302 для временной переадресации или код 301 для постоянной переадресации зависимости от того, установлен ли флаг Permanent.
-- __RedirectToRouteResult__: класс работает подобно RedirectResult, но перенаправляет пользователя по определенному адресу URL, указанному через параметры маршрута
-- __RedirectToActionResult__: выполняет переадресацию на определенный метод контроллера
-- __LocalRedirectResult__: перенаправляет пользователя по определенному адресу URL в рамках веб-приложения
-- __ViewComponentResult__: возвращает в ответ сущность ViewComponent
-- __ViewResult__: производит рендеринг представления и отправляет результаты рендеринга в виде html-страницы клиенту
-
-Рассмотрим некоторые из этих классов.
-
-## ContentResult и JsonResult
-
-### ContentResult
-
-ContentResult отправляет клиенту ответ в виде строки. Так, следующий пример:
+    while ( i++ < 5);
+</ul>
+```
 
-```cs
-public string Index()
+### try...catch
+
+Конструкция __try...catch...finally__, как и в C#, позволяет обработать исключение, которое может возникнуть при выполнение кода:
+
+```
+@try
+{
+    throw new InvalidOperationException("Something wrong");
+}
+catch (Exception ex)
+{
+    <p>Exception: @ex.Message</p>
+}
+finally
+{
+    <p>finally</p>
+}
+```
+
+Если в блоке __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
 {
-    return "Hello METANIT.COM";
+    public int Sum(int a, int b) 
+    {
+        return a + b;
+    }
+    public int Square(int n) => n * n;
 }
+<p>Sum of 5 and 4: <b> @Sum(5, 4)</b></p>
+<p>Square of 4: <b>@Square(4)</b></p>
 ```
 
-Можно переписать с использованием ContentResult:
+## Передача данных в представление
+
+Существуют различные способы передачи данных из контроллера в представление:
+
+- ViewData
+- ViewBag
+- Модель представления
+
+### ViewData
+
+ViewData представляет словарь из пар ключ-значение:
 
 ```cs
 public IActionResult Index()
 {
-    return Content("Hello METANIT.COM");
+    ViewData["Message"] = "Hello METANIT.COM";
+    return View();
 }
 ```
 
-Для отправки ContentResult не надо использовать конструктор, так как в контроллере уже определен специальный метод Content(), который принимает отправляемую строку и создает объект ContentResult.
+Здесь динамически определяется во ViewData объект с ключом "Message" и значением "Hello METANIT.COM". При этом в качестве значения может выступать любой объект. И после этому мы можем его использовать в представлении:
 
-### JsonResult
+```
+@{
+    ViewData["Title"] = "ASP.NET Core MVC";
+}
+ 
+<h2>@ViewData["Title"]</h2>
+<h3>@ViewData["Message"]</h3>
+```
 
-Одним из наиболее популярных в наше время форматов хранения и передачи данных является формат JSON (JavaScript Object Notation). JSON не зависит от языка программирования, он более удобен и легче обрабатывается.
+Причем не обязательно устанавливать все объекты во ViewData в контроллере. Так, в данном случае объект с ключом "Title" устанавливается непосредственно в представлении.
 
-В JSON каждый отдельный объект заключается в фигурные скобки и представляет собой набор пар ключ-значение, разделенных запятыми, где ключом является название свойства объекта, а значением соответственно значение этого свойства. Например: {"name":"Tom"}. Здесь "name" является ключом, а "Tom" - значением.
+### ViewBag
 
-Для отправки объекта в формате json в контроллере имеется метод Json(object obj), который в качестве параметра принимает отправляемый объект. Например:
+ViewBag во многом подобен ViewData. Он позволяет определить различные свойства и присвоить им любое значение. Так, мы могли бы переписать предыдущий пример следующим образом:
 
 ```cs
-public JsonResult GetName()
+public IActionResult Index()
 {
-    return Json("Tom");
+    ViewBag.Message = "Hello METANIT.COM";
+ 
+    return View();
 }
 ```
 
-В данном случае на сторону клиента отправляется строка "Tom".
+И таким же образом нам надо было бы изменить представление:
 
-Допустим, у нас есть следующий класс Person:
+```cshtml
+@{
+    ViewBag.Title = "ASP.NET Core MVC";
+}
+<h2>@ViewBag.Title.</h2>
+<h3>@ViewBag.Message</h3>
+```
+
+И не важно, что изначально объект ViewBag не содержит никакого свойства Message, оно определяется динамически. При этом свойства ViewBag могут содержать не только простые объекты типа string или int, но и сложные данные. Например, передадим список:
 
 ```cs
-record class Person(string Name, int Age);
+public IActionResult Index()
+{
+    ViewBag.People = new List<string> { "Tom", "Sam", "Bob" };
+    return View();
+}
 ```
 
-И тогда для отправки клиенту объекта Person мы можем написать следующий метод:
+И также в представлении мы можем получить этот список:
+
+```cshtml
+<h2>Count: @ViewBag.People.Count</h2>
+<ul>
+@foreach(string person in ViewBag.People)
+{
+    <li>@person</li>
+}
+</ul>
+```
+
+### Модель представления
+
+Модель представления является во многих случаях более предпочтительным способом для передачи данных в представление. Для передачи данных в представление используется одна из версий метода View:
 
 ```cs
 public IActionResult Index()
 {
+    var people = new List<string> { "Tom", "Sam", "Bob" };
+    return View(people);
+}
+```
+
+В метод View передается список, поэтому моделью представления Index.cshtml будет тип List<string> (либо IEnumerable<string>). И теперь в представлении мы можем написать так:
+
+```cshtml
+@model List<string>
+ 
+<h2>Count: @Model.Count</h2>
+<ul>
+@foreach(string person in Model)
+{
+    <li>@person</li>
+}
+</ul>
+```
+
+В самом начале представления с помощью директивы `@model` устанавливается модель представления. Тип модели должен совпадать с типом объекта, который передается в метод View() в контроллере.
+
+Установка модели указывает, что объект __Model__ теперь будет представлять объект `List<string>` или список. И мы сможем использовать Model в качестве списка.
+
+## Мастер-страницы (layout)
+
+Мастер-страницы или layout позволяют задать единый шаблон для представлений и применяются для создания единообразного, унифицированного вида сайта. По сути мастер-страницы - это те же самые представления, которе могут включать в себя другие представления. Например, можно определить на мастер-странице общие для всех остальных представлений меню, а также подключить общие стили и скрипты. В итоге нам не придется на каждом отдельном представлении прописывать путь к файлам стилей, а потом при необходимости его изменять. А специальные теги позволяют вставлять в определенное место на мастер-страницах другие представления.
+
+Код мастер-страницы напоминает полноценную веб-страницу: здесь присутствуют основные теги `<html>`, `<head>`, `<body>` и так далее. И также здесь могут использоваться конструкции 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";
+}
+<h2>Представление Index.cshtml</h2>
+```
+
+Мы можем вообще не использовать мастер-страницу, тогда в представлении свойству _Layout_ надо присвоить значение `null`:
+
+```cshtml
+@{
+    Layout = null;
+}
+ 
+<!DOCTYPE html>
+<html>
+<head>
+    <meta name="viewport" content="width=device-width" />
+    <title>Home Page</title>
+</head>
+<body>
+    <h2>Представление Index.cshtml</h2>
+</body>
+</html>
+```
+
+## Секции
+
+Кроме метода _RenderBody()_, который вставляет основное содержимое представлений, мастер-страниц может также использовать специальный метод _RenderSection()_ для вставки секций. Мастер-страница может иметь несколько секций, куда представления могут поместить свое содержимое. Например, добавим к мастер-странице `_Layout.cshtml` секцию с именем "footer":
+
+```html
+<!DOCTYPE html>
+<html>
+<head>
+    <meta name="viewport" content="width=device-width" />
+    <title>METANIT.COM | @ViewBag.Title</title>
+</head>
+<body>
+    <h2>@ViewBag.Title</h2>
+    <div><a href="/Home/Index">Home</a> | <a href="/Home/About">About</a></div>
+    <div>
+        @RenderBody()
+    </div>
+    <footer>@RenderSection("Footer")</footer>
+</body>
+</html>
+```
+
+Теперь при запуске предыдущего представления __Index__ мы получим ошибку, так как секция __Footer__ не определена. По умолчанию представление должно передавать содержание для каждой секции мастер-страницы. Поэтому добавим вниз представления __Index__ секцию __footer__. Это мы можем сделать с помощью выражения `@section`:
+
+```
+@{
+    ViewBag.Title = "Index";
+}
+</h3>Index Content</h3>
+ 
+@section Footer {
+   Copyright© Metanit.com, @DateTime.Now.Year. All rights reserved
+}
+```
+
+Но при таком подходе, если у нас есть куча представлений, и мы вдруг захотели определить новую секцию на мастер-странице, нам придется изменить все имеющиеся представления, что не очень удобно. В этом случае мы можем воспользоваться одним из вариантов гибкой настройки секций.
+
+Первый вариант заключается в использовании перегруженной версии метода _RenderSection_, которая позволяет указать, что данную секцию не обязательно определять в представлении. Чтобы отметить секцию __Footer__ в качестве необязательной, надо передать в метод в качестве второго параметра значение `false`:
+
+```cshtml
+<footer>@RenderSection("Footer", false)</footer>
+```
+
+Второй вариант позволяет задать содержание секции по умолчанию, если данная секция не определена в представлении:
+
+```cshtml
+<footer>
+    @if (IsSectionDefined("Footer"))
+    {
+        @RenderSection("Footer")
+    }
+    else
+    {
+        <span>Содержание элемента footer по умолчанию.</span>
+    }
+</footer>
+```
+
+## Файл _ViewImports.cshtml
+
+Файл `_ViewImports.cshtml` позволяет по умолчанию подключить в представления некоторый функционал. Сначала рассмотрим ситуацию, с которой мы можем столкнуться. Пусть у нас в проекте есть некоторый класс __Person__:
+
+```cs
+namespace MvcApp
+{
+    public record class Person(string Name, int Age);
+}
+```
+
+Допустим, мы хотим использовать тип __Person__ в представлении `Index.cshtml`:
+
+```cshtml
+@using MvcApp @* Подключаем пространство имен класса Person *@
+@{
+    Layout = null;
     Person tom = new Person("Tom", 37);
-    return Json(tom);
 }
+<h2>Person Data</h2>
+<h3>Name: @tom.Name</h3>
+<h3>Age: @tom.Age</h3>
 ```
 
-При обращении к методу веб-браузер выведет полное описание объекта в формате json
+Чтобы использовать тип __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-запроса. По своему действию частичные представления похожи на секции, которые использовались в прошлой теме, только их код выносится в отдельные файлы.
 
-Дополнительная версия метода Json() в качестве второго параметра принимает объект, который задает настройки сериализации в формат json. В качестве такого объекта выступает объект типа JsonSerializerOptions:
+Частичные представления полезны для создания различных панелей веб-страницы, например, панели меню, блока входа на сайт, каких-то других блоков.
+
+За рендеринг частичных представлений отвечает объект __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()
     {
-        var tom = new Person("Tom", 37);
-        var jsonOptions = new System.Text.Json.JsonSerializerOptions 
-        { 
-            PropertyNameCaseInsensitive = true, // не учитываем регистр
-            WriteIndented = true                // отступы для красоты
-        };
-        return Json(tom, jsonOptions);
+        return View();
+    }
+    public IActionResult Hello()
+    {
+        return PartialView();
     }
 }
-record class Person(string Name, int Age);
 ```
 
-В данном случае объект JsonSerializerOptions задает с помощью свойств параметры сериализации в json. В частности, значение свойства `PropertyNameCaseInsensitive = true` говорит, что регистр названий свойств не учитывается. А свойство `WriteIndented = true` задает установку отступов перед свойствами для более человекопонятного вывода
+Теперь добавим в папку `Views/Home` новое представление `Hello.cshtml`, в котором будет простенькое содержимое:
 
-## Отправка статусных кодов
+```html
+<h2>Hello View</h2>
+```
 
-Нередко возникает необходимость отправить в ответ на запрос какой-либо статусный код. Например, если пользователь пытается получить доступ к ресурсу, который недоступен, или для которого у пользователя нету прав. Либо если просто нужно уведомить браузер пользователя с помощью статусного кода об успешном выполнении операции, как иногда применяется в ajax-запросах. Для этого в ASP.NET Core MVC определена богатая палитра классов, которые можно использовать для отправки статусного кода.
+Теперь обратимся к методу Hello как к обычному действию контроллера, и оно нам вернет частичное представление
 
-### StatusCodeResult
+По своему действию частичное представление похоже на обычное, только для него по умолчанию не определяется мастер-страница (то есть в ответе на запрос не будет ни заголовков ни шаблона, а только тот html, который есть в частичном представлении).
 
-StatusCodeResult позволяет отправить любой статусный код клиенту:
+### Встраивание частичного представления в обычное
 
-```cs
-public IActionResult Index()
-{
-    return StatusCode(401);
+Теперь рассмотрим, как мы можем встраивать частичные представления в обычные. Для этого изменим представление `Index.cshtml`:
+
+```cshtml
+<h1>Index View</h1>
+ 
+@await Html.PartialAsync("Hello")
+```
+
+Метод _Html.PartialAsync()_ встраивает код частичного представления в обычное. Он является асинхронным и возвращает объект __IHtmlContent__, который представляет html-содержимое и который обернут в объект `Task<TResult>`.
+
+Обращения к методу _Hello()_ в контроллере при этом не происходит.
+
+Кроме метода _Html.PartialAsync()_ частичное представление можно встроить с помощью другого метода - _Html.RenderPartialAsync_. Этот метод также принимает имя представления, только он используется не в строчных выражениях кода Razor, а в блоке кода, то есть обрамляется фигурными скобками:
+
+```cshtml
+@{
+    await Html.RenderPartialAsync("Hello");
 }
 ```
 
-Для создания этого результата используется метод StatusCode(), в который передается отправляемый код статуса.
+_Html.RenderPartialAsync_ напрямую пишет вывод в выходной поток в асинхронном режиме, поэтому может работать чуть быстрее, чем _Html.PartialAsync_.
+
+### Передача данных в частичное представление
+
+Одна из перегруженных версий методов _Html.PartialAsync_ / _Html.RenderPartialAsync_ позволяет передать модель в частичное представление. В итоге у нас получится стандартное строго типизированное представление. Например, в качестве второго параметра список строк:
+
+```cshtml
+<h1>Welcome</h1>
+ 
+@await Html.PartialAsync("Hello", new List<string>{"Hello", "Привет", "Hallo", "Salut"})
+```
+
+Поскольку здесь в частичное представление передается список строк, то мы можем использовать модель `List<string>`, чтобы получить этот список. Теперь изменим частичное представление `Hello.cshtml`:
 
-Подобным образом мы можем послать браузеру любой другой статусный код. Но для отдельных кодов статуса предназначены свои отдельные классы.
+```cshtml
+@model IEnumerable<string>
+<ul>
+    @foreach(string s in Model)
+    {
+        <li>@s</li>
+    }
+</ul>
+```
 
-### HttpNotFoundResult и HttpNotFoundObjectResult
+## Работа с формами
 
-NotFoundResult и NotFoundObjectResult посылает код `404`, уведомляя браузер о том, что ресурс не найден. Второй класс в дополнении к статусному коду позволяет отправить доплнительную информацию, которая потом отобразится в браузере.
+Формы html представляют одну из форм передачи наборов данных на сервер. Обычно для создания форм и их элементов в MVC применяются либо html-хелперы, либо tag-хелперы, которые рассматриваются далее. Однако в данном случае мы рассмотрим взаимодействие на примере стандартных тегов html, которые создают элементы формы.
 
-Объекты обоих классов создаются методом NotFound. Для первого класса - это метод без параметров, для второго класса - метод, который в качестве параметра принимает отправляемую информацию. Например, используем NotFoundObjectResult:
+В одной из прошлых тем было рассмотрено, как MVC связывает отправленные значения формы с параметрами метода. Например, у нас есть действие Index:
 
 ```cs
-public IActionResult Index()
+public class HomeController : Controller
 {
-    return NotFound("Resource not found");
+    [HttpGet]
+    public IActionResult Index() => View();
+
+    [HttpPost]
+    public string Index(string username) => $"User Name: {username}";
 }
 ```
 
-### UnauthorizedResult и UnauthorizedObjectResult
+Одно действие разделено на два метода: GET-версию, которая отдает представление с формой ввода, и POST-версию, которая принимает введенные в эту форму данные.
+
+Теперь создадим само представление. Создайье в папке `Views/Home` представление `Index.cshtml` со следующим кодом:
+
+```cshtml
+<h2>User Name Form</h2>
+<form method="post">
+    <label>User Name:</label><br />
+    <input type="text" name="username" /><br />
+    <input type="submit" value="Send" />
+</form>
+```
+
+Чтобы инфраструктура MVC могла автоматически связать данные из формы с параметрами метода, значения атрибутов `name` у полей формы совпадают с именами параметров.
+
+Таким образом, когда форма отправится на сервер, при обработке запроса фреймворк MVC автоматически свяжет значения полей формы с параметрами:
+
+![](../img/web_cs_08.png)
+
+В HTML мы можем использовать ряд встроенных элементов. Для ввода строк используется элементы `<input type="text" />` (для однострочного текста) и `textarea` (для многострочного текста).
+
+Для ввода парольной информации можно применять элемент `<input type="password" />`
 
-UnauthorizedResult посылает код `401`, уведомляя пользователя, что он не автризован для доступа к ресурсу:
+Для ввода чисел (которые соответствуют типа int, short, byte, long, float, double) используется элемент `<input type="number" />`
+
+Например, метод принимает ряд значений:
 
 ```cs
-public IActionResult Index(int age)
+public class HomeController : Controller
 {
-    if (age < 18) return Unauthorized();
-    return Content("Access is available");
+    [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}";
+    }
 }
 ```
 
-Для создания ответа используется метод Unauthorized().
+Тогда форма в представлении могла бы выглядеть следующим образом:
+
+```cshtml
+<h2>User Form</h2>
+<form method="post">
+    <p>
+        <label>User Name:</label><br />
+        <input type="text" name="username" />
+    </p>
+    <p>
+        <label>Password:</label><br />
+        <input type="password" name="password" />
+    </p>
+    <p>
+        <label>Age:</label><br />
+        <input type="number" name="age" />
+    </p>
+    <p>
+        <label>Comment:</label><br />
+        <textarea name="comment"></textarea>
+    </p>
+    <p>
+        <input type="submit" value="Send" />
+    </p>
+</form>
+```
+
+### Элемент checkbox и тип bool
 
-UnauthorizedObjectResult также посылает код `401`, только позволяет добавить в ответ некоторый объект с информацией об ошибке:
+Для передачи логических значений `true` или `false` удобно использовать элемент `<input type="checkbox" />`. Например, пусть метод принимает объект типа __bool__:
 
 ```cs
 public class HomeController : Controller
 {
-    public IActionResult Index(int age)
-    {
-        if (age < 18) return Unauthorized(new Error("Access is denied"));
-        return Content("Access is available");
-    }
+    [HttpGet]
+    public IActionResult Index() => View();
+
+    [HttpPost]
+    public string Index(bool isMarried) => $"isMarried: {isMarried}";
 }
-record class Error(string Message);
+
+Тогда мы могли бы определить следующий элемент input:
+
+```cshtml
+<form method="post">
+    <label>Is Married:</label>
+    <input type="checkbox" name="isMarried" value="true"/><br /><br />
+    <input type="submit" value="Send" />
+</form>
 ```
 
-### BadResult и BadObjectResult
+Для связи с параметром метода элемент __input__ называется по имени параметра - __isMarried__. И, кроме того, это поле имеет по умолчанию значение `true`.
 
-BadResult и BadObjectResult посылают код `400`, что говорит о том, что запрос некорректный. Второй класс в дополнении к статусному коду позволяет отправить доплнительную информацию, которая потом отобразится в браузере.
+### Радиокнопки
 
-Эти классы можно применять, например, если в запросе нет каких-то параметров или данные представляют совсем не те типы, которые мы ожидаем получить, и т.д.
+Радиокнопки или переключатели позволяют выбрать один из нескольких вариантов. Для их создания используется элемент `<input type="radio" />`:
 
-Объекты обоих классов создаются методом BadRequest. Для первого класса - это метод без параметров, для второго класса - метод, который в качестве параметра принимает отправляемую информацию:
+```html
+<form method="post">
+ 
+    <input name="color" type="radio" value="red" />
+    <span>red</span> <br />
+    <input name="color" type="radio" value="blue" />
+    <span>blue</span> <br />
+    <input checked="checked" name="color" type="radio" value="green" />
+    <span>green</span>
+     
+    <p>
+        <input type="submit" value="Send" />
+    </p>
+</form>
+```
+
+Все радиокнопки имеют одинаковое имя - __color__ и отличаются только значением, которое хранится в атрибуте `value`. В контроллере мы могли бы получить выбранное значение в виде обычной строки:
 
 ```cs
-public IActionResult Index(string? name)
+public class HomeController : Controller
 {
-    if (string.IsNullOrEmpty(name)) return BadRequest("Name undefined");
-    return Content($"Name: {name}");
+    [HttpGet]
+    public IActionResult Index() => View();
+
+    [HttpPost]
+    public string Index(string color) => $"color: {color}";
 }
 ```
 
-### OkResult и OkObjectResult
+### Список select
+
+Элемент __select__ предназначен для создания двух видов списков - выпадающих и развернутых.
+
+В выпадающем списке мы можем выбрать один элемент:
+
+```html
+<form method="post">
+    <p>
+        <label for="language">Выберите язык:</label>
+        <select name="language">
+            <option value="csharp">C#</option>
+            <option value="vbnet">Visual Basic.NET</option>
+            <option value="fsharp">F#</option>
+            <option value="typescript">TypeScript</option>
+        </select>
+    </p>
+    <p>
+        <input type="submit" value="Отправить" />
+    </p>
+</form>
+```
 
-OkResult и OkObjectResult посылают код `200`, уведомляя об успешном выполнении запроса. Второй класс в дополнении к статусному коду позволяет отправить дополнительную информацию, которая потом отправляется клиенту в формате json.
+При этом отображаемый текст элемента списка необязательно должен совпадать с его значением. Например:
 
-Объекты обоих классов создаются методом Ok(). Для первого класса - это метод без параметров, для второго класса - метод, который в качестве параметра принимает отправляемую информацию:
+```html
+<option value="csharp">C#</option>
+```
+
+Здесь текст "C#" - это то, что мы увидим в списке, а "csharp" - это значение, которое при выборе данного элемента отправится на сервер.
+
+Чтобы получить выбранный в этом списке элемент, параметр метода контроллера должен называться также, как и элемент __select__:
 
 ```cs
-public IActionResult Index()
+[HttpPost]
+public string Index(string language) => $"Language: {language}";
+```
+
+Для создания списка со множественным выбором к элементу __select__ добавляется атрибут `multiple`:
+
+```html
+<form method="post">
+    <p>
+        <label for="language">Выберите язык:</label>
+        <select 
+            multiple  
+            ^^^^^^^^
+            name="languages"
+        >
+            <option value="csharp">C#</option>
+            <option value="vbnet">Visual Basic.NET</option>
+            <option value="fsharp">F#</option>
+            <option value="typescript">TypeScript</option>
+        </select>
+    </p>
+    <p>
+        <input type="submit" value="Отправить" />
+    </p>
+</form>
+```
+
+Но теперь, чтобы получить все выбранные значения в этом списке, метод контроллера должен в качестве параметра принимать массив или список:
+
+```cs
+public class HomeController : Controller
 {
-    return Ok("Don't worry. Be happy");
+    [HttpGet]
+    public IActionResult Index() => View();
+    [HttpPost]
+    public string Index(string[] languages)
+    {
+        string result = "Вы выбрали:";
+        foreach (string lang in languages)
+        {
+            result = $"{result} {lang};";
+        }
+        return result;
+    }
 }
 ```
+
+## Задание на дом
+
+Сделать репозиторий - конспект, то есть создать приложение и в конспекте привести блоки кода и скриншоты для всех примеров из лекции.

+ 455 - 0
articles/web_cs_03.md

@@ -0,0 +1,455 @@
+# Маршрутизация
+
+## Добавление маршрутизации
+
+Чтобы связать контроллеры MVC и их действия с запросами применяется система маршрутизации. Чтобы задействовать систему маршрутизации для контроллеров MVC, можно использовать различные способы.
+
+```cs
+app.MapControllerRoute(
+    name: "default",
+    pattern: "{controller=Home}/{action=Index}/{id?}");
+```
+
+Параметр _name_ определяет название маршрута - оно произвольно и в данное случае имеет значение "default". А параметр _pattern_ определяет шаблон маршрута, с которым будут сопоставляться входящие маршруты.
+
+Шаблон маршрута использует три параметра. Параметр "controller" будет сопоставляться по имени с одним из контроллеров приложения, а параметр "action" - с действием этого контроллера. Например, при запросе `http://localhost:3456/Home/Index` система выберет для обработки запроса контроллер __Home__ - имя контроллера без префикса Controller и его действие __Index__.
+
+Данный тип маршрутизации еще называют __Convention-Based Routing__, то есть маршрутизация, основанная на условностях в определениях маршрутов.
+
+### Добавление маршрутов
+
+Для добавления маршрутов в MVC мы можем применять следующие методы:
+
+- __MapControllerRoute()__ определяет произвольный маршрут и принимает следующие параметры:
+
+    ```
+    MapControllerRoute(
+        string name, 
+        string pattern, 
+        [object defaults = null], 
+        [object constraints = null], 
+        [object dataTokens = null])
+    ```
+
+    Его параметры:
+
+    - _name_: название машрута
+    - _pattern_: шаблон машрута
+    - _defaults_: значения параметров маршрутов по умолчанию
+    - _constraints_: ограничения маршрута
+    - _dataTokens_: определения токенов маршрута
+
+    Первые два параметра являются обязательными, остальные необязательные.
+
+- __MapDefaultControllerRoute()__ определяет стандартный маршрут, фактически эквивалентен вызову
+
+    ```cs
+    app.MapControllerRoute(
+        name: "default",
+        pattern: "{controller=Home}/{action=Index}/{id?}");
+    ```
+    Поскольку это довольно часто используемое определение маршрута, то для него и был введен отдельный метод.
+
+- __MapAreaControllerRoute()__ определяет маршрут, который также учитывает область приложения. Имеет следующие параметры:
+
+    ```
+    MapAreaControllerRoute(
+        string name, 
+        string areaName, 
+        string pattern, 
+        [object defaults = null], 
+        [object constraints = null], 
+        [object dataTokens = null])
+    ```
+
+    Обязательный параметр _areaName_ позволяет определить область, с которой будет связан маршрут.
+
+- __MapControllers()__ сопоставляет действия контроллера с запросами, используя маршрутизацию на основе атрибутов. Про атрибуты маршрутизации будет сказано в последующих статьях.
+
+- __MapFallbackToController()__ определяет действие контроллера, которое будет обрабатывать запрос, если все остальые определенные маршруты не соответствуют запросу. Принимает имя контроллера и его метода:
+
+    ```
+    MapFallbackToController(string action, string controller)
+    ```
+
+Определение шаблонов маршрутов, использование ограничений, необязательных параметров и параметров по умолчанию в MVC будет происходить также, как и уже было рассмотрено на примере ASP.NET Core в целом. Единственное существенное отличие состоит в том, что MVC добавляет поддержку атрибутов маршрутизации, которые мы далее рассмотрим.
+
+### Получение параметров маршрутов в контроллере
+
+Мы можем получить в контроллере все параметры маршрута, используя объект __RouteData__:
+
+```cs
+public class HomeController : Controller
+{
+    public string Index()
+    {
+        var controller = RouteData.Values["controller"];
+        var action = RouteData.Values["action"];
+        return $"controller: {controller} | action: {action}";
+    }
+}
+```
+
+### Дополнительные параметры маршрута
+
+Шаблон маршрута может содержать дополнительные параметры, которые можно получить через параметры метода. Например, определим следующие маршруты:
+
+```cs
+// устанавливаем сопоставление маршрутов с контроллерами
+app.MapControllerRoute(name: "default", pattern: "{controller}/{action}/{id}");
+app.MapControllerRoute(name: "name_age", pattern: "{controller}/{action}/{name}/{age}");
+```
+
+Здесь первый маршрут - "default" принимает третий параметр - _id_, который располагается в третьем сегменте строки запроса.
+
+Второй маршрут - "name_age" принимает дополнительно два параметра - _name_ и _age_, который располагаются соответственно в третьем сегменте и четвертом сегментах строки запроса.
+
+Для тестирования маршрутов определим следующий контроллер:
+
+```cs
+public class HomeController : Controller
+{
+    public string Index(int id) => $"Index Page. Id: {id}";
+    public string About(string name, int age) => $"About Page. Name: {name}  Age: {age}";
+}
+```
+
+И в данном случае, если к приложению придет запрос типа `Home/Index/6`, система маршрутизации сможет сопоставить этот запрос с первым маршрутом - маршрутом "default", который также содержит из трех сегментов, и значение последнего сегмента - число `6` будет передано в метод _Index_ через параметр _id_
+
+Если же к приложению придет запрос из четырех сегментов типа `Home/About/Tom/37`, то система маршрутизации сможет сопоставить этот запрос со вторым маршрутом - маршрутом "name_age". Тогда значение третьего сегмента будет передано в метод через параметр _name_, а значение четвертого сегмента - через параметр _age_:
+
+### Статические сегменты
+
+Шаблоны маршрутов могут иметь статические сегменты, которые не связывются с параметрами маршрутов. Например, определим следующий маршрут:
+
+```cs
+// устанавливаем сопоставление маршрутов с контроллерами
+app.MapControllerRoute(
+    name: "default", 
+    pattern: "api/{controller}/{action}/{id}");
+```
+
+В данном случае шаблон маршрута начинается со статического сегмента `api/`. Таким образом, этому маршруту будут соответствовать все маршруты, которые состоят из четырех сегментов, где первый сегмент равен `/api`, например, запрос `https://localhost:7288/api/Home/Index/6`
+
+### Необязательные параметры маршрутов
+
+Параметры маршрута могут быть необязательными. Чтобы определить параметр как необязательный, после его названия указывается знак вопроса. Например, определим следующее приложение:
+
+```cs
+app.MapControllerRoute(
+    name: "default", 
+    pattern: "{controller}/{action}/{id?}");
+```
+
+В этом шаблоне маршрута третий сегмент представляет параметр _id_, который помечен как необязательный. А это значит, что мы можем в запросе игнорировать значение для этого сегмента. Например, данный шаблон будет соответствовать двух- и трехсегментным запросам, например, двум следующим url:
+
+```
+/home/index
+/home/index/23
+```
+
+Для тестирования определим следующий контроллер:
+
+```cs
+public class HomeController : Controller
+{
+    public string Index(int? id)
+    {
+        if(id is not null) return $"Product Id: {id}";
+            
+        return "Product List";
+    }
+}
+```
+
+Здесь, если параметр _id_ определен, то метод _Index_ возвращает одну строку, если неопределен - то другую
+
+Необязательные параметры следует помещать в конце шаблона маршрута, как в случае с параметром _id_ в примере выше. То есть, к примеру, шаблон "{controller}/{action}/{id?}/{name}" не будет работать корректно, если мы для параметра _id_ не передадим значение. А вот шаблон "{controller}/{action}/{name}/{id?}" будет работать нормально.
+
+### Значения параметров по умолчанию
+
+Параметрам маршрута также можно назначить значения по умолчанию на случай, если им не переданы значения:
+
+```cs
+// устанавливаем сопоставление маршрутов с контроллерами
+app.MapControllerRoute(
+    name: "default", 
+    pattern: "{controller=Home}/{action=Index}/{id?}");
+```
+
+Здесь определен шаблон маршрута, который состоит из трех параметров. Параметр "controller" имеет значение по умолчанию "Home". Параметр "action" имеет значение по умолчанию "Index". Параметр "id" определен как необязательный. В итоге при различных запросах у нас получатся следующие значения:
+
+Запрос | Параметры запроса
+-------|------------------
+https://localhost:7256/ | controller=Home<br/>action=Index
+https://localhost:7256/Book | controller=Book<br/>action=Index
+https://localhost:7256/Book/Show | controller=Book<br/>action=Show
+https://localhost:7256/Book/Show/2 | controller=Book<br/>action=Show<br/>id=2
+
+Для установки значений по умолчанию также можно применять параметр _defaults_ метода _MapControllerRoute()_. Этот параметр представляет объект, свойства которого соответствуют параметрам маршрута. Например, определим следующий маршрут:
+
+```cs
+app.MapControllerRoute(
+    name: "default", 
+    pattern: "{action}",
+    defaults: new { controller = "Home", action = "Index"});
+``` 
+
+Здесь шаблон маршрута состоит из одного сегмента, который соответствует параметру "action", то есть представляет действие контроллера. А параметр _defaults_:
+
+```
+defaults: new { controller = "Home", action = "Index"}
+```
+
+Устанавливает, что в качестве контроллера по умолчанию будет использоваться контроллер Home, а в качестве действия - метод Index.
+
+Например, пусть у нас будет следующий HomeController:
+
+```cs
+public class HomeController : Controller
+{
+    public string Index() => "Index Page";
+    public string About() => "About Page";
+}
+```
+
+В этом случае запрос типа `https://localhost:7288/` будет обрабатываться методом _Index_ контроллера __Home__, а запрос `https://localhost:7288/About` - методом _About_.
+
+При использовании значений по умолчанию мы можем вовсе не использовать в шаблоне параметры маршрута. Например:
+
+```cs
+app.MapControllerRoute(
+    name: "info", 
+    pattern: "contact/info",
+    defaults: new { controller = "Home", action = "About"});
+```
+
+В данном случае запрос `https://localhost:7288/contact/info` будет обрабатываться методом _About_ контроллера __Home__.
+
+### Ограничения маршрутов
+
+Для параметров маршрута в MVC, также как и в общем в ASP.NET Core, можно устанавливать ограничения. Подробно ограничения были расписаны в лекции [API. REST API. Создание сервера ASP.NET Core.](./api_asp_net_core.md#ограничения-маршрута) Ограничения маршрутов в ASP.NET. В MVC применяется те же ограничения, что и в общем в ASP.NET Core, поэтому в данной статье я не буду их подробно описывать.
+
+Ограничения можно установить непосредственно в шаблоне маршрута:
+
+```cs
+app.MapControllerRoute(
+    name: "default", 
+    pattern: "{controller=Home}/{action=Index}/{id:int?}");
+```
+
+В данном случае указывается, что параметр _id_ может иметь либо значение типа __int__, либо значение `null`
+
+Второй способ установки ограничений представляет параметр _constraints_ метода _MapControllerRoute_:
+
+```cs
+app.MapControllerRoute(
+    name: "default", 
+    pattern: "{controller=Home}/{action=Index}/{id?}",
+    constraints: new {id= new IntRouteConstraint()});  // ограничения маршрутов
+```
+
+Параметр _constraints_ принимает объект, в котором свойства соответствуют по названиям параметрам маршрутов, а значения свойств - ограничения, применяемые к одноименным параметрам маршрутов. Так, в данном случае к параметру _id_ применяется ограничение __IntRouteConstraint__, которое указывает, что _id_ должно представлять значение типа __int__.
+
+## Атрибуты маршрутизации
+
+Кроме маршрутизации на основе условностей или __Convention-Based Routing__, которая рассматривалась в прошлых темах, фреймворк MVC позволяет использовать в приложении маршрутизацию на основе атрибутов. Такой тип маршрутизации еще называется __Attribute-Based Routing__. Атрибуты предоставляют более гибкий способ определения маршрутов.
+
+Для определения маршрута непосредственно в контроллере, необходимо использовать атрибут `[Route]`:
+
+```cs
+public class HomeController : Controller
+{
+    [Route("Home/Index")]
+    public IActionResult Index()
+    {
+        return Content("ASP.NET Core на metanit.com");
+    }
+
+    [Route("About")]
+    public IActionResult About()
+    {
+        return Content("About site");
+    }
+}
+```
+
+В данном случае метод _Index_ будет обрабатывать запросы по адресу "Home/Index", а метод _About_ по адресу "About".
+
+Если в проекте планируется использовать только маршрутизацию на основе атрибутов, то в файле ``Program.cs`` мы можем не определять никаких маршрутов. В этом случае для подключения в приложении возможностей маршрутизации достаточно применить метод _MapControllers_ без определения конкретных маршрутов:
+
+```cs
+var builder = WebApplication.CreateBuilder(args);
+builder.Services.AddControllersWithViews();
+ 
+var app = builder.Build();
+ 
+app.MapControllers(); 
+^^^^^^^^^^^^^^^^^^^^^
+
+app.Run();
+```
+
+Но также мы можем комбинировать способы маршрутизации. При чем здесь надо учитывать, что маршрутизация на основе атрибутов имеет больший приоритет. Например, определим следующее действие контроллера:
+
+```cs
+public class HomeController : Controller
+{
+    [Route("homepage")]
+    public string Index() => "ASP.NET Core MVC on METANIT.COM";
+}
+```
+
+В качестве параметра атрибут `Route` принимает шаблон URL, с которым будет сопоставляться запрошенный адрес. И даже если у нас определен стандартный маршрут в файле `Program.cs`:
+
+```cs
+app.MapControllerRoute(
+    name: "default",
+    pattern: "{controller=Home}/{action=Index}/{id?}");
+``` 
+
+Запрос типа `http://localhost:xxxx/home/index` работать не будет, так как мы явно указали с помощью атрибута, что метод _Index_ контроллера __Home__ будет обрабатывать только запросы `http://localhost:xxxx/homepage`
+
+Определение маршрутов с помощью атрибутов подчиняется тем же правилам, что определение маршрутов с помощью метода _MapControllerRoute()_. Например, используем параметры и ограничения:
+
+```cs
+[Route("{name:minlength(3)}/{age:int}")]
+public string Person(string name, int age)
+{
+    return $"name={name} | age={age}";
+}
+```
+
+И к такому методу мы сможем обратиться с запросом типа `http://localhost:xxxx/tom/37`.
+
+Или другой пример - применение необязательного параметра:
+
+```cs
+public class HomeController : Controller
+{
+    [Route("Home/Index/{id?}")]
+    public string Test(int? id)
+    {
+        if(id is not null)
+            return $"Параметр id={id}";
+        else
+            return $"Параметр id неопределен";
+    }
+}
+```
+
+И к такому методу мы сможем обратиться с запросом типа `http://localhost:xxxx/home/index` или добавить параметр `http://localhost:xxxx/home/index/5`.
+
+### Использование префиксов
+
+Допустим, у нас есть несколько методов, для которых определен свой маршрут, и мы хотим, чтобы эти маршруты начинались с одного определенного префикса. Например:
+
+```cs
+public class HomeController : Controller
+{
+    [Route("main/index/{name}")]
+    public string Index(string name) => name;
+ 
+    [Route("main/{id:int}/{name:maxlength(10)}")]
+    public string Test(int id, string name) => $" id={id} | name={name}";
+}
+```
+
+Вместо того, чтобы добавлять префикс "main" к каждому маршруту, мы могли бы определить его глобально для всего контроллера:
+
+```cs
+[Route("main")]
+public class HomeController : Controller
+{
+    [Route("index/{name}")]
+    public string Index(string name) => name;
+ 
+    [Route("{id:int}/{name:maxlength(10)}")]
+    public string Test(int id, string name) => $" id={id} | name={name}";
+}
+```
+
+### Параметры controller и action
+
+От всех параметров шаблона маршрутов в атрибутах отличаются два параметра _controller_ и _action_, которые ссылаются соответственно на контроллер и его действие и которые также можно получить в методе контроллера:
+
+```cs
+public class HomeController : Controller
+{
+    [Route("{controller}/{action}")]
+    public string Index()
+    {
+        var controller = RouteData.Values["controller"];
+        var action = RouteData.Values["action"];
+        return $"controller: {controller} | action: {action}";
+    }
+}
+```
+
+```cs
+public class HomeController : Controller
+{
+    [Route("{controller}/{action}")]
+    public string Index(string controller, string action)
+    {
+        return $"controller: {controller} | action: {action}";
+    }
+}
+```
+
+### Множественные маршруты
+
+С помощью атрибутов можно задать несколько маршрутов для одного метода. Например:
+
+```cs
+[Route("{controller}")]
+public class HomeController : Controller
+{
+   [Route("")]     // сопоставляется с Home
+   [Route("Index")] // сопоставляется с Home/Index
+    public string Index() => "Index Page";
+}
+```
+
+Также для контроллера можно задать сразу несколько маршрутов:
+
+```cs
+[Route("Store")]
+[Route("{controller}")]
+public class HomeController : Controller
+{
+   [Route("Main")]     // сопоставляется с Home/Main, либо с Store/Main
+   [Route("Index")] // сопоставляется с Home/Index, либо с Store/Index
+   public string Index() => "Index Page";
+}
+```
+
+### Значения параметров по умолчанию
+
+Также параметры в атрибутах могут иметь значения по умолчанию:
+
+```cs
+public class HomeController : Controller
+{
+    [Route("{controller=Home}/{action=Index}")]
+    public string Index(string controller, string action)
+    {
+        return $"controller: {controller} | action: {action}";
+    }
+}
+```
+
+В данном случае методом _Index_ будут сопоставляться такие запросы как `http://localhost:xxxx/home/index`, `http://localhost:xxxx/home/` и `http://localhost:xxxx/`
+
+Подобным образом можно задавать значения по умолчанию для других параметров, например:
+
+```cs
+public class HomeController : Controller
+{
+    [Route("{name=Tom}")]
+    public string Index(string name) => $"Name: {name}";
+}
+```
+
+В данном случае если параметру _name_ не передано значение, то он по умолчанию принимает в качестве значения строку "Tom"
+

BIN
img/web_cs_08.png


+ 2 - 1
readme.md

@@ -245,7 +245,8 @@ https://office-menu.ru/uroki-sql Уроки SQL
 
 1. [Введение в веб-разработку](./articles/web_intro.md)
 1. [Введение в ASP.NET Core MVC. Создание веб-приложения, структура проекта, контроллеры](./articles/web_cs_01.md)
-1. [MVC. Контроллер: результат действий](./articles/web_cs_02.md)
+1. [MVC. Представления](./articles/web_cs_02.md)
+1. [MVC. Маршрутизация](./articles/web_cs_03.md)
 
 ### Разработка мобильных приложений (MAUI)