Цель урока. Научиться делать вывод данных в 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