# MVC. Контроллер: результат действий ## Результаты действий При обращении к веб-приложению, как правило, пользователь ожидает получить некоторый ответ, например, в виде веб-страницы, которая наполнена данными. На стороне сервера метод контроллера, получая параметры и данные запроса, обрабатывает их и формирует ответ в виде результата действия. Результат действия - это тот объект, который возвращается методом после обработки запроса. Результатом действия может быть практически что угодно. Например, в прошлых темах использовался объект __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"); } ```