web-dev-qa-db-fra.com

Utilisation d'une liste paginée avec un MVC ViewModel ASP.Net

J'essaie d'utiliser une PagedList dans mon application ASP.Net et j'ai trouvé cet exemple sur le site Web de Microsoft http://www.asp.net/mvc/tutorials/getting-started-with-ef-using -mvc/tri-filtrage-et-paging-avec-l'entité-cadre-dans-une-asp-net-mvc-application

Comment est-il possible d'utiliser un PagedList dans une situation complexe qui utilise un ViewModel? J'essaie d'ajouter une liste paginée sans succès à l'exemple de l'instructeur posté ici: 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

Le problème est que ViewModel est composé de classes et non de champs simples. Je ne peux donc pas convertir le résultat avec la méthode ToPageList ().

Voici la structure 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; }
    }
}

Je dois joindre les trois tables dans le ViewModel et afficher le résultat dans une vue.

25
ltedone

J'ai modifié le code comme suit:

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; }
    }
}

Manette

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);
}

Vue

<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>

J'espère que cela vous aiderait !!

13
ltedone

Pour tous ceux qui essaient de le faire sans modifier vos ViewModels ET ne pas charger tous vos enregistrements à partir de la base de données.

référentiel

    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.
    }

contrôleur

    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);
    }

Voir

@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>

Prime

Faites ci-dessus d'abord, alors peut-être utiliser ceci!

Puisque cette question concerne les modèles (voir), je vais vous donner une petite solution qui ne sera pas seulement utile pour la pagination, mais pour le reste de votre application si vous souhaitez que vos entités soient séparées, uniquement utilisées dans. dans le référentiel et que le reste de l’application traite des modèles (qui peuvent être utilisés comme modèles de vue).

référentiel

Dans votre référentiel de commandes (dans mon cas), ajoutez une méthode statique pour convertir un modèle:

public static OrderModel ConvertToModel(Order entity)
{
    if (entity == null) return null;
    OrderModel model = new OrderModel
    {
        ContactId = entity.contactId,
        OrderId = entity.orderId,
    }
    return model;
}

Sous votre classe de référentiel, ajoutez ceci:

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
        });
    }
}

Et ensuite dans votre méthode 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().
    }

Laissez-moi expliquer:

La méthode statique ConvertToModel peut être accédée par n’importe quel autre référentiel, comme utilisé ci-dessus, j’utilise ConvertToModel à partir de AddressRepository.

La classe/méthode d'extension vous permet de convertir une entité en un modèle. Cela peut être IQueryable ou toute autre liste, collection .

Maintenant, voici la magie: Si vous avez exécuté la requête AVANT d’appeler l’extension SelectOrderModel(), includeRelations dans l’extension sera vraie car le source n’est PAS un type de requête de base de données ( pas un linq-to-sql IQueryable). Lorsque cela est vrai, l'extension peut appeler d'autres méthodes/extensions dans votre application pour convertir des modèles.

Maintenant de l’autre côté: Vous pouvez d’abord exécuter l’extension, puis poursuivre le filtrage LINQ. le filtrage finira par se produire dans la base de données, car vous n'avez pas encore effectué de .ToList(), l'extension est simplement une couche de traitement de vos requêtes. Linq-to-sql finira par savoir quel filtrage appliquer dans la base de données. La inlcudeRelations sera fausse de sorte qu'elle n'appelle pas d'autres méthodes c # que SQL ne comprend pas.

Cela a l'air compliqué au début, les extensions sont peut-être quelque chose de nouveau, mais c'est vraiment utile. Finalement, lorsque vous avez configuré cela pour tous les référentiels, simplement un .Include() extra chargera les relations.

23
CularBytes

Comme Chris l'a suggéré, la raison pour laquelle vous utilisez ViewModel ne vous empêche pas d'utiliser PagedList. Vous devez former une collection d'objets ViewModel à envoyer à la vue pour la pagination.

Voici un guide étape par étape sur la façon d'utiliser PagedList pour vos données de modèle de vue.

Votre modèle de vue ( J'ai pris un exemple simple pour la brièveté et vous pouvez le modifier facilement pour l'adapter à vos besoins. )

public class QuestionViewModel
{
        public int QuestionId { get; set; }
        public string QuestionName { get; set; }
}

et la méthode Index de votre contrôleur sera quelque chose comme

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));
}

Et votre vue d'index

@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 }) )

Voici le lien SO avec ma réponse qui contient un guide étape par étape sur la façon d'utiliser PageList

16
Dennis R

J'ai compris comment faire cela. Je construisais une application très similaire à l'exemple/tutoriel dont vous avez parlé dans votre question initiale.

Voici un extrait du code qui a fonctionné pour moi:

        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);

J'ai ajouté

using.PagedList;

à mon contrôleur comme le dit le tutoriel.

Maintenant, à mon avis, mes déclarations d'utilisation, etc. en haut, NOTE: je n'ai pas changé ma déclaration de modèle d'utilisation.

@model CSHM.ViewModels.TeacherIndexData
@using PagedList;
@using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />

et puis en bas pour construire ma liste paginée, j'ai utilisé ce qui suit et cela semble fonctionner. Je n'ai pas encore intégré la fonctionnalité pour le tri en cours, montrant les données associées, les filtres, etc. mais je ne pense pas que ce sera si difficile.

Page @ViewBag.PageNumber of @ViewBag.TotalPages

@Html.PagedListPager((IPagedList)ViewBag.OnePageofTeachers, page => Url.Action("Index", new { page }))

J'espère que cela fonctionne pour vous. Laissez-moi savoir si cela fonctionne!!

3
dave317

Le fait que vous utilisiez un modèle d'affichage n'a pas d'incidence. La manière habituelle d’utiliser PagedList consiste à stocker "une page d’éléments" en tant que variable ViewBag. Tout ce que vous avez à déterminer, c'est quelle collection constitue ce sur quoi vous allez paginer. Vous ne pouvez pas logiquement mettre en page plusieurs collections en même temps, donc si vous avez choisi Instructors:

ViewBag.OnePageOfItems = myViewModelInstance.Instructors.ToPagedList(pageNumber, 10);

Ensuite, le reste du code standard fonctionne comme toujours.

1
Chris Pratt