Цель урока. Научиться делать вывод данных в html, использование Razor. Helperы. PageableData. Динамические формы. RedirectToLogin, RedirectToNotFoundPage. Страница ошибки. RssActionResult.
Итак, рассмотрим как устроена часть View.
В контроллере все action-методы возвращают тип ActionResult. И для вывода результата мы используем:
Основными параметрами View может быть:
Выбор, какой же View использовать происходит следующим образом:
приступим к изучению.
При создании View есть выбор между двумя движками: ASPX и Razor. Первый мы не будем использовать в дальнейшем, поэтому поговорим о Razor.

ASPX был громозким движком с тегами
Razor использует конструкцию
Внутри { } находятся теги – это маркер того, что это шаблон. Для простого выполнения кода внутри шаблона используем структуру @{ code }, для корректного вывода данных внутри атрибутов или текстом конструкция — @(string result):
Чтобы вывести не теговый текст, нужно использовать псевдотеги :
Для вывода html-текста – или должна возвращаться MvcHtmlString, или использовать конструкцию Html.Raw(html-string-value), иначе текст будет выведен с экранированием тегов.
Рассмотрим постраничный вывод таблицы из БД. Проанализируем:
Создадим Generic-класс PageableData (/Models/Info/PageableData.cs):
По умолчанию количество выводимых значений на странице – 20, но мы можем изменить этот параметр в конструкторе. Передаем IQueryable и вычисляем кол-во страниц CountPage. Используя PageNo, выбираем страницу:
В контроллере используем:
Во View используем данный класс:
Запускаем, проверяем (http://localhost/User)

Для продолжения, сгенерируем больше данных (просто ctrl-c, ctrl-v в таблице в Server Explorer)

Перейдем к созданию Helper’а пагинатора, который даст нам возможность пролистывать этот список.
Так как мы используем bootstrap, то и на базе него будем делать пагинатор. В коде он выглядит так:
Нас интересует только внутренняя часть .
Helper создается как Extension для класса System.Web.Mvc.HtmlHelper. План таков:
Код будет выглядеть так:
Добавим namespace LessonProject.Helper в объявления во View. Это можно сделать двумя способами:
Добавляем пагинатор во View:
Обратите внимание на конструкцию
Это делегат, который возвращает ссылку на страницу. А Url.Action() – формирует ссылку на страницу /User/Index с параметром page = x.
Вот что получилось (уменьшил количество вывода на странице до 5, чтобы образовалось больше страниц):

Следующим шагом к просмотру данных будет создание поиска. Поиск будет простой, по совпадению подстроки в одном из полей данных. Входной параметр – searchString.
Создадим класс
Основа
Итак, рассмотрим как устроена часть View.
В контроллере все action-методы возвращают тип ActionResult. И для вывода результата мы используем:
return View(modelData);Основными параметрами View может быть:
- Имя, обычно оно совпадает с именем action-метода. В случае если надо вызвать иной по имени View, то используется конструкция
return View(“ViewName”, modelData). - Данные для отображения во View. Необязательный параметр. При передаче во View этот объект данных будет обозначаться Model. Для связывания типа данных во View указывается ожидаемый тип данных:
@model LessonProject.Model.User
- Layout. Необязательный параметр. При указании этого параметра по данной строке найдется страница-контейнер и вызовется. View-часть будет обработана методом RenderBody()
Выбор, какой же View использовать происходит следующим образом:
- Ищется в папке /Areas/[Area]/Views/[ControllerName]/
- Ищется в папке /Areas/[Area]/Views/Shared/
- Ищется в папке /Views/[ControllerName]/
- Ищется в папке /Views/Shared/
приступим к изучению.
Razor
При создании View есть выбор между двумя движками: ASPX и Razor. Первый мы не будем использовать в дальнейшем, поэтому поговорим о Razor.

ASPX был громозким движком с тегами
<% %> для выполнения кода и <%: %> для вывода данных.Razor использует конструкцию
@Model.Name. Т.е. всё, что начинается с @ переводит в режим или исполнения кода, или вывода данных @foreach() {…}, или @if() { … } else { … }:@if (Model.Any()) { <p>Список</p> } @foreach (var role in Model) { <div class="item"> <span class="id"> @role.ID </span> <span class="name"> @role.Name </span> <span class="Code"> @role.Code </span> </div> }
Внутри { } находятся теги – это маркер того, что это шаблон. Для простого выполнения кода внутри шаблона используем структуру @{ code }, для корректного вывода данных внутри атрибутов или текстом конструкция — @(string result):
@{ int i = 0; } @foreach (var role in Model) { <div class="item @(i % 2 == 0 ? "odd" : "")"> <span class="id"> @role.ID </span> <span class="name"> @role.Name </span> <span class="Code"> @role.Code </span> </div> i++; }
Чтобы вывести не теговый текст, нужно использовать псевдотеги :
@foreach (var role in Model) { @role.Name<text>, </text> }
Для вывода html-текста – или должна возвращаться MvcHtmlString, или использовать конструкцию Html.Raw(html-string-value), иначе текст будет выведен с экранированием тегов.
PageableData
Рассмотрим постраничный вывод таблицы из БД. Проанализируем:
- Контроллер должен получить в параметрах значение страницы, которую мы будем выводить
- По умолчанию это будет первая страница
- При выводе, мы должны знать:
- Список элементов БД, которые выводим
- Количество страниц
- Текущую страницу
Создадим Generic-класс PageableData (/Models/Info/PageableData.cs):
public class PageableData<T> where T : class { protected static int ItemPerPageDefault = 20; public IEnumerable<T> List { get; set; } public int PageNo { get; set; } public int CountPage { get; set; } public int ItemPerPage { get; set; } public PageableData(IQueryable<T> queryableSet, int page, int itemPerPage = 0) { if (itemPerPage == 0) { itemPerPage = ItemPerPageDefault; } ItemPerPage = itemPerPage; PageNo = page; var count = queryableSet.Count(); CountPage = (int)decimal.Remainder(count, itemPerPage) == 0 ? count / itemPerPage : count / itemPerPage + 1; List = queryableSet.Skip((PageNo - 1) * itemPerPage).Take(itemPerPage); } }
По умолчанию количество выводимых значений на странице – 20, но мы можем изменить этот параметр в конструкторе. Передаем IQueryable и вычисляем кол-во страниц CountPage. Используя PageNo, выбираем страницу:
List = queryableSet.Skip((PageNo - 1) * itemPerPage).Take(itemPerPage);
В контроллере используем:
public class UserController : DefaultController { public ActionResult Index(int page = 1) { var data = new PageableData<User>(Repository.Users, page, 30); return View(data); } …
Во View используем данный класс:
@model LessonProject.Models.Info.PageableData<LessonProject.Model.User> @{ ViewBag.Title = "Users"; Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml"; } <h2>Users</h2> <p> @foreach (var user in Model.List) { <div class="item"> <span class="id"> @user.ID </span> <span class="email"> @user.Email </span> <span class="activateDate"> @user.AddedDate </span> </div> } </p>
Запускаем, проверяем (http://localhost/User)

Для продолжения, сгенерируем больше данных (просто ctrl-c, ctrl-v в таблице в Server Explorer)

Перейдем к созданию Helper’а пагинатора, который даст нам возможность пролистывать этот список.
Helper (PagerHelper)
Так как мы используем bootstrap, то и на базе него будем делать пагинатор. В коде он выглядит так:
<div class="pagination"> <ul> <li><a href="#">Prev</a></li> <li><a href="#">1</a></li> <li><a href="#">2</a></li> <li><a href="#">3</a></li> <li><a href="#">4</a></li> <li><a href="#">5</a></li> <li><a href="#">Next</a></li> </ul> </div>
Нас интересует только внутренняя часть .
Helper создается как Extension для класса System.Web.Mvc.HtmlHelper. План таков:
- Вывести Prev (сделать активным если надо)
- Вывести ссылки на первые три страницы 1, 2, 3
- Вывести троеточие, если необходимо
- Вывести активной ссылку текущей страницы
- Вывести троеточие, если необходимо
- Вывести последние три страницы
- Вывести Next (сделать активной если надо)
- Заключить всё в ul и вывести как MvcHtmlString
Код будет выглядеть так:
public static MvcHtmlString PageLinks(this HtmlHelper html, int currentPage, int totalPages, Func<int, string> pageUrl) { StringBuilder builder = new StringBuilder(); //Prev var prevBuilder = new TagBuilder("a"); prevBuilder.InnerHtml = "«"; if (currentPage == 1) { prevBuilder.MergeAttribute("href", "#"); builder.AppendLine("<li class=\"active\">" + prevBuilder.ToString() + "</li>"); } else { prevBuilder.MergeAttribute("href", pageUrl.Invoke(currentPage - 1)); builder.AppendLine("<li>" + prevBuilder.ToString() + "</li>"); } //По порядку for (int i = 1; i <= totalPages; i++) { //Условие что выводим только необходимые номера if (((i <= 3) || (i > (totalPages - 3))) || ((i > (currentPage - 2)) && (i < (currentPage + 2)))) { var subBuilder = new TagBuilder("a"); subBuilder.InnerHtml = i.ToString(CultureInfo.InvariantCulture); if (i == currentPage) { subBuilder.MergeAttribute("href", "#"); builder.AppendLine("<li class=\"active\">" + subBuilder.ToString() + "</li>"); } else { subBuilder.MergeAttribute("href", pageUrl.Invoke(i)); builder.AppendLine("<li>" + subBuilder.ToString() + "</li>"); } } else if ((i == 4) && (currentPage > 5)) { //Троеточие первое builder.AppendLine("<li class=\"disabled\"> <a href=\"#\">...</a> </li>"); } else if ((i == (totalPages - 3)) && (currentPage < (totalPages - 4))) { //Троеточие второе builder.AppendLine("<li class=\"disabled\"> <a href=\"#\">...</a> </li>"); } } //Next var nextBuilder = new TagBuilder("a"); nextBuilder.InnerHtml = "»"; if (currentPage == totalPages) { nextBuilder.MergeAttribute("href", "#"); builder.AppendLine("<li class=\"active\">" + nextBuilder.ToString() + "</li>"); } else { nextBuilder.MergeAttribute("href", pageUrl.Invoke(currentPage + 1)); builder.AppendLine("<li>" + nextBuilder.ToString() + "</li>"); } return new MvcHtmlString("<ul>" + builder.ToString() + "</ul>"); }
Добавим namespace LessonProject.Helper в объявления во View. Это можно сделать двумя способами:
- В самом View
@using LessonProject.Helper;
- В Web.config (рекомендуется)
<configSections> … <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> </sectionGroup> </configSections> + <system.web.webPages.razor> <pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <add namespace="LessonProject.Helper" /> </namespaces> </pages> </system.web.webPages.razor>
Добавляем пагинатор во View:
<div class="pagination"> @Html.PageLinks(Model.PageNo, Model.CountPage, x => Url.Action("Index", new {page = x})) </div>
Обратите внимание на конструкцию
x => Url.Action("Index", new {page = x})Это делегат, который возвращает ссылку на страницу. А Url.Action() – формирует ссылку на страницу /User/Index с параметром page = x.
Вот что получилось (уменьшил количество вывода на странице до 5, чтобы образовалось больше страниц):

SearchEngine
Следующим шагом к просмотру данных будет создание поиска. Поиск будет простой, по совпадению подстроки в одном из полей данных. Входной параметр – searchString.
public ActionResult Index(int page = 1, string searchString = null) { if (!string.IsNullOrWhiteSpace(searchString)) { //тут Поиск return View(data); } else { var data = new PageableData<User>(Repository.Users, page, 5); return View(data); } }
Создадим класс
SearchEngine, который принимает значения IQueryable, и строку поиска, а возвращает данные по поиску (/Global/SearchEngine.cs):
Первым делом, создадим классы по очистке строки запроса, никаких тегов и убираем разделители типа [,], {,}, (,):
/// <summary>
/// The regex strip html.
/// </summary>
private static readonly Regex RegexStripHtml = new Regex("<[^>]*>", RegexOptions.Compiled);
private static string StripHtml(string html)
{
return string.IsNullOrWhiteSpace(html) ? string.Empty :
RegexStripHtml.Replace(html, string.Empty).Trim();
}
private static string CleanContent(string content, bool removeHtml)
{
if (removeHtml)
{
content = StripHtml(content);
}
content =
content.Replace("\\", string.Empty).
Replace("|", string.Empty).
Replace("(", string.Empty).
Replace(")", string.Empty).
Replace("[", string.Empty).
Replace("]", string.Empty).
Replace("*", string.Empty).
Replace("?", string.Empty).
Replace("}", string.Empty).
Replace("{", string.Empty).
Replace("^", string.Empty).
Replace("+", string.Empty);
var words = content.Split(new[] { ' ', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
var sb = new StringBuilder();
foreach (var word in
words.Select(t => t.ToLowerInvariant().Trim()).Where(word => word.Length > 1))
{
sb.AppendFormat("{0} ", word);
}
return sb.ToString();
}
Создаем поиск:
public static IEnumerable<User> Search(string searchString, IQueryable<User> source)
{
var term = CleanContent(searchString.ToLowerInvariant().Trim(), false);
var terms = term.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var regex = string.Format(CultureInfo.InvariantCulture, "({0})", string.Join("|", terms));
foreach (var entry in source)
{
var rank = 0;
if (!string.IsNullOrWhiteSpace(entry.Email))
{
rank += Regex.Matches(entry.Email.ToLowerInvariant(), regex).Count;
}
if (rank > 0)
{
yield return entry;
}
}
}
В первой строке очищаем строку запроса. Создаем regex для поиска. В данном случае, мы ищем только в поле Email у пользователей.
Как это работает:
- При вводе слова в поиске, например, «cher [2]», вначале убираем разделители, получаем «cher 2».
- Создаем regex = (cher|2).
- Просматриваем весь список, переданный через
IQueryable
Если есть совпадение, то выносим его в IEnumerable - yield return entry