2.4. Немного о контроллерах

Итак, мы научились с помощью различных URL вызывать разный код в приложении и теперь пора переходить к вопросу отображения данных.

До сих пор в качестве результата я всегда возвращал просто строку. Методы контроллера объявлялись так, что они отображают строку и в качестве результата возвращалась она же. Это работает и что бы вы не возвращали, .NET будет пытаться отобразить это на экране.

Но это такая вспомогательная функция, которой лучше не злоупотреблять и если честно, я ей пользуюсь только в обучающих целях. Желательно, чтобы методы контроллера возвращали в результате любой класс, который реализует метод IActionResult:

public IActionResult Index()
{
  return Content("Test"); 
}

Здесь я объявил метод, который возвращает более предназначенный для Web результат – IActionResult. Опять же, пример будет простой, и чтобы вернуть данные, которые соответствуют этому интерфейсу, я пользуюсь простым методом Content. Этот метод получает на входе строку и возвращает в качестве результата объект класса ContentResult, который в свою очередь является реализацией IActionResult.

Таким образом, мы сделали практически то же самое, что и в прошлой главе – отобразили на странице просто текстовую строку, но сделали это в соответствии с рекомендациями.

Если посмотреть на внутренности, то ContentResult происходит от класса ActionResult:

public class ContentResult : ActionResult,
      IActionResult,      
      IStatusCodeActionResult

Класс ActionResult является абстрактным и его цель реализовать нужный интерфейс IActionResult:

public abstract class ActionResult : IActionResult

На основе базового класса ActionResult в .NET Core создано несколько классов, которые предназначены для разных типов возвращаемых данных:

  • Microsoft.AspNetCore.Mvc.ChallengeResult
  • Microsoft.AspNetCore.Mvc.ContentResult
  • Microsoft.AspNetCore.Mvc.EmptyResult
  • Microsoft.AspNetCore.Mvc.FileResult
  • Microsoft.AspNetCore.Mvc.ForbidResult
  • Microsoft.AspNetCore.Mvc.JsonResult
  • Microsoft.AspNetCore.Mvc.LocalRedirectResult
  • Microsoft.AspNetCore.Mvc.ObjectResult
  • Microsoft.AspNetCore.Mvc.PartialViewResult
  • Microsoft.AspNetCore.Mvc.RedirectResult
  • Microsoft.AspNetCore.Mvc.RedirectToActionResult
  • Microsoft.AspNetCore.Mvc.RedirectToPageResult
  • Microsoft.AspNetCore.Mvc.RedirectToRouteResult
  • Microsoft.AspNetCore.Mvc.SignInResult
  • Microsoft.AspNetCore.Mvc.SignOutResult
  • Microsoft.AspNetCore.Mvc.StatusCodeResult
  • Microsoft.AspNetCore.Mvc.ViewComponentResult
  • Microsoft.AspNetCore.Mvc.ViewResult
  • Microsoft.AspNetCore.Mvc.RazorPages.PageResult

То есть теоретически в своем коде вы можете вместо IActionResult возвращать базовый класс ActionResult и от этого функционал не поменяется, но все же лучше использовать интерфейс. Разница между привязкой к интерфейсу и к абстрактному классу небольшая, но привязка к интерфейсам теоретически лучше, чем привязка к классам, даже абстрактным.

Следующий код вполне легален, но желательно все же использовать интерфейс:

public IActionResult Index()
{
  return Content("Test"); 
}

Так как наш контроллер происходит от класса Controller, вместе с ним мы получаем много интересного функционала, который может пригодиться. Например, если обратиться к this.ControllerContext.ActionDescriptor, то в этом дескрипторе много полезной информации, такой как имя текущего Action, которое выполняется.

Но чаще всего вам будет необходимо получить доступ к Http контексту. Раньше это было глобальное свойство, которое сложно было перезаписать и из-за него возникали проблемы с юнит тестами и интеграционными тестами. Сейчас это свойство контроллера и к нему можно получить доступ через this.HttpContext. Вся информация о запросе будет в свойстве this.HttpContext.Request. Здесь будет информация о том, какой URL пользователь вызвал, какие параметры передал, какой тип запроса пришел – GET или POST, и т.д.

У контроллера есть еще несколько интересных методов, которые могут генерировать специальные типы. Например, у нас может быть метод, который показывает на странице какую-то статью, а номер статьи должен передаваться в качестве параметра. Если по номеру никакой статьи не найдено, то мы можем вернуть стандартную ошибку 404 и ее можно вернуть вызвав метод контроллера this.NotFound:

public IActionResult loadarticle(int id)
{
  if (id == 10) {
    return Content("Good stuff");
  }
  return this.NotFound();
} 

В результате на странице отобразиться следующее:

Это классический метод обработки классической проблемы. Если вам кажется, что страница выглядит очень страшной, то это поправимо, есть способ сделать ее красивее, но эта тема отдельного разговора.

Для тех случаев, когда переданы некорректные данные нужно использовать метод BadRequest. Например, номер статьи не должен быть меньше нуля, а значит мы можем добавить проверку:

if (id < 0)
{
  return this.BadRequest("Something is really bad");
}

Есть еще метод File, с помощью которого можно передать пользователю с сервера файл.

А можем мы вернуть в качестве результата метода что-то совсем сумасшедшее? Да! Давайте создадим класс Person и попробуем вернуть объект этого типа:

public class Person
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
}
 

Теперь создаем новый метод в контроллере:

public Person person()
{
  return new Person { 
     FirstName = "Михаил", 
     LastName = "Фленов" 
  };
}
 

Если обратиться к этому методу то браузер отобразить объект в виде JSON:

{"firstName":"Михаил","lastName":"Фленов"}

И хотя такой способ работает, он все же не совсем рекомендуем. Лучше все же возвращать IActionResult, а чтобы объект превратить в такой интерфейс можно использовать класс ObjectResult:

public IActionResult person()
{
  return new ObjectResult(new Person { 
     FirstName = "Михаил", 
     LastName = "Фленов" 
    }
  );
}
 

Все описанные примеры находятся в файле контроллера ContentController. Исходник можно скачать здесь

Следующая глава

2.5. Представления

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне