Reputation: 455
I am working on an online library using ASP.NET MVC. This is my view model for the library management page:
public class ManageViewModel
{
public IPagedList<ManageBookViewModel> WholeInventory;
public IPagedList<ManageBookViewModel> CurrentInventory;
public bool OldInventoryIsShown { get; set; } = false;
}
In the corresponding view I have a checkbox for whether or not to show the old inventory and a local variable modelList
, which I would like to set to Model.WholeInventory
if the checkbox is checked and to Model.CurrentInventory
otherwise. I use modelList
to display a table with all the books and I would need its value to be reset every time I (un)check the checkbox in order for the list to be properly displayed.
Is this possible? How would I go about doing this?
In my view I currently have:
<label class="switch">
<input id="OldInventoryIsShown" name="OldInventoryIsShown" type="checkbox" />
<span class="slider round"></span>
</label>
@{
var modelList = Model.OldInventoryIsShown ? Model.WholeInventory : Model.CurrentInventory;
}
@using (Html.BeginForm())
{
<table id="bookInventory" class="table table-hover">
<thead>
<tr>
<th>Author</th>
<th>Title</th>
....
</tr>
</thead>
@foreach (var entry in modelList)
{
<tr>
<td>@Html.DisplayFor(modelItem => entry.Author)</td>
<td>@Html.DisplayFor(modelItem => entry.Title)</td>
....
</tr>
}
</table>
<p>Page @(modelList.PageCount < modelList.PageNumber ? 0 : modelList.PageNumber) of @modelList.PageCount</p>
@Html.PagedListPager(modelList, page => Url.Action("Manage", page }))
}
The controller action:
public ActionResult Manage(int? page)
{
var wholeInventory = _bookService.GetBooksIncludingDisabled().Select(b => Mapper.Map<Book, ManageBookViewModel>(b));
var currentInventory = _bookService.GetBooks().Select(b => Mapper.Map<Book, ManageBookViewModel>(b));
int pageSize = 3;
int pageNumber = page ?? 1;
var model = new ManageViewModel
{
WholeInventory = wholeInventory.ToPagedList(pageNumber, pageSize),
CurrentInventory = currentInventory.ToPagedList(pageNumber, pageSize)
};
return View(model);
}
Models:
Book.cs
public class Book
{
public int BookId { get; set; }
[Required]
[MinLength(1)]
public string Title { get; set; }
[Required]
[MinLength(1)]
public string Author { get; set; }
....
public bool IsDisabled { get; set; } = false;
public virtual ICollection<UserBook> UserBooks { get; set; }
}
ManageBookViewModel.cs
public class ManageBookViewModel
{
public int BookId { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Enter the book title")]
public string Title { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Enter the book author.")]
public string Author { get; set; }
....
public bool IsDisabled { get; set; }
}
Upvotes: 1
Views: 2311
Reputation:
Your ManageViewModel
needs to include only one property for the paged list and it should be IPagedList<Book>
(see explanation below)
public class ManageViewModel
{
public IPagedList<Book> Inventory;
[Display(Name = "Include old inventory")]
public bool OldInventoryIsShown { get; set; }
... // any other search/filter properties
}
and your view needs to include the checkbox inside the <form>
element, and the form should be making a GET to your controller method. Then you also need to include the current value of OldInventoryIsShown
as a route value in the @Html.PagedListPager()
method so that the current filter is retained when paging.
@model ManageViewModel
...
@using (Html.BeginForm("Manage", "ControllerName", FormMethod.Get))
{
@Html.CheckBoxFor(m => m.OldInventoryIsShown)
@Html.LabelFor(m => m.OldInventoryIsShown)
... // any other search/filter properties
<input type="submit" value="search" />
}
<table id="bookInventory" class="table table-hover">
....
</table>
<p>Page @(modelList.PageCount < modelList.PageNumber ? 0 : modelList.PageNumber) of @modelList.PageCount</p>
@Html.PagedListPager(modelList, page =>
Url.Action("Manage", new { page = page, oldInventoryIsShown = Model.OldInventoryIsShown })) // plus any other search/filter properties
Finally in the controller method you need a parameter for the value of the bool
property an modify your query based on that value.
public ActionResult Manage(int? page, bool oldInventoryIsShown)
{
int pageSize = 3;
int pageNumber = page ?? 1;
IQueryable<Book> inventory = db.Books;
if (!oldInventoryIsShown)
{
inventory = inventory.Where(x => !x.IsDisabled);
}
ManageViewModel model = new ManageViewModel
{
Inventory = inventory.ToPagedList(pageNumber, pageSize),
OldInventoryIsShown = oldInventoryIsShown
};
return View(model);
}
You current controller code is terribly inefficient. Lets assume your table has 10,000 Book
records, and 5,000 of those are 'disabled' (archived). You current code first gets all 10,0000 records and adds them to memory. Then you map all then to a view model. Then you call another query to get another 5,0000 records (which are just duplicates of what you already have), which you add to memory and map to a view model. But all you want in the view is 3 records (the value of pageSize
) so you have done thousands of times of extra unnecessary processing.
In your case, there is no need for a view model (although if you did need one, you would use the StaticPagedList
methods - refer this answer for an example). Your query should be using your db context to generate an IQueryable<Book>
so that only the results you need are returned from the database (internally the ToPagedList()
method uses .Skip()
and .Take()
on IQueryable<T>
)
Upvotes: 2