Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений .NET Framework Как извлечь максимум возможного из WebGrid в ASP.NET MVC RSS

Как извлечь максимум возможного из WebGrid в ASP.NET MVC

Текущий рейтинг: 2.71 (проголосовало 7)
 Посетителей: 4892 | Просмотров: 9811 (сегодня 0)  Шрифт: - +

В этой статье я ознакомлю вас с WebGrid и покажу, как его можно использовать в ASP.NET MVC 3; затем мы рассмотрим, как выжать из него максимум возможного в решении на основе ASP.NET MVC. (Обзор WebMatrix и синтаксис Razor, который я буду использовать здесь, см. в статье Кларка Селла «Введение в WebMatrix» в апрельском номере по ссылке msdn.microsoft.com/ru-ru/magazine/gg983489.aspx.)

Вы также увидите, как компонент WebGrid интегрируется в среду ASP.NET MVC и упрощает разработчикам рендеринг табличных данных. Основное внимание я буду уделять рассмотрению WebGrid в разрезе ASP.NET MVC: созданию строго типизированной версии WebGrid с полной поддержкой IntelliSense, использованию точек подключения к WebGrid для поддержки разбиения на страницы на серверной стороне и добавлению функциональности AJAX. Рабочие примеры опираются на сервис, который предоставляет доступ к базе данных AdventureWorksLT через Entity Framework. Если вас интересует код доступа к данным, вы можете просмотреть его в пакете исходного кода, который можно скачать для этой статьи. Кроме того, я советую прочитать статью Джули Лерман «Разбиение на страницы на серверной стороне с применением Entity Framework и ASP.NET MVC 3» в мартовском номере (msdn.microsoft.com/ru-ru/magazine/gg650669).

Очень часто при рендеринге списка элементов нужно давать возможность пользователям щелкать любой из элементов для перехода в представление Details.

Приступаем к работе с WebGrid

Чтобы показать простой пример с WebGrid, я подготовил простую операцию ASP.NET MVC, которая передает IEnumerable<Product> в представление. В большей части статьи я использую механизм представлений Razor, но потом мы обсудим еще и механизм представлений Web Forms. Мой класс ProductController имеет следующую операцию:

public ActionResult List()
{
  IEnumerable<Product> model = _productService.GetProducts();
  return View(model);
}

Представление List включает следующий Razor-код, который выполняет рендеринг сетки, показанной на рис. 1:

@model IEnumerable<MsdnMvcWebGrid.Domain.Product>
@{
  ViewBag.Title = "Basic Web Grid";
}
<h2>Basic Web Grid</h2>
<div>
@{
  var grid = new WebGrid(Model, defaultSort:"Name");
}
@grid.GetHtml()
</div>

*

Рис. 1. Рендеринг Basic Web Grid

В первой строке представления указывается тип модели (например, тип свойства Model, к которому мы обращаемся в представлении) — IEnumerable<Product>. Затем внутри элемента div я создаю экземпляр WebGrid, передавая данные модели; я делаю это в блоке кода @{...}, чтобы Razor не пытался осуществлять рендеринг результата. В конструкторе я также присваиваю параметру defaultSort значение «Name», чтобы WebGrid знал, что передаваемые данные уже отсортированы по Name. Наконец, я использую @grid.GetHtml(), чтобы сгенерировать HTML для сетки и выполнить его рендеринг в ответе.

Этот небольшой фрагмент кода создает весьма богатую функциональность сетки. Сетка ограничивает объем отображаемых данных и включает ссылки элемента управления страницами (pager) для перемещения по данным; заголовки столбцов визуализируются как ссылки для поддержки «пролистывания» страниц. Для настройки этого поведения можно указать ряд параметров в конструкторе WebGrid и методе GetHtml. Эти параметры позволяют отключать разбиение на страницы и сортировку, изменять количество строк на страницу, заменять текст в ссылках элемента управления страницами и многое другое. В табл. 1 показаны параметры конструктораWebGrid, а в табл. 2 — параметры методаGetHtml.

Табл. 1. Параметры конструктора WebGrid

ИмяТипОписание
sourceIEnumerable<dynamic>Данные, подлежащие рендерингу
columnNamesIEnumerable<string>Фильтрует выводимые столбцы
defaultSortstringУказывает столбец по умолчанию, по которому осуществляется сортировка
rowsPerPageintУправляет тем, сколько строк (записей) отображается на каждой странице (по умолчанию — 10)
canPageboolВключает или отключает разбиение данных на страницы
canSortboolВключает или отключает сортировку данных
ajaxUpdateContainerIdstringИдентификатор элемента-контейнера для сетки, обеспечивающего поддержку AJAX
ajaxUpdateCallbackstringКлиентская функция, вызываемая при завершении AJAX-обновления
fieldNamePrefixstringПрефикс для полей строк запроса (для поддержки нескольких сеток)
pageFieldNamestringИмя поля строки запроса для номера страницы
selectionFieldNamestringИмя поля строки запроса для выбранного номера записи
sortFieldNamestringИмя поля строки запроса для столбца, по которому осуществляется сортировка
sortDirectionFieldNamestringИмя поля строки запроса для направления сортировки

Табл. 2. Параметры метода WebGrid.GetHtml

ИмяТипОписание
tableStylestringКласс таблицы для применения стилей
headerStylestringКласс строки заголовка для применения стилей
footerStylestringКласс строки нижнего колонтитула для применения стилей
rowStylestringКласс строки для применения стилей (только нечетные строки)
alternatingRowStylestringКласс строки записи для применения стилей (только четные строки)
selectedRowStylestringВыбранный класс строки для применения стилей
captionstringСтрока, отображаемая в названии таблицы
displayHeaderboolУказывает, надо ли отображать строку заголовка
fillEmptyRowsboolУказывает, можно ли добавлять в таблицу пустые строки, чтобы соблюсти количество строк на страницу (rowsPerPage)
emptyRowCellValuestringЗначение, используемое для заполнения пустых строк; используется, только когда установлен флаг fillEmptyRows
columnsIEnumerable<WebGridColumn>Модель столбца для настройки рендеринга столбцов
exclusionsIEnumerable<string>Столбцы, исключаемые из процесса автоматического заполнения столбцов
modeWebGridPagerModesРежимы рендеринга элемента управления страницами (по умолчанию — NextPrevious и Numeric)
firstTextstringТекст ссылки на первую страницу
previousTextstringТекст ссылки на предыдущую страницу
nextTextstringТекст ссылки на следующую страницу
lastTextstringТекст ссылки на последнюю страницу
numericLinksCountintКоличество отображаемых ссылок-номеров (по умолчанию — 5)
htmlAttributesobjectСодержит HTML-атрибуты, задаваемые для элемента

Параметр format метода Column позволяет настраивать рендеринг элемента данных.

Предыдущий Razor-код будет осуществлять рендеринг всех свойств для каждой строки (записи), но вы, вероятно, захотите ограничить количество отображаемых полей. Этого можно добиться несколькими способами. Первый (и самый простой) — передать набор полей конструктору WebGrid. Например, показанный ниже код выполняет рендеринг только свойств Name и ListPrice:

var grid = new WebGrid(Model, columnNames:
  new[] {"Name", "ListPrice"});

Указать нужные поля можно и при вызове GetHtml, а не конструктора. Хотя код при этом получается длиннее, у вас появляется возможность передачи дополнительной информации о том, как выполнять рендеринг полей. В следующем примере через свойство header я делаю столбец ListPrice более дружественным к пользователю:

@grid.GetHtml(columns: grid.Columns(
 grid.Column("Name"),
 grid.Column("ListPrice", header:"List Price")
 )
)

Очень часто при рендеринге списка элементов нужно давать возможность пользователям щелкать любой из элементов для перехода в представление Details. Параметр format метода Column позволяет настраивать рендеринг элемента данных. В следующем коде показано, как изменять рендеринг имен, чтобы вывести ссылку для какого-либо элемента в представлении Details; при этом выводится прайс-лист, в котором цены округляются с точностью до двух разрядов после точки, как видно на рис. 2.

@grid.GetHtml(columns: grid.Columns(
 grid.Column("Name", format: @<text>@Html.ActionLink(
            (string)item.Name, "Details", "Product",
            new {id=item.ProductId}, null)</text>),
 grid.Column("ListPrice", header:"List Price",
            format: @<text>@item.ListPrice.ToString("0.00")
            </text>)
 )
)

*

Рис. 2. Базовая сетка с отформатированными полями

Параметр format на самом деле представляет собой Func<dynamic,object> — делегат, который принимает динамический параметр и возвращает объект. Механизм Razor принимает фрагмент, указанный в параметре format, и преобразует его в делегат. Этот делегат принимает динамический параметр с именованным элементом (item), и это переменная item, используемая в данном фрагменте format. Подробнее о том, как работают такие делегаты, см. публикацию Фила Хаака (Phil Haack) в блоге по ссылке bit.ly/h0Q0Oz.

Вызов методов расширения с динамическими параметрами не поддерживается.

Поскольку параметр item имеет тип dynamic, при написании кода вы лишаетесь поддержки IntelliSense или проверок со стороны компилятора (см. статью Александры Русины по динамическим типам в февральском номере по ссылке msdn.microsoft.com/ru-ru/magazine/gg598922). Более того, вызов методов расширения с динамическими параметрами не поддерживается. То есть при вызове методов расширения вы должны использовать статические типы — это и является причиной того, что item.Name приводится к строке, когда я вызываю метод расширения Html.ActionLink в предыдущем коде. В ряде методов расширения, применяемых в ASP.NET MVC, конфликт между dynamic и методами расширения может здорово досаждать (это тем более верно, если вы используете нечто вроде T4MVC — см. bit.ly/9GMoup).

Добавление строгой типизации

Хотя динамическая типизация, вероятно, является хорошим выбором для WebMatrix, свои преимущества есть и у представлений со строгой типизацией. Один из способов получить их — создать производный тип WebGrid<T> (рис. 3). Как видите, это весьма облегченная оболочка!

Рис. 3. Создание производного WebGrid

public class WebGrid<T> : WebGrid
{
  public WebGrid(
    IEnumerable<T> source = null,
    ...(список параметров опущен для краткости)
    : base(
      source.SafeCast<object>(),
      ...(список параметров опущен для краткости)
  { }
  public WebGridColumn Column(
              string columnName = null,
              string header = null,
              Func<T, object> format = null,
              string style = null,
              bool canSort = true)
  {
    Func<dynamic, object> wrappedFormat = null;
    if (format != null)
    {
      wrappedFormat = o => format((T)o.Value);
    }
    WebGridColumn column = base.Column(
                  columnName, header,
                  wrappedFormat, style, canSort);
    return column;
  }
  public WebGrid<T> Bind(
         IEnumerable<T> source,
         IEnumerable<string> columnNames = null,
         bool autoSortAndPage = true,
         int rowCount = -1)
  {
    base.Bind(
         source.SafeCast<object>(),
         columnNames,
         autoSortAndPage,
         rowCount);
    return this;
  }
}

public static class WebGridExtensions
{
  public static WebGrid<T> Grid<T>(
           this HtmlHelper htmlHelper,
           ...(список параметров опущен для краткости)
  {
    return new WebGrid<T>(
      source,
      ...(список параметров опущен для краткости);
  }
}

Что это нам дает? С помощью новой реализации WebGrid<T> я добавил новый метод Column, который принимает Func<T, object> в качестве параметра format, а значит, при вызове методов расширения преобразование типов не потребуется. Кроме того, вы теперь получаете поддержку IntelliSense и проверки компилятором (если в файле проекта включен MvcBuildViews; по умолчанию он отключен).

В ряде методов расширения, применяемых в ASP.NET MVC, конфликт между dynamic и методами расширения может здорово досаждать.

Метод расширения Grid позволяет задействовать преимущества логического распознавания типов компилятором применительно к обобщенным параметрам. Поэтому в данном примере можно написать Html.Grid(Model) вместо new WebGrid<Product>(Model). В любом случае возвращаемым типов является WebGrid<Product>.

Добавление поддержки разбиения на страницы и сортировки

Вы уже видели, что WebGrid предоставляет функциональность разбиения на страницы и сортировки безо всяких усилий с вашей стороны. Кроме того, вы узнали, как сконфигурировать страницу через параметр rowsPerPage (в конструкторе или вызовом вспомогательного метода Html.Grid), чтобы сетка автоматически показывала одну страницу данных и визуализировала элементы управления для навигации между страницами. Однако поведение по умолчанию может оказаться не тем, что вам хотелось бы. Чтобы проиллюстрировать это, я добавил код для рендеринга ряда элементов в источнике данных после рендеринга сетки, как показано на рис. 4.

*

Рис. 4. Несколько элементов в источнике данных

Как видите, передаваемые нами данные содержат полный список продуктов (295 в этом примере, но нетрудно вообразить ситуации, где извлекается куда больше данных). По мере увеличения объема возвращаемых данных вы все больше нагружаете свои сервисы и базы данных, но по-прежнему визуализируете только одну страницу данных. Однако есть подход получше: разбиение на страницы на серверной стороне (server-side paging). В этом случае вы извлекаете лишь те данные, которые нужны для отображения текущей страницы (например, лишь пяти записей).

Первый шаг в реализации для WebGrid разбиения на страницы на серверной стороне — ограничение данных, извлекаемых из источника. Для этого нужно знать, какая страница запрашивается; тогда можно извлечь правильную страницу. Выполняя рендеринг ссылок на страницы, WebGrid повторно использует URL страницы и включает в параметр строки запроса номера страницы, например http://localhost:27617/Product/DefaultPagingAndSorting?page=3 (имя параметра строки запроса настраивается через параметры вспомогательного метода — это удобно, если вы хотите поддерживать на странице разбиение более чем для одной сетки). То есть ваш метод операции может принимать параметр page, и этот параметр будет заполнен значением строки запроса.

Если вы просто измените существующий код, чтобы передавать одну страницу данных в WebGrid, то этот элемент увидит всего одну страницу данных. Поскольку ему не известно о наличии других страниц, он больше не будет визуализировать элементы управления для навигации между страницами. К счастью, в WebGrid есть другой метод, Bind, с помощью которого можно указывать данные. Bind принимает не только данные: у него есть параметр, через который вы передаете общее количество записей, позволяющее вычислять число страниц. Чтобы задействовать этот метод, операцию List нужно обновить так, чтобы она извлекала дополнительную информацию для передачи в представление (рис. 5).

Первый шаг в реализации для WebGrid разбиения на страницы на серверной стороне — ограничение данных, извлекаемых из источника.

Рис. 5. Обновление операции List

public ActionResult List(int page = 1)
{
  const int pageSize = 5;

  int totalRecords;
  IEnumerable<Product> products = productService.GetProducts(
    out totalRecords, pageSize:pageSize, pageIndex:page-1);

  PagedProductsModel model = new PagedProductsModel
                                 {
                                   PageSize= pageSize,
                                   PageNumber = page,
                                   Products = products,
                                   TotalRows = totalRecords
                                 };
  return View(model);
}

Благодаря этой дополнительной информации представление можно обновить для использования метода WebGrid.Bind. При вызове Bind передаются визуализируемые данные и общее количество записей, а также параметру autoSortAndPage присваивается значение false. Этот параметр указывает WebGrid, что разбивать на страницы не требуется, так как об этом позаботится метод-операция List. Для иллюстрации сказанного взгляните на следующий код:

<div>
@{
  var grid = new WebGrid<Product>(null, rowsPerPage: Model.PageSize,
    defaultSort:"Name");
  grid.Bind(Model.Products, rowCount: Model.TotalRows, autoSortAndPage: false);
}
@grid.GetHtml(columns: grid.Columns(
 grid.Column("Name", format: @<text>@Html.ActionLink(item.Name,
   "Details", "Product", new { id = item.ProductId }, null)</text>),
  grid.Column("ListPrice", header: "List Price",
    format: @<text>@item.ListPrice.ToString("0.00")</text>)
  )
 )

</div>

После этих изменений WebGrid вернется к жизни, визуализируя элементы управления навигацией, но разбиение на страницы будет осуществляться сервисом, а не в представлении! Однако при выключенном параметре autoSortAndPage функциональность сортировки ломается. WebGrid использует параметры строк запросов для передачи сортируемых столбцов и направления сортировки, а мы указали ему не выполнять сортировку. Для устранения этой проблемы нужно добавить параметры sort и sortDir к методу операции и передавать их через сервис, чтобы тот выполнял необходимую сортировку, как показано на рис. 6.

Рис. 6. Добавление параметров сортировки к методу операции

public ActionResult List(
           int page = 1,
           string sort = "Name",
           string sortDir = "Ascending" )
{
  const int pageSize = 5;

  int totalRecords;
  IEnumerable<Product> products =
    _productService.GetProducts(out totalRecords,
                                pageSize: pageSize,
                                pageIndex: page - 1,
                                sort:sort,
                                sortOrder:GetSortDirection(sortDir)
                                );

  PagedProductsModel model = new PagedProductsModel
  {
    PageSize = pageSize,
    PageNumber = page,
    Products = products,
    TotalRows = totalRecords
  };
  return View(model);
}

AJAX: изменения на клиентской стороне

WebGrid поддерживает асинхронное обновление содержимого сетки через AJAX. Чтобы задействовать это преимущество, вам нужно лишь убедиться, что у div, содержащего сетку, есть идентификатор, а затем передать этот идентификатор в параметре ajaxUpdateContainerId конструктору сетки. Вам также потребуется ссылка на jQuery, но она уже включена в представление разметки (layout view). Когда ajaxUpdateContainerId указан, WebGrid меняет свое поведение так, чтобы ссылки, относящиеся к разбиению страниц и сортировке, использовали AJAX для обновлений:

<div id="grid">

@{
  var grid = new WebGrid<Product>(null, rowsPerPage: Model.PageSize,
  defaultSort: "Name", ajaxUpdateContainerId: "grid");
  grid.Bind(Model.Products, autoSortAndPage: false, rowCount: Model.TotalRows);
}
@grid.GetHtml(columns: grid.Columns(
 grid.Column("Name", format: @<text>@Html.ActionLink(item.Name,
   "Details", "Product", new { id = item.ProductId }, null)</text>),
 grid.Column("ListPrice", header: "List Price",
   format: @<text>@item.ListPrice.ToString("0.00")</text>)
 )
)

</div>

Хотя встроенная функциональность для использования AJAX весьма хороша, генерация вывода не будет работать при отключении поддержки скриптов. Причина этого в том, что в режиме AJAX компонент WebGrid осуществляет рендеринг анкерных тегов (anchor tags) с href, равным «#», и вводит поведение AJAX через обработчик onclick.

Я всегда стараюсь создавать страницы, функциональность которых корректно сокращается при отключении поддержки скриптов, и, в целом, считаю, что лучший способ добиться этого — применять пропорциональное расширение (т. е. страница работает без поддержки скриптов, а при добавлении этой поддержки функциональность страницы просто расширяется). С этой целью вы можете прибегнуть к WebGrid без AJAX и создать скрипт, показанный на рис.7, для повторного встраивания поведения AJAX.

Рис. 7. Повторное встраивание поведения AJAX

$(document).ready(function () {

  function updateGrid(e) {
    e.preventDefault();
    var url = $(this).attr('href');
    var grid = $(this).parents('.ajaxGrid');
    var id = grid.attr('id');
    grid.load(url + ' #' + id);
  };
  $('.ajaxGrid table thead tr a').live('click', updateGrid);
  $('.ajaxGrid table tfoot tr a').live('click', updateGrid);
 });

Чтобы разрешить применение скрипта только к WebGrid, в этом коде используются jQuery-селекторы, которые идентифицируют элементы с установленным классом ajaxGrid. Этот скрипт устанавливает обработчики события щелчка для ссылок сортировки и разбиения на страницы (они идентифицируются через верхний и нижний заголовки таблицы внутри контейнера сетки), используя jQuery-метод live (api.jquery.com/live). Он устанавливает обработчик событий для существующих и будущих элементов, отвечающих критериям селектора, что удобно, если скрипт будет заменять содержимое.

Метод updateGrid устанавливается как обработчик событий и первое, что он делает, — вызывает preventDefault, чтобы отключить поведение по умолчанию. После этого он получает URL для использования (из атрибута href анкерного тега), а затем выдает AJAX-вызов для загрузки обновленного содержимого в элемент-контейнер. Чтобы применить этот подход, убедитесь, что AJAX-поведение по умолчанию в WebGrid отключено, добавьте класс ajaxGrid в контейнер div и вставьте скрипт с рис. 7.

AJAX: изменения на серверной стороне

Обратите внимание на то, что скрипт использует функциональность jQuery-метода load для изоляции фрагмента от возвращаемого документа. Простой вызов load(‘http://example.com/someurl’) загрузит содержимое, находящееся по данному URL, а вызов load(‘http://example.com/someurl #someId’) будет не только загружать содержимое, но и возвращать фрагмент с идентификатором «someId». Это дублирует AJAX-поведение WebGrid по умолчанию и избавляет вас от обновления серверного кода для добавления поддержки рендеринга части данных; WebGrid будет загружать полную страницу, а затем убирать из нее новую сетку.

В плане быстрого получения AJAX-функциональности это просто отлично, но подразумевает, что вы передаете по сети больше данных, чем необходимо, и потенциально перебираете на сервере больше данных, чем могли бы. К счастью, ASP.NET MVC позволяет весьма легко справиться с этими проблемами. Основная идея в том, чтобы выделить весь рендеринг, общий для запросов с AJAX и без в частичное представление (partial view). Тогда операция List в контроллере сможет выполнять рендеринг либо только частичного представления для вызовов с AJAX, либо полного представления (которое в свою очередь использует частичное представление) для вызовов без AJAX.

Этот подход может оказаться предельно простым, и вам будет достаточно проверять результат метода расширения Request.IsAjaxRequest, вызываемого внутри вашего метода операции. Все будет работать великолепно, если пути кода для AJAX и без AJAX имеют лишь незначительные различия. Однако зачастую эти пути различаются куда значимее (например, при полном рендеринге требуется больше данных, чем при частичном). В этой ситуации вы, вероятно, написали бы AjaxAttribute, чтобы можно было создавать раздельные методы, а затем предоставили бы инфраструктуре MVC выбор нужного метода в зависимости от запроса (с AJAX или без) — точно так же, как работают атрибуты HttpGet и HttpPost. Соответствующий пример см. в моем блоге по ссылке bit.ly/eMlIxU.

WebGrid и механизм представлений Web Forms

До сих пор во всех примерах использовался механизм представлений Razor. В простейшем случае вам не придется что-либо менять для использования WebGrid с механизмом представлений Web Forms (не считая различий в синтаксисе механизма представлений). В предыдущих примерах я показал, как можно настроить рендеринг данных из записей, используя параметр format:

grid.Column("Name",
  format: @<text>@Html.ActionLink((string)item.Name,
  "Details", "Product", new { id = item.ProductId }, null)</text>),

Параметр format на самом деле является делегатом Func, но механизм представлений Razor скрывает это от нас. Но вы вправе сами передавать Func — например, вы могли бы использовать лямбда-выражение:

grid.Column("Name",
  format: item => Html.ActionLink((string)item.Name,
  "Details", "Product", new { id = item.ProductId }, null)),

И теперь вы можете легко задействовать преимущества WebGrid с механизмом представлений Web Forms!

WebGrid поддерживает асинхронное обновление содержимого сетки через AJAX.

Заключение

В этой статье я показал, как с помощью нескольких весьма простых приемов использовать преимущества функциональности WebGrid, не жертвуя строгой типизацией, поддержкой IntelliSense или эффективным разбиением на страницы на серверной стороне. В WebGrid встроена весьма впечатляющая функциональность, которая помогает вам эффективно решать задачи рендеринга табличных данных. Надеюсь, что эта статья позволила вам прочувствовать, как выжать максимум возможного из этого компонента в приложении ASP.NET MVC.

Автор: Стюарт Ликс  •  Иcточник: MSDN Magazine  •  Опубликована: 21.10.2011
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   ASP.NET, MVC, WebGrid.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.