Использование PagedList с ViewModel ASP.Net MVC
Я пытаюсь использовать PagedList в своем приложении ASP.Net, и я нашел этот пример на веб-сайте Microsoft http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application
Как можно использовать PagedList в сложной ситуации, использующей ViewModel? Я пытаюсь добавить PagedList без успеха в пример инструктора, размещенный здесь: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
Проблема заключается в том, что ViewModel состоит из классов, а не простых полей, поэтому я не могу преобразовать результат с помощью метода ToPageList().
Это структура ViewModel:
using System.Collections.Generic;
using ContosoUniversity.Models;
namespace ContosoUniversity.ViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Мне нужно объединить три таблицы в ViewModel и отобразить результат в представлении.
Ответы
Ответ 1
Я изменил код следующим образом:
ViewModel
using System.Collections.Generic;
using ContosoUniversity.Models;
namespace ContosoUniversity.ViewModels
{
public class InstructorIndexData
{
public PagedList.IPagedList<Instructor> Instructors { get; set; }
public PagedList.IPagedList<Course> Courses { get; set; }
public PagedList.IPagedList<Enrollment> Enrollments { get; set; }
}
}
Контроллер
public ActionResult Index(int? id, int? courseID,int? InstructorPage,int? CoursePage,int? EnrollmentPage)
{
int instructPageNumber = (InstructorPage?? 1);
int CoursePageNumber = (CoursePage?? 1);
int EnrollmentPageNumber = (EnrollmentPage?? 1);
var viewModel = new InstructorIndexData();
viewModel.Instructors = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses.Select(c => c.Department))
.OrderBy(i => i.LastName).ToPagedList(instructPageNumber,5);
if (id != null)
{
ViewBag.InstructorID = id.Value;
viewModel.Courses = viewModel.Instructors.Where(
i => i.ID == id.Value).Single().Courses.ToPagedList(CoursePageNumber,5);
}
if (courseID != null)
{
ViewBag.CourseID = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments.ToPagedList(EnrollmentPageNumber,5);
}
return View(viewModel);
}
Вид
<div>
Page @(Model.Instructors.PageCount < Model.Instructors.PageNumber ? 0 : Model.Instructors.PageNumber) of @Model.Instructors.PageCount
@Html.PagedListPager(Model.Instructors, page => Url.Action("Index", new {InstructorPage=page}))
</div>
Надеюсь, это поможет вам!
Ответ 2
Как сказал Крис, причина, по которой вы используете ViewModel, не мешает вам использовать PagedList
.
Вам нужно сформировать коллекцию объектов ViewModel, которые необходимо отправить в представление для подкачки.
Ниже приведено пошаговое руководство о том, как вы можете использовать PagedList
для своих данных viewmodel.
Ваш viewmodel (я сделал простой пример для краткости, и вы можете легко изменить его в соответствии с вашими потребностями.)
public class QuestionViewModel
{
public int QuestionId { get; set; }
public string QuestionName { get; set; }
}
и метод Index вашего контроллера будет что-то вроде
public ActionResult Index(int? page)
{
var questions = new[] {
new QuestionViewModel { QuestionId = 1, QuestionName = "Question 1" },
new QuestionViewModel { QuestionId = 1, QuestionName = "Question 2" },
new QuestionViewModel { QuestionId = 1, QuestionName = "Question 3" },
new QuestionViewModel { QuestionId = 1, QuestionName = "Question 4" }
};
int pageSize = 3;
int pageNumber = (page ?? 1);
return View(questions.ToPagedList(pageNumber, pageSize));
}
И ваш индексный указатель
@model PagedList.IPagedList<ViewModel.QuestionViewModel>
@using PagedList.Mvc;
<link href="/Content/PagedList.css" rel="stylesheet" type="text/css" />
<table>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.QuestionId)
</td>
<td>
@Html.DisplayFor(modelItem => item.QuestionName)
</td>
</tr>
}
</table>
<br />
Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
@Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )
Вот ссылка SO > с моим ответом, в которой есть пошаговое руководство о том, как вы можете использовать PageList
Ответ 3
Для тех, кто пытается это сделать, не изменяя ваши ViewModels И не загружая все ваши записи из базы данных.
Repository
public List<Order> GetOrderPage(int page, int itemsPerPage, out int totalCount)
{
List<Order> orders = new List<Order>();
using (DatabaseContext db = new DatabaseContext())
{
orders = (from o in db.Orders
orderby o.Date descending //use orderby, otherwise Skip will throw an error
select o)
.Skip(itemsPerPage * page).Take(itemsPerPage)
.ToList();
totalCount = db.Orders.Count();//return the number of pages
}
return orders;//the query is now already executed, it is a subset of all the orders.
}
контроллер
public ActionResult Index(int? page)
{
int pagenumber = (page ?? 1) -1; //I know what you're thinking, don't put it on 0 :)
OrderManagement orderMan = new OrderManagement(HttpContext.ApplicationInstance.Context);
int totalCount = 0;
List<Order> orders = orderMan.GetOrderPage(pagenumber, 5, out totalCount);
List<OrderViewModel> orderViews = new List<OrderViewModel>();
foreach(Order order in orders)//convert your models to some view models.
{
orderViews.Add(orderMan.GenerateOrderViewModel(order));
}
//create staticPageList, defining your viewModel, current page, page size and total number of pages.
IPagedList<OrderViewModel> pageOrders = new StaticPagedList<OrderViewModel>(orderViews, pagenumber + 1, 5, totalCount);
return View(pageOrders);
}
Просмотр
@using PagedList.Mvc;
@using PagedList;
@model IPagedList<Babywatcher.Core.Models.OrderViewModel>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div class="container-fluid">
<p>
@Html.ActionLink("Create New", "Create")
</p>
@if (Model.Count > 0)
{
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.First().orderId)
</th>
<!--rest of your stuff-->
</table>
}
else
{
<p>No Orders yet.</p>
}
@Html.PagedListPager(Model, page => Url.Action("Index", new { page }))
</div>
Bonus
Сделайте выше, а затем, возможно, используйте это!
Поскольку этот вопрос касается моделей (просмотра), я собираюсь отдать вам небольшое решение, которое будет не только полезно для подкачки, но и для остальной части вашего приложения, если вы хотите, чтобы ваши объекты были разделены, используется только в репозитории, а остальная часть приложения связана с моделями (которые могут использоваться в качестве моделей представлений).
Repository
В репозитории вашего заказа (в моем случае) добавьте статический метод для преобразования модели:
public static OrderModel ConvertToModel(Order entity)
{
if (entity == null) return null;
OrderModel model = new OrderModel
{
ContactId = entity.contactId,
OrderId = entity.orderId,
}
return model;
}
Ниже вашего класса репозитория добавьте следующее:
public static partial class Ex
{
public static IEnumerable<OrderModel> SelectOrderModel(this IEnumerable<Order> source)
{
bool includeRelations = source.GetType() != typeof(DbQuery<Order>);
return source.Select(x => new OrderModel
{
OrderId = x.orderId,
//example use ConvertToModel of some other repository
BillingAddress = includeRelations ? AddressRepository.ConvertToModel(x.BillingAddress) : null,
//example use another extension of some other repository
Shipments = includeRelations && x.Shipments != null ? x.Shipments.SelectShipmentModel() : null
});
}
}
И затем в вашем методе GetOrderPage
:
public IEnumerable<OrderModel> GetOrderPage(int page, int itemsPerPage, string searchString, string sortOrder, int? partnerId,
out int totalCount)
{
IQueryable<Order> query = DbContext.Orders; //get queryable from db
.....//do your filtering, sorting, paging (do not use .ToList() yet)
return queryOrders.SelectOrderModel().AsEnumerable();
//or, if you want to include relations
return queryOrders.Include(x => x.BillingAddress).ToList().SelectOrderModel();
//notice difference, first ToList(), then SelectOrderModel().
}
Позвольте мне объяснить:
Статический метод ConvertToModel
может быть доступен любым другим репозиторием, как используется выше, я использую ConvertToModel
из некоторого AddressRepository
.
Класс/метод расширения позволяет преобразовать объект в модель. Это может быть IQueryable или любой другой список, коллекция.
Теперь придет волшебство: если вы выполнили запрос ПЕРЕД вызовом расширения SelectOrderModel()
, includeRelations
внутри расширения будет истинным, потому что source
НЕ является типом запроса базы данных (не linq-to-sql IQueryable
). Когда это верно, расширение может вызывать другие методы/расширения в вашем приложении для преобразования моделей.
Теперь с другой стороны: вы можете сначала выполнить расширение, а затем продолжить фильтрацию LINQ. Фильтрация будет происходить в базе данных в конечном итоге, потому что вы еще не сделали .ToList()
, а расширение - это всего лишь слой с вашими запросами. Linq-to-sql в конце концов узнает, какая фильтрация применяется в базе данных. inlcudeRelations
будет ложным, чтобы он не вызывал другие методы С#, которые SQL не понимает.
Сначала выглядит сложнее, расширения могут быть чем-то новым, но это действительно полезно. В конце концов, когда вы установите это для всех репозиториев, просто добавьте .Include()
extra.
Ответ 4
Я понял, как это сделать. Я создавал приложение, очень похожее на пример/учебник, который вы обсуждали в своем исходном вопросе.
Вот фрагмент кода, который работал у меня:
int pageSize = 4;
int pageNumber = (page ?? 1);
//Used the following two formulas so that it doesn't round down on the returned integer
decimal totalPages = ((decimal)(viewModel.Teachers.Count() /(decimal) pageSize));
ViewBag.TotalPages = Math.Ceiling(totalPages);
//These next two functions could maybe be reduced to one function....would require some testing and building
viewModel.Teachers = viewModel.Teachers.ToPagedList(pageNumber, pageSize);
ViewBag.OnePageofTeachers = viewModel.Teachers;
ViewBag.PageNumber = pageNumber;
return View(viewModel);
Я добавил
using.PagedList;
для моего контроллера, как указано в руководстве.
Теперь, на мой взгляд, мои операторы и т.д. наверху, ПРИМЕЧАНИЕ. Я не изменил свой оператор модели.
@model CSHM.ViewModels.TeacherIndexData
@using PagedList;
@using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
а затем в нижней части, чтобы построить мой выгружаемый список, я использовал следующее и, похоже, работает. Я еще не создал функциональность для текущей сортировки, показывая связанные данные, фильтры и т.д., Но я не думаю, что это будет сложно.
Page @ViewBag.PageNumber of @ViewBag.TotalPages
@Html.PagedListPager((IPagedList)ViewBag.OnePageofTeachers, page => Url.Action("Index", new { page }))
Надеюсь, что это сработает для вас. Дайте мне знать, если это работает!
Ответ 5
Тот факт, что вы используете модель представления, не имеет никакого отношения. Стандартный способ использования PagedList
- хранить "одну страницу элементов" как переменную ViewBag
. Все, что вам нужно определить, - это то, что коллекция представляет собой то, что вы будете пейджинговым. Вы не можете логически печатать несколько коллекций одновременно, поэтому, если вы выбрали Instructors
:
ViewBag.OnePageOfItems = myViewModelInstance.Instructors.ToPagedList(pageNumber, 10);
Затем остальная часть стандартного кода работает так, как всегда.