При обращении к веб-приложению, как правило, пользователь ожидает получить некоторый ответ, например, в виде веб-страницы, которая наполнена данными. На стороне сервера метод контроллера, получая параметры и данные запроса, обрабатывает их и формирует ответ в виде результата действия. Результат действия - это тот объект, который возвращается методом после обработки запроса.
Результатом действия может быть практически что угодно. Например, в прошлых темах использовался объект string, например:
public string Index()
{
return "Hello METANIT.COM";
}
Пользователь передает методу некоторые значения и в ответ на запрос видит в своем браузере строку ответа.
Результатом действия может быть какой-нибудь сложный объект:
public class HomeController : Controller
{
public Message Index() => new Message("Hello METANIT.COM");
}
public record class Message(string Text);
Результатом может быть даже void, то есть по сути ничего:
public void GetVoid()
{
}
В данном случае метод GetVoid представляет вполне реальное действие контроллера, к которому можно обращаться из адресной строки браузера, передавать параметры, и который может содержать сложную логику обработки запроса. Только после обращения к этому методу пользователь увидит в своем веб-браузере одну пустоту, так как метод ничего не возвращает.
Но в большинстве случаев мы будем иметь дело не с void и даже не с типом string, а с объектами типа IActionResult, которые непосредственно предназначены для генерации результата действия. Интерфейс IActionResult находится в пространстве имен Microsoft.AspNetCore.Mvc и определяет один метод:
public interface IActionResult
{
Task ExecuteResultAsync(ActionContext context);
}
Метод ExecuteResultAsync принимает контекст действия и выполняет генерацию результата.
Этот интерфейс затем реализуется абстрактным базовым классом ActionResult:
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
Определим в нем следующий код:
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()
Теперь используем этот класс в контроллере:
public class HomeController : Controller
{
public IActionResult Index()
{
return new HtmlResult("<h2>Hello METANIT.COM!</h2>");
}
}
Здесь определен метод Index(), который возвращает объект HtmlResult. При обращении к этому объекту будет срабатывать его метод ExecuteResultAsync(), в котором будет происходить генерация html-страницы
Однако в большинстве случаев нам не придется создавать свои классы результатов, потому что фреймворк ASP.NET Core MVC итак предоставляет довольно большую палитру классов результатов для самых различных ситуаций:
EmptyResult: отправляет пустой ответ в виде статусного кода 200
public IActionResult GetVoid()
{
return new EmptyResult();
}
Аналогичен следующему методу:
public void GetVoid()
{
}
NoContentResult: во многом похож на EmptyResult, также отправляет пустой ответ, только в виде статусного кода 204
public IActionResult GetVoid()
{
return new NoContentResult();
}
FileResult: является базовым классом для всех объектов, которые пишут набор байтов в выходной поток. Предназначен для отправки файлов
FileContentResult: класс, производный от FileResult, пишет в ответ массив байтов
VirtualFileResult: также производный от FileResult класс, пишет в ответ файл, находящийся по заданному пути
PhysicalFileResult: также производный от FileResult класс, пишет в ответ файл, находящийся по заданному пути. Только в отличие от предыдущего класса использует физический путь, а не виртуальный.
FileStreamResult: класс, производный от FileResult, пишет бинарный поток в выходной ответ
ObjectResult: возвращает произвольный объект, как правило, применяется в качестве базового класса для других классов результатов. Но можно применять и самостоятельно:
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 отправляет клиенту ответ в виде строки. Так, следующий пример:
public string Index()
{
return "Hello METANIT.COM";
}
Можно переписать с использованием ContentResult:
public IActionResult Index()
{
return Content("Hello METANIT.COM");
}
Для отправки ContentResult не надо использовать конструктор, так как в контроллере уже определен специальный метод Content(), который принимает отправляемую строку и создает объект ContentResult.
Одним из наиболее популярных в наше время форматов хранения и передачи данных является формат JSON (JavaScript Object Notation). JSON не зависит от языка программирования, он более удобен и легче обрабатывается.
В JSON каждый отдельный объект заключается в фигурные скобки и представляет собой набор пар ключ-значение, разделенных запятыми, где ключом является название свойства объекта, а значением соответственно значение этого свойства. Например: {"name":"Tom"}. Здесь "name" является ключом, а "Tom" - значением.
Для отправки объекта в формате json в контроллере имеется метод Json(object obj), который в качестве параметра принимает отправляемый объект. Например:
public JsonResult GetName()
{
return Json("Tom");
}
В данном случае на сторону клиента отправляется строка "Tom".
Допустим, у нас есть следующий класс Person:
record class Person(string Name, int Age);
И тогда для отправки клиенту объекта Person мы можем написать следующий метод:
public IActionResult Index()
{
Person tom = new Person("Tom", 37);
return Json(tom);
}
При обращении к методу веб-браузер выведет полное описание объекта в формате json
Дополнительная версия метода Json() в качестве второго параметра принимает объект, который задает настройки сериализации в формат json. В качестве такого объекта выступает объект типа JsonSerializerOptions:
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 позволяет отправить любой статусный код клиенту:
public IActionResult Index()
{
return StatusCode(401);
}
Для создания этого результата используется метод StatusCode(), в который передается отправляемый код статуса.
Подобным образом мы можем послать браузеру любой другой статусный код. Но для отдельных кодов статуса предназначены свои отдельные классы.
NotFoundResult и NotFoundObjectResult посылает код 404, уведомляя браузер о том, что ресурс не найден. Второй класс в дополнении к статусному коду позволяет отправить доплнительную информацию, которая потом отобразится в браузере.
Объекты обоих классов создаются методом NotFound. Для первого класса - это метод без параметров, для второго класса - метод, который в качестве параметра принимает отправляемую информацию. Например, используем NotFoundObjectResult:
public IActionResult Index()
{
return NotFound("Resource not found");
}
UnauthorizedResult посылает код 401, уведомляя пользователя, что он не автризован для доступа к ресурсу:
public IActionResult Index(int age)
{
if (age < 18) return Unauthorized();
return Content("Access is available");
}
Для создания ответа используется метод Unauthorized().
UnauthorizedObjectResult также посылает код 401, только позволяет добавить в ответ некоторый объект с информацией об ошибке:
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 посылают код 400, что говорит о том, что запрос некорректный. Второй класс в дополнении к статусному коду позволяет отправить доплнительную информацию, которая потом отобразится в браузере.
Эти классы можно применять, например, если в запросе нет каких-то параметров или данные представляют совсем не те типы, которые мы ожидаем получить, и т.д.
Объекты обоих классов создаются методом BadRequest. Для первого класса - это метод без параметров, для второго класса - метод, который в качестве параметра принимает отправляемую информацию:
public IActionResult Index(string? name)
{
if (string.IsNullOrEmpty(name)) return BadRequest("Name undefined");
return Content($"Name: {name}");
}
OkResult и OkObjectResult посылают код 200, уведомляя об успешном выполнении запроса. Второй класс в дополнении к статусному коду позволяет отправить дополнительную информацию, которая потом отправляется клиенту в формате json.
Объекты обоих классов создаются методом Ok(). Для первого класса - это метод без параметров, для второго класса - метод, который в качестве параметра принимает отправляемую информацию:
public IActionResult Index()
{
return Ok("Don't worry. Be happy");
}