# Введение в ASP.NET Core MVC. Создание веб-приложения, структура проекта, контроллеры >Эта и следующие лекции взяты [отсюда](https://metanit.com/sharp/aspnetmvc/) Фреймворк ASP.NET Core MVC является частью платформы ASP.NET Core, его отличительная особенность - применение паттерна MVC. Преимуществом использования фрейморка ASP.NET Core MVC по сравнению с "чистым" ASP.NET Core является то, что он упрощает в ряде ситуаций и сценариев организацию и создание приложений, особенно это относится к большим приложениям. Стоит отметить, чам паттерн MVC не является исключительной особенностью ASP.NET Core MVC, данный паттерн появился еще в конце 1970-х годов в компании Xerox как способ организации компонентов в графическом приложение на языке Smalltalk и в настоящее время применяется во многих платформах и для различных языках программирования. Особенно популярен паттерн MVC в веб-приложениях. Концепция паттерна MVC предполагает разделение приложения на три компонента: - __Модель__ (model): описывает используемые в приложении данные, а также логику, которая связана непосредственно с данными, например, логику валидации данных. Как правило, объекты моделей хранятся в базе данных. В MVC модели представлены двумя основными типами: модели представлений, которые используются представлениями для отображения и передачи данных, и модели домена, которые описывают логику управления данными. Модель может содержать данные, хранить логику управления этими данными. В то же время модель не должна содержать логику взаимодействия с пользователем и не должна определять механизм обработки запроса. Кроме того, модель не должна содержать логику отображения данных в представлении. - __Представление__ (view): отвечают за визуальную часть или пользовательский интерфейс, нередко html-страница, через который пользователь взаимодействует с приложением. Также представление может содержать логику, связанную с отображением данных. В то же время представление не должно содержать логику обработки запроса пользователя или управления данными. - __Контроллер__ (controller): представляет центральный компонент MVC, который обеспечивает связь между пользователем и приложением, представлением и хранилищем данных. Он содержит логику обработки запроса пользователя. Контроллер получает вводимые пользователем данные и обрабатывает их. И в зависимости от результатов обработки отправляет пользователю определенный вывод, например, в виде представления, наполненного данными моделей. Отношения между компонентами паттерна можно описать следующей схемой: ![](../img/web_intro_06.png) В этой схеме модель является независимым компонентом - любые изменения контроллера или представления никак не влияют на модель. Контроллер и представление являются относительно независимыми компонентами. Так, из представления можно обращаться к определенному контроллеру, а из контроллера генерировать представления, но при этом нередко их можно изменять независимо друг от друга. Такое разграничение компонентов приложения позволяет реализовать концепцию разделение ответственности, при которой каждый компонент отвечает за свою строго очерченную сферу. В связи с чем легче построить работу над отдельными компонентами. И благодаря этому приложение легче разрабатывать, поддерживать и тестировать отдельные компоненты. Допустим, если нам важна визуальная часть или фронтэнд, то мы можем тестировать представление независимо от контроллера. Либо мы можем сосредоточиться на бэкэнде и тестировать контроллер. ## Первый проект на ASP.NET Core MVC Для создания проекта на ASP.NET Core MVC мы можем выбрать любой тип проекта на ASP.NET Core и в нем уже добавлять необходимые компоненты. Однако для упрощения Visual Studio уже по умолчанию предоставляет для этого шаблон ASP.NET Core Web App (Model-View-Controller): ![](../img/web_cs_01.png) Выберем данный шаблон для создания проекта. Дальше нам откроется окно для установки имени проекта. Допустим, проект будет называться asp_net_mvc: ![](../img/web_cs_02.png) Можно создать проект используя командную строку: ``` dotnet new mvc ``` ## Структура проекта ![](../img/web_cs_03.png) - __wwwroot__: этот узел (на жестком диске ему соответствует одноименная папка) предназначен для хранения статических файлов - изображений, скриптов javascript, файлов css и т.д., которые используются приложением. - __Controllers__: папка для хранения контроллеров, используемых приложением. По умолчанию здесь уже есть один контроллер - Homecontroller - __Models__: каталог для хранения моделей. По умолчанию здесь создается модель ErrorviewModel - __Views__: каталог для хранения представлений. Здесь также по умолчанию добавляются ряд файлов - представлений - __appsettings.json__: хранит конфигурацию приложения - __Program.cs__: файл, который определяет входную точку в приложение ASP.NET Core Можно запустить созданный проект (я делаю через консоль, но можно и в GUI) ``` dotnet run Используются параметры запуска из /Users/kei/Projects/asp_net_mvc/Properties/launchSettings.json... Сборка… info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5208 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: /Users/kei/Projects/asp_net_mvc ``` При запуске консоль отобразит адрес, по которому доступен проект. В моем случае это "http://localhost:5208". И если мы откроем в браузере этот адрес, то сработает запрос к контроллеру по умолчанию - классу __HomeController__, который выберет для генерации ответа нужное представление. И в итоге из представления будет создана html-страница: ![](../img/web_cs_04.png) Посмотрим что происходит в `Program.cs`: ```cs var builder = WebApplication.CreateBuilder(args); /** в приложение добавляются сервисы MVC (поддержка контроллеров и представлений) */ builder.Services.AddControllersWithViews(); var app = builder.Build(); /** HSTS (HTTP Strict Transport Security) — это стандарт безопасности, который принуждает браузеры подключаться к веб-сайту только по защищенному протоколу HTTPS, даже если пользователь вводит ссылку с HTTP. */ if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } // middleware app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); // подключает статические ресурсы app.MapStaticAssets(); /** Вместо конечных точек (как в минималистичном АПИ) подключается маршрутизация через контроллер */ app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}") .WithStaticAssets(); app.Run(); ``` Прежде всего надо отметить, что функциональность MVC, в частности, поддержка контроллеров и представлений, подключается в приложение в виде сервиса - в данном случае с помощью вызова `services.AddControllersWithViews()`. После этого мы можем использовать функциональность фреймворка MVC. Кроме того, чтобы связать приходящие от пользователей запросы с контроллерами применяется метод `MapControllerRoute()`. Через первый параметр - _name_ в метод передается название маршрута - в данном случае "default". Через второй параметр - параметр _pattern_ передается шаблон маршрута, которому должен соответствовать запрос. В качестве шаблона маршрута применяется шаблон "`{controller=Home}/{action=Index}/{id?}`", который представляет трехсегментный запрос. В нем первый сегмент представляет контроллер, второй сегмент - метод контроллера, а третий - необязательный параметр. При этом если в запросе не указаны сегменты (например, обращение идет к корню веб-приложения), то в качестве контроллера по умолчанию применяется `HomeController`, а в качестве его метода - метод `Index`. Посмотрим что находится в `HomeController.cs` ```cs namespace asp_net_mvc.Controllers; public class HomeController : Controller { private readonly ILogger _logger; // конструктор public HomeController(ILogger logger) { _logger = logger; } // точка входа по-умолчанию // реальный путь /home/index public IActionResult Index() { return View(); } // точка входа /home/privacy public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } ``` Контроллер имеет __конструктор__, через который посредством механизма dependency injection передается сервис ILogger, используемый для логгирования. Также контроллер определяет три __метода__ - _Index_, _Privacy_ и _Error_. Как видим все методы в итоге возвращают представление (`return View()`). Представления лежат в каталоге `/Views/<Название контроллера>` - в нашем случае `/Views/Home`, называются так же как методы и имеют расширение `*.cshtml` Рассмотрим файл `/Views/Home/Index.cshtml`: ```html @{ViewData["Title"] = "Home Page";} ``` В принципе это обычный HTML файл, кроме первой строки. Немного поправим его: ```html @{ViewData["Title"] = "Заголовок нашего MVC приложения";}

Привет ЙОТК!

``` ![](../img/web_cs_05.png) ## Сервисы MVC Функциональность MVC и ее работа в приложении зависит от добавляемых сервисов. В примере выше мы использовали метод `AddControllersWithViews()` для добавления сервисов MVC, благодаря чему система маршрутизации смогла связать запрос с контроллером. Есть и другие сервисы: - `AddMvc()`: добавляет все сервисы фреймворка MVC (в том числе сервисы для работы с аутентификацией и авторизацией, валидацией и т.д.) - `AddMvcCore()`: добавляет только основные сервисы фреймворка MVC, а всю дополнительную функциональность, типа аутентификацией и авторизацией, валидацией и т.д., необходимо добавлять самостоятельно - `AddControllersWithViews()`: добавляет только те сервисы фреймворка MVC, которые позволяют использовать контроллеры и представления и связанную функциональность. При создании проекта по типу ASP.NET Core Web App (Model-View-Controller) используется именно этот метод - `AddControllers()`: позволяет использовать контроллеры, но без представлений. И в зависимости от того, насколько широко нам надо использовать возможности фреймворка MVC, выбирается соответствующий метод. Например, мы могли бы использовать вместо вызова `AddControllersWithViews()` метод `AddMvc()`. ## Контроллеры ### Контроллеры и их действия Основным элементом в архитектуре ASP.NET Core MVC является __контроллер__. При получении запроса система маршрутизации выбирает для обработки запроса нужный контроллер и передает ему данные запроса. Контроллер обрабатывает эти данные и посылает обратно результат обработки. Например, проект, создаваемый по шаблону ASP.NET Core Web App (Model-View-Controller), по умолчанию содержит как минимум один контроллер - класс _HomeController_ (его содержимое мы уже посмотрели выше) В ASP.NET Core MVC контроллер представляет обычный класс на языке C#, который обычно наследуется от абстрактного базового класса Microsoft.AspNetCore.Mvc.Controller и который, как и любой класс на языке C#, может иметь поля, свойства, методы. Согласно соглашениям об именовании названия контроллеров обычно оканчиваются на суффикс "Controller", остальная же часть до этого суффикса считается именем контроллера, например, HomeController. Но в принципе эти условности также необязательны. Но есть также и обязательные условности, которые предъявляются к контроллерам. В частности, класс контроллера должен удовлетворять как минимум одному из следующих условий: - Класс контроллера имеет суффикс "Controller" ```cs public class HomeController { //............ } ``` - Класс контроллера наследуется от класса, который имеет суффикс "Controller" ```cs public class Home : Controller { //............. } ``` - К классу контроллера применяется атрибут [Controller] ```cs [Controller] public class Home { //.................. } ``` ### Действия контроллера Ключевым элементом контроллера являются его __действия__. Действия контроллера - это публичные методы, которые могут сопоставляться с запросами. Например, контроллер _HomeController_ и его метод _Index_. В общем случае метод может возвращать просто текст и возможна такая релизация ```cs public string Index() { return "Привет ЙОТК!"; } ``` Естественно, в этом случае нет ни заголовка ни тела HTML и текст выводится "как есть": ![](../img/web_cs_06.png) ### Обращение к действиям контроллера Сопоставление запроса с контроллером и его действием происходит благодаря системе маршрутизации. ```cs app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}") .WithStaticAssets(); ``` Метод `app.MapControllerRoute` добавляет один маршрут с именем __default__ и шаблоном "`{controller=Home}/{action=Index}/{id?}`". Данный шаблон устанавливает трехсегментную структуру строки запроса: `controller/action/id`. То есть в начале идет название контроллера, затем название действия, и далее может идти необязательный параметр `id`. Собственно поэтому система может соотнести запрос типа `localhost:xxxx/Home/Index` с контроллером и его действием. Чтобы обратиться к контроллеру из веб-браузера, нам надо в адресной строке набрать `адрес_сайта/Имя_контроллера/Действие_контроллера`. Так, по запросу `адрес_сайта/Home/Index` система маршрутизации по умолчанию вызовет метод _Index_ контроллера __HomeController__ для обработки входящего запроса. ### Методы, которые не являются действиями Однако не все методы контроллера являются действиями. Контроллер также может иметь непубличные методы - такие методы не рассматриваются как действия и соответственно не могут соотноситься с запросами. Например, определим в контроллере следующий метод: ```cs public class HomeController : Controller { protected internal string Hello() => "Hello ASP.NET"; } ``` Метод _Hello_ не является действием, поскольку его модификатор отличается от __public__. Соответственно мы не сможем обратиться к этому методу с запросом `localhost:xxxx/Home/Hello`. Хотя такие не публичные методы также могут быть полезными - в них можно определять какие-нибудь промежуточные вычисления и затем использовать в действиях контроллера. При этом если мы изменим модификатор метода на `public`, то метод _Hello_ станет полноценным действием. ### Атрибуты NonController, ActionName и NonAction Возможно, сопоставление по умолчанию бывает не всегда удобно. Например, у нас есть класс в папке `Controllers`, но мы не хотим, чтобы он мог обрабатывать запрос и использоваться как контроллер. Чтобы указать, что этот класс не является контроллером, нам надо использовать над ним атрибут `[NonController]`: ```cs [NonController] public class HomeController : Controller { //........... } ``` Аналогично, если мы хотим, чтобы какой-либо публичный метод контроллера не рассматривался как действие, то мы можем использовать над ним атрибут `[NonAction]`: ```cs [NonAction] public string Hello() { return "Hello ASP.NET"; } ``` Атрибут `[ActionName]` позволяет для метода задать другое имя действия. Например: ```cs [ActionName("Welcome")] public string Hello() { return "Hello ASP.NET"; } ``` В этом случае чтобы обратиться к этому методу, надо отправить запрос `localhost:xxxx/Home/Welcome`. А запрос `localhost:xxxx/Home/Hello` работать не будет. ### Типы запросов Кроме того, методы могут обслуживать разные типы запросов. Для указания типа запроса HTTP нам надо применить к методу один из атрибутов: `[HttpGet]`, `[HttpPost]`, `[HttpPut]`, `[HttpPatch]`, `[HttpDelete]` и `[HttpHead]`. Если атрибут явным образом не указан, то метод может обрабатывать все типы запросов: `GET`, `POST`, `PUT`, `DELETE`. Например: ```cs public class HomeController : Controller { [HttpGet] public string Index() => "Hello METANIT.COM"; [HttpPost] public string Hello() => "Hello ASP.NET"; } ``` Так, в данном случае метод _Index_ обрабатывает только запросы типа `GET`, а метод _Hello_ - запросы типа `POST`. ### Контекст контроллера При обращении к контроллеру среда ASP.NET создает для этого контроллера контекст, который содержит различные связанные с контроллером данные. Для получения контекста в классе контроллера нам доступно свойство _ControllerContext_, которое представляет одноименный класс __ControllerContext__. Этот класс определяет ряд важный свойств: - __HttpContext__: содержит информацию о контексте запроса - __ActionDescriptor__: возвращает дескриптор действия - объект ActionDescriptor, который описывает вызываемое действие контроллера - __ModelState__: возвращает словарь _ModelStateDictionary_, который используется для валидации данных, отправленных пользователем - __RouteData__: возвращает данные маршрута Для получения информации о запросе нас прежде всего будет интересовать свойство _HttpContext_, которое представляет объект `Microsoft.AspNetCore.Http.HttpContext`. В принципе это тот же самый объект, который нам доступен в любом компоненте __middleware__ в ASP.NET Core. Этот объект также доступен через свойство _HttpContext_ класса контроллера. То есть следующие вызовы будут обращаться к одному и тому же объекту: ```cs var ctx1 = ControllerContext.HttpContext; var ctx2 = HttpContext; ``` Объект __HttpContext__ инкапсулирует всю информацию о запросе. В частности, он определяет следующие свойства: - __Request__: содержит собственно информацию о текущем запросе. - __Response__: управляет ответом - __User__: представляет текущего пользователя, который обращается к приложению - __Session__: объект для работы с сессиями #### Response Свойство `HttpContext.Response` представляет объект __HttpResponse__ и позволяет управлять ответом на запрос, в частности, устанавливать заголовки ответа, куки, отправлять в выходной поток некоторый ответ. Этот же объект доступен через свойство _Response_ класса __Conroller__. Среди свойств объекта __Response__ можно выделить следующие: - __Body__: объект Stream, который применяется для отправки данных в ответ пользователю - __Cookies__: куки, отправляемые в ответе - __ContentType__: MIME-тип ответа - __Headers__: коллекция заголовков ответа - __StatusCode__: статусный код ответа С помощью объекта __Response__ мы можем настроить параметры ответа и отправить его клиенту. Например, отправим из действия контроллера данные клиенту: ```cs public class HomeController : Controller { public async Task Index() { Response.ContentType = "text/html;charset=utf-8"; await Response.WriteAsync("

Hello METANIT.COM

"); } } ``` Здесь в методе _Index_ вначале устанавливается заголовок `Content-Type`, а затем с помощью метода _WriteAsync_ отправляется некоторое простейшее содержимое в виде строки с кодом html. Поскольку метод _WriteAsync_ - асинхронный, то к нему можно применить оператор __await__, а метод-действие _Index_ определен как асинхронный. #### Request Свойство `HttpContext.Request` представляет объект __HttpRequest__ и предоставляет разнообразную информацию о запросе. Этот же объект доступен через свойство _Request_ класса контроллера. Среди свойств объекта __Request__ можно выделить следующие: - __Body__: объект Stream, который используетя для чтения данных запроса - __Cookies__: куки, полученные в запросе - __Form__: коллекция значений отправленных форм - __Headers__: коллекция заголовков запроса - __Path__: возвращает запрошенный путь - строка запроса без домена и порта - __Query__: возвращает коллекцию переданных через строку запроса параметров - __QueryString__: возвращает ту часть запроса, которая содержит параметры. Например, в запросе `http://localhost:52682/Home/Index?alt=4` это будет `?alt=4` Вся основная информация нам доступна из заголовков. Например, получим из запроса все заголовки и выведем их в браузере: ```cs public class HomeController : Controller { public async Task Index() { Response.ContentType = "text/html;charset=utf-8"; System.Text.StringBuilder tableBuilder = new("

Request headers

"); foreach (var header in Request.Headers) { tableBuilder.Append( $""); } tableBuilder.Append("
{header.Key}{header.Value}
"); await Response.WriteAsync(tableBuilder.ToString()); } } ``` ### Передача данных в контроллер через строку запроса #### Получение данных через строку запроса Вместе с запросом приложению могут приходить различные данные. И чтобы получить эти данные, мы можем использовать разные способы. Самым распространенным способом считается применение параметров. Определение в методах контроллера параметров ничем не отличается от определения параметров в языке C#. Например, определим в контроллере следующий метод: ```cs public string Index(string name) => $"Your name: {name}"; ``` В данном случае метод _Index_ из вне получает некоторую строку через параметр _name_. Передавать значения для параметров можно различными способами. При отправке GET-запроса значения можно передать через строку запроса. Строка запроса представляет ту часть адреса, которая идет после знака вопроса `?` и представляет набор параметров, где каждый параметр отделен от другого с помощью амперсанда: ``` название_ресурса?параметр1=значение1&параметр2=значение2 ``` Например, в адресе: ``` https://localhost:7288/Home/Index?name=Tom&age=37 ``` часть `?name=Tom&age=37` представляет строку запроса, которая содержит два параметра: _name_ и _age_. Значение параметра _name_ - "Tom", а значение параметра _age_ - 37. Например, передадим в выше определенный метод _Index_ через строку запроса данные для параметра _name_: ![](../img/web_cs_07.png) То есть в данном случае при обращении к методу _Index_ с запросом `https://localhost:7288/Home/Index?name=Eugene` параметру _name_ будет передаваться значение "Eugene". Система привязки MVC, которую мы позже рассмотрим, по умолчанию сопоставляет параметры запроса и параметры метода по имени. То есть, если в строке запроса идет параметр _name_, то его значение будет передаваться именно параметру метода, который также называется _name_. При этом должно быть также соответствие по типу, то есть если параметр метода принимает числовое значение, то и через строку запроса надо передавать для этого параметра число, а не строку. Подобным образом можно передать значения для нескольких параметров. Например, изменим метод _Index_ следующим образом: ```cs public string Index(string name, int age) { return $"Name: {name} Age: {age}"; } ``` В этом случае мы можем обратиться к действию, набрав в адресной строке `https://localhost:7288/Home/Index?name=Tom&age=37`. Если же мы не используем параметры в строке запроса, то для параметров можно задать значения по умолчанию. Например, при отправке запроса `https://localhost:7288/Home/Index` параметры _name_ и _age_ будут иметь значения по умолчанию. ```cs public string Index(string name = "Bob", int age = 33) { return $"Name: {name} Age: {age}"; } ``` ### Объект Request.Query Параметры представляют самый простой способ получения данных, но в действительности нам необязательно их использовать. В контроллере доступен объект __Request__, у которого можно получить как данные строки запроса через свойство _Request.Query_. Это свойство представляет объект __IQueryCollection__, где по ключу - названию параметра можно получить его значение. Например: ```cs public string Index() { string name = Request.Query["name"]; string age = Request.Query["age"]; return $"Name: {name} Age: {age}"; } ``` В данном случае мы можем передать методу _Index_ данные через запрос типа `https://localhost:7288/Home/Index?name=Tom&age=37`. ### Передача данных в контроллер через формы #### Передача данных через формы в запросе POST Кроме GET-запросов также широко применяются POST-запросы. Как правило, такие запросы отправляются с помощью форм на веб-странице. Но основные принципы передачи данных будут теми же, что и в GET-запросах. Для передачи POST-запросов определим два метода: ```cs [HttpGet] public async Task Index() { string content = @"




"; Response.ContentType = "text/html;charset=utf-8"; await Response.WriteAsync(content); } [HttpPost] public string Index(string name, int age) => $"{name}: {age}"; ``` Первый метод _Index_ имеет атрибут [HttpGet], поэтому данный метод будет обрабатывать только запросы GET. Для упрощения примера в ответ метод будет возвращать html-код с формой ввода (хотя естественно, для формы html можно было бы определить представление) Эта форма содержит два поля ввода. Что важно, первое поле имеет имя "name", которое задается с помощью атрибута "name": ```html ``` Второе поле имеет имя "age": ```htlm ``` Таким образом, при обращении к методу пользователь увидит в браузере форму ввода. При нажатии на кнопку "Send" введенные данные будут отправляться на сервер методом POST (`
`). Поскольку у элемента `` не задан атрибут _action_, который устанавливает адрес, то введенные данные отправляются на тот же адрес (то есть по сути методу с тем же именем - методу _Index_). Но поскольку у формы установлен атрибут method='post', то данные будут отправлять в запросе типа POST. А запросы данного типа обрабатывает второй метод _Index_: ```cs [HttpPost] public string Index(string name, int age) => $"{name}: {age}"; ``` Чтобы система могла связать параметры метода и данные формы, необходимо, чтобы атрибуты _name_ у полей формы соответствовали названиям параметров. То есть в данном случае параметры метода _Index_ называются так же, как и поля формы - _name_ и _age_. #### Получение данных из контекста запроса Для получения данных отправленных форм можно использовать свойство _Request.Form_. Это свойство представляет коллекцию __IFormsCollection__, где каждый элемент имеет ключ и значение. В качестве ключа элемента выступает название поля формы, а в качестве значения - введенные в это поле данные: ```cs public async Task Index() { string content = @"



"; Response.ContentType = "text/html;charset=utf-8"; await Response.WriteAsync(content); } [HttpPost] public string PersonData() { string name = Request.Form["name"]; string age = Request.Form["age"]; return $"{name}: {age}"; } ``` Обратите внимание, у формы определен атрибут `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 = @$" METANIT.COM {htmlCode} "; await context.HttpContext.Response.WriteAsync(fullHtmlCode); } } ``` Данный класс будет реализовать интерфейс __IActionResult__. В конструкторе он принимает html-код, который затем будет выводиться на веб-страницу. Для вывода используется асинхронный метод context.HttpContext.Response.WriteAsync() Теперь используем этот класс в контроллере: ```cs public class HomeController : Controller { public IActionResult Index() { return new HtmlResult("

Hello METANIT.COM!

"); } } ``` Здесь определен метод `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"); } ``` --- ## Задание на дом Сделать репозиторий - конспект, то есть создать приложение и в конспекте привести блоки кода и скриншоты для всех примеров из лекции.