Здравствуйте, уважаемые читатели. Сегодня мы
продолжим погружение в мир ASP.NET MVC 5. И основной посыл данной статьи – рассмотреть использование класса ActionResult. Задача этого класса
заключается в том, чтобы представить результат выполняемого метода. По умолчанию
к контроллеру доступны снаружи все методы, помеченные как public. Сегодня мы
пробежимся по всех доступных классах, которые унаследованы от
ActionResult и которые вы можете использовать в своих приложениях. На рисунке ниже представлен весь список.
Для того чтобы
проверить все на практике, давайте создадим новое ASP.NET Web Application, которое назовем “ActionSample”, как показано на рисунке ниже.
Затем выберем
стандартный темплейт “MVC”.
ViewResult
Начнем с
самого первого варианта рассмотрения ViewResult. Если мы откроем наш контроллер HomeController, то
сможем увидеть следующий список методов:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description
page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return
View();
}
}
Эти все методы
возвращают ViewResult. Этот класс
используется для рендеринга представления, используя интерфейс IView, экземпляр которого возвращается с помощью объекта IViewEngine. Такая гибкость позволяет создавать представления
на лету. Более детально описание этого класса взято с хабрахабр.
Уделим особое внимание одному
особенному классу, наследованному от ActionResult – ViewResult. Этот класс
способен найти и отрендерить соответствующий шаблон представления, передав ему
что-либо в коллекции ViewData, сформированном в методе действия. Это и есть то,
что называется «движок отображения» («view engine») (класс .NET, реализующий
интерфейс IViewEngine).
Стандартный движок – это
WebFormViewEngine. Его шаблоны представления являются страницами WebForms
(.aspx) (то есть серверные страницы, используюшиеся в традиционной технологии
ASP.NET WebForms). Страницы WebForms имеют свой собственный конвейер обработки,
начинающийся с компиляции ASPX/ASCX «на лету» и проходящий через серию событий,
называющийся жизненным циклом страницы. В отличие от традиционного ASP.NET, в
ASP.NET MVC эти страницы должны быть максимально простыми, поскольку, согласно
принципам MVC, представления могут отвечать только за генерацию HTML кода. Это
значит что, вам не требуется детально понимать жизненный цикл страниц WebForms.
С соблюдением принципов разделения ролей приходят простота и удобство
сопровождения кода.
Наша
задача – научиться все использовать самим, поэтому мы создадим новое
представление. Благо, редактор в Visual Studio настолько удобен, что от вас потребуется минимум
действий.
Добавим в наш код
следующий метод:
public ActionResult Info()
{
ViewBag.Message = "Your info page.";
return
View();
}
Представление для
нашего кода делается очень легко через редактор. Для этого в ставим курсор на
любую строчку внутри метода Info и нажимаем правую
клавишу мыши. Затем в выпадающем меню выбираем “Add View…”, как показано
ниже на рисунке.
У нас будет показан
дизайнер, как ниже на рисунке.
Мы можем указать
темплейт для выбора, выбрать нужною модель, указать, частичное это представление
или полное, связать с нужным дата контекстом и другое. Для себя все оставляем
так, как показано на рисунке выше, и нажимаем на кнопку “Add”. После
этого у нас будет создана наше представление, как
показано ниже.
@{
ViewBag.Title = "Info";
}
<h2>Info</h2>
После этого мы
запустим наш проект на выполнение и добавим в адресную строку браузера /Info.
PartialView
Теперь поскольку мы
рассмотрели использование ViewResult, возложив всю работу на дизайнер Visual Studio, пришло время
рассмотреть, как работают PartialView. Генерировать PartialView можно разными способами.
Как, например, с клиента, используя вызов метода @Html.Partial, так и с сервера, как сейчас рассмотрим мы.
Давайте для начала
создадим новую модель. Для этого в папку Models добавим новый класс
InfoModel.
public class InfoModel
{
[Display(Name = "User Id")]
public int
Id { get; set; }
[Display(Name = "User name")]
public string Name { get;
set;
}
}
Затем рассмотрим
второй способ создания представлений не с контроллера. Для этого остановимся на
папочке Views\Home и нажмем правой
кнопкой мыши.
Затем выбираем в
темлейтах “Details”, а в model class ищем только что
созданный класс, и ставим галочку что нам нужно создать partial view.
Смотрим на то, что
нам сгенерировал темплейт.
@model
ActionSample.Models.InfoModel
<div>
<h4>InfoModel</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model
=> model.Name)
</dt>
<dd>
@Html.DisplayFor(model
=> model.Name)
</dd>
</dl>
</div>
<p>
@Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
@Html.ActionLink("Back to List", "Index")
</p>
Следующим делом
добавим новый метод _InfoResult в наш HomeController.
public PartialViewResult
_InfoResult()
{
var infoModel = new InfoModel()
{
Id = 1,
Name = "Alex"
};
return
PartialView(infoModel);
}
Теперь осталось
добавить вызов этого метода с Info.cshtml.
@{
ViewBag.Title = "Info";
}
<h2>Info</h2>
@{Html.RenderAction("_InfoResult");}
Запускаем теперь
наш пример и смотрим на изменения.
RedirectResult
Это очень простой
класс, задачей которого является переадресация посредством Url. Зайдем в наш HomeController и добавим новый метод InfoResultRedirect, который будет нас переадресовывать в google поиск.
public ActionResult
InfoResultRedirect()
{
return Redirect("http://www.google.com");
}
Затем перейдем в
последнему нашему созданному частичному представлению _InfoResult.cshtml и
добавим туда новую ссылку, которая будет адресовать нас к помощи google.
@model
ActionSample.Models.InfoModel
<div>
<h4>InfoModel</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model
=> model.Name)
</dt>
<dd>
@Html.DisplayFor(model
=> model.Name)
</dd>
</dl>
</div>
<p>
@Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
@Html.ActionLink("Back to List", "Index")
|
@Html.ActionLink("Back to google", "InfoResultRedirect")
</p>
Запускаем наш
пример и смотрим? что ссылка работает.
Нажимаем на “Back to google” и убеждаемся
в том, что навигация успешно работает.
RedirectToRouteResult
В отличие от предыдущего класса, задача класса RedirectToRouteResult
заключается в том, чтобы сделать навигацию на другое событие, используя для
этого таблицу маршрутизации вашего сайта. Как это работает – смотрите ниже.
Добавим в наш HomeController новый метод, задачей которого будет переход
на страницу контактов.
public ActionResult
InfoResultRedirectToContact()
{
return RedirectToAction("Contact");
}
Допиливаем, как
обычно, наше представление _InfoResult.cshtml, а точнее – добавим туда
новый линк, который будет называться “Contact page”.
@model
ActionSample.Models.InfoModel
<div>
<h4>InfoModel</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model
=> model.Name)
</dt>
<dd>
@Html.DisplayFor(model
=> model.Name)
</dd>
</dl>
</div>
<p>
@Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
@Html.ActionLink("Back to List", "Index")
|
@Html.ActionLink("Back to google", "InfoResultRedirect") |
@Html.ActionLink("Contact page", "InfoResultRedirectToContact")
</p>
Запускаем и
проверяем, все ли у нас работает так, как нужно.
ContentResult
Данный класс
позволяет вернуть пользовательский тип данных. Например, мы можем вернуть
отформатированный кусок текста. По умолчанию в классе Controller есть метод Content который возвращает тип ContentResult. По умолчанию результат возвращается как text/plain (ContentType property). Мы же немного
усложним задачу и вернем отформатированный с помощью html текст. Поэтому добавляем в наш HomeController метод, который назовем HelloWorld.
public ActionResult
HelloWorld()
{
return Content("<p>Hello World</p>", "text/html");
}
Запускаем наш
пример и смотрим на то, что же у нас получилось.
JsonResult
Позволяет вернуть
на клиент данные в формате json. Используется
очень часто и я уверен, что вам пригодится не один раз. Как обычно. в класс HomeController добавим новый метод, который будет возвращать результат в
json
формате.
public JsonResult
DetailInfo()
{
return Json(new
{ Text = "Hello
World" }, JsonRequestBehavior.AllowGet);
}
Если мы запустим
наш проект, то мы получим файл в формате json, как показано на рисунке ниже.
Как мы с вами
понимаем, это удобно только для тестирования. На практике же полученные данные
нужно как-то обрабатывать. Поэтому давайте перейдем в нашу представление Info.cshtml и немного его
допилим, чтобы полученный результат как-то использовался.
@{
ViewBag.Title = "Info";
}
<h2>Info</h2>
@{Html.RenderAction("_InfoResult");}
<div id="result"></div>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script type="text/javascript">
$(function () {
$.get("/Home/DetailInfo", function (data) {
$("#result").html("<p>"
+ data.Text + "</p>");
});
})
</script>
Для этого нам
понадобилось использовать jQuery, но
код получился несложным для понимания. Запускаем наш проект и смотрим, что
наше слово “Hello World” успешно отображается.
JavaScriptResult
С помощью данного
класса вы можете вернуть java script результат на клиент.
Вероятность того, что вы когда-либо будете это использовать, очень
маленькая, да и лучше избегайте по возможности такого подхода, так как он считается антипаттерном MVC. Все то же самое можно сделать успешно на клиенте.
Но так как наша цель – это ознакомление, по старинке правим наш HomeController и добавляем туда новый метод UpdateInfo.
public JavaScriptResult
UpdateInfo()
{
var script = "$('#java-script-update').html('<p>
Updated by Alex</p>');";
return JavaScript(script);
}
В этом скрипте мы
ищем по id контрол,
а затем обновляем в него значение. То есть, нам нужно просто в наше
представление Info.cshtml добавить блок div с именем “java-script-update” и сделать подгруздку скрипта, например, через ajax запрос. Можно вызов сделать
через Razor с помощью метода @Ajax.ActionLink, но я предпочитаю
это делать через jQuery. Тем более что в
предыдущем примере мы уже использовали jQuery.
@{
ViewBag.Title = "Info";
}
<h2>Info</h2>
@{Html.RenderAction("_InfoResult");}
<div id="result"></div>
<div id="java-script-update"></div>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script type="text/javascript">
$(function () {
$.get("/Home/DetailInfo", function (data) {
$("#result").html("<p>"
+ data.Text + "</p>");
});
})
$.ajax({
url: "/Home/UpdateInfo"
});
</script>
Как видите, все как
я и говорил. Добавился дин блок div к старому коду, и
добавился в самом конце вызов метода через ajax запрос. Запускаем наш пример и смотрим, что у нас
появилась надпись “Updated by Alex”.
HttpStatusCodeResult
Позволяет
возвратить как результат код http запроса
и статус (HTTP response code and description).
Используется очень часто, когда нужно вернуть какую-то пользовательскую
ошибку. Давайте в наш контроллер добавим новый метод Details, который будет возвращать ошибку 410. Полное
описание всех кодов ошибок можно посмотреть здесь Status Code Definitions.
10.4.11 410 Gone
The requested resource is no longer
available at the server and no forwarding address is known. This condition is
expected to be considered permanent. Clients with link editing capabilities
SHOULD delete references to the Request-URI after user approval. If the server
does not know, or has no facility to determine, whether or not the condition is
permanent, the status code 404 (Not Found) SHOULD be used instead. This
response is cacheable unless indicated otherwise.
The 410 response is primarily intended to
assist the task of web maintenance by notifying the recipient that the resource
is intentionally unavailable and that the server owners desire that remote
links to that resource be removed. Such an event is common for limited-time,
promotional services and for resources belonging to individuals no longer
working at the server's site. It is not necessary to mark all permanently
unavailable resources as "gone" or to keep the mark for any length of
time -- that is left to the discretion of the server owner.
Давайте посмотрим
теперь на саму реализацию.
public ActionResult
Details(int id)
{
return new HttpStatusCodeResult(410);
}
Запускаем наш пример и с браузера вызываем метод Details.
HttpUnauthorizedResult
Данный класс
используется повсеместно в том случае, когда у пользователя нет
прав на получение той или иной информации. Давайте добавим новый метод Page, который будет возвращать нам данный тип ошибки.
public ActionResult Page(int
id)
{
return new HttpUnauthorizedResult("Access is denied");
}
После того как мы в
браузере введем /Page/1, например, мы
перейдем на страницу авторизации.
Это у нас
происходит, потому что в функции ConfigeAuth класса Startup, которая задает начальные настройки для
аутентификации, прописана следующая строка кода:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security
stamp when the user logs in.
// This is a security feature which is used
when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user)
=> user.GenerateUserIdentityAsync(manager))
}
});
В случае если вы не введете корректные данные для пользователя, эта строка вас сразу
перенаправит на нужную страницу. Давайте закомментируем эту строку кода и
запустим наш пример опять.
Теперь мы уже с
вами получили то, чего хотели. Небольшое дополнение к данному action result. Наверное, в 90%
случаев вы не будете использовать подход с непосредственным возвратом HttpUnauthorizedResult по той причине, что это все сделано через фильтры.
Фильтры – это целая тема отдельной статьи, поэтому я просто покажу пример их использования, и вы наверняка вспомните, что видели нечто подобное у себя.
И пример с методом:
Можно написать свои
кастомные реализации. Но суть в том, что вы можете задавать всю необходимую
логику через фильтры. Например, мы можем переписать наш метод следующим
образом:
[Authorize]
public ActionResult Page(int id)
{
return null;
}
И также получим
ошибку авторизации, но уже с помощью фильтров.
Думаю, что такой
подход вам пригодится намного больше.
HttpNotFoundResult
Используется в тех
случаях, когда, например, нам нужно уведомить пользователя. что некой страницы не
существует. Например, с помощью вашего метода хотят получить информацию о
кастомере, но передали идентификатор кастомера, которого у вас нет. В ответ вы
можете вернуть данный тип результата, и это будет валидно. Давайте подправим
написанный для предыдущего примера код и будем возвращать, что страница не
найдена, если идентификатор меньше 10-ти.
public ActionResult Page(int
id)
{
if (id < 10)
return HttpNotFound();
return new HttpUnauthorizedResult("Access is denied");
}
Запускаем наш
пример, чтобы получить нужный результат.
Вводим значение
больше 10-ти – получаем старую ошибку с проблемой авторизации.
FileResult
Представляет собой
базовый класс и используется для того, чтобы дать возможность отправить бинарный
файл в виде ответа. Представлен реализацией такими классами, как FilePathResult, FileContentResult и
FileStreamResult, каждый из которых мы рассмотрим дальше в статье.
FilePathResult
Позволяет отправить
в виде ответа содержимое файла. В отличие от остальных классов, о которых речь
шла выше, первым параметров в данном классе идет путь к файлу. Для того чтобы
проверить. как это все работает, я добавил в папку App_Data картинку с
драконом.
Затем в контроллер HomeController добавил метод GetPhoto, который будет возвращать эту саму картинку. Ниже
приведена реализация этого метода.
public FileResult GetPhoto()
{
string fileName = HttpContext.Server.MapPath("~/App_Data/dragon.jpg");
;
string contentType = "application/jpeg";
return File(fileName,
contentType);
}
Некоторых может
смутить, почему указан метод File, а
не возвращается непосредственно FilePathResult. Дело в том, что класс Controller, от которого унаследован наш контроллер HomeController, просто напичкан разными вспомогательными методами.
Ниже на фото можно увидеть, что делает метод File.
Как видите, в зависимости
от первого параметра, он возвращает наружу необходимый класс. Теперь вы можете
запустить ваше приложение и получить по конкретному url - ~/Home/GetPhoto вашу картинку. Но так как мы с вами рассматриваем
примеры, которые больше подходят для реалий, нам нужно поправить слегка наш
клиент Info.cshtml.
@{
ViewBag.Title = "Info";
}
<h2>Info</h2>
<div>
<img src="/Home/GetPhoto" />
</div>
@Html.ActionLink("Download dragon picture", "GetPhoto")
@{Html.RenderAction("_InfoResult");}
<div id="result"></div>
<div id="java-script-update"></div>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script type="text/javascript">
$(function () {
$.get("/Home/DetailInfo", function (data) {
$("#result").html("<p>"
+ data.Text + "</p>");
});
})
$.ajax({
url: "/Home/UpdateInfo"
});
</script>
Здесь я добавил
вывод содержимого в img, а также возможность загрузки в виде ссылки “Download dragon image”. Запускаем наш проект и проверяем, что он
работает.
Понятное дело, что
лучше сразу так картинке не загружать в img, но как загружать какие-то данные с помощью ajax запроса, мы уже рассмотрели, поэтому переписать все
это вам не составит труда.
FileContentResult
Отличается от
предыдущего только тем ,что выгружает сразу бинарные данные. Добавим по старинке
в наш контроллер метод, который для оригинальности назовем GetPhoto1.
public FileResult
GetPhoto1()
{
string fileName =
HttpContext.Server.MapPath("~/App_Data/dragon.jpg"); ;
string contentType = "application/jpeg";
var body = System.IO.File.ReadAllBytes(fileName);
return
File(body, contentType);
}
В нашем
представлении правок нужно сделать по минимуму. Просто
заменить с GetPhoto на
GetPhoto1. Запустить и
убедиться, что все работает, вы можете самостоятельно в этот раз. Тем более. что
результат будет такой же, как и в примере выше. Единственное, что будет
отличатся, – так это при попытке выгрузить файл имя ему будет предложено GetPhoto1.
FileStreamResult
Как говорит само
название класса, задача его – это возвращать нам stream для работы.
Особо не заморачиваясь, добавляем
метод GetPhoto2.
public FileResult GetPhoto2()
{
string fileName = HttpContext.Server.MapPath("~/App_Data/dragon.jpg"); ;
string contentType = "application/jpeg";
var stream = new
System.IO.FileStream(fileName, System.IO.FileMode.Open);
return
File(stream, contentType);
}
Клиент меняем, как
и в предыдущем примере, простой заменой GetPhoto1 на GetPhoto2. Работать будет так же, как и в предыдущих двух
примерах.
EmptyResult
Самый простой
класс, который является надстройкой на null, когда мы возвращаем null с нашего метода.
Ниже приведен пример использования.
public EmptyResult
Nothing()
{
return new EmptyResult();
}
Легко заменяется на
такой код:
public ActionResult
Nothing()
{
return null;
}
В общем, запускаем и
смотрим. Должна быть просто пустая страница.
Итоги
У нас получилась достаточно продуктивная работа. У вас – потому что вы это
дочитали до конца, у меня – потому что я это все осилил дописать. Здесь всего по
чуть-чуть и все в одном месте. Возможно, вам это пригодится в работе, мне же это
будет что-то вроде шпаргалки. Буду рад ответить на ваши вопросы в комментариях.
Исходники к статье
вы можете скачать по ссылке: Actions sample.
No comments:
Post a Comment