问题
I have a web app that is written using ASP.NET MVC framework. In my Homecontroller
I have an action called Index
which responds to a Get
request. In this action, I create pages using IPagedList
library to break the records into multiple pages. My Index@HttpGet
looks something like this
public ActionResult Index(int? id)
{
using(var connection = new Context())
{
int pageNumber = (id ?? 1);
var presenter = new Presenter
{
Presenter = pageNumber,
Tasks = connection.Tasks.ToPagedList(pageNumber, 30),
Form = new TasksFiltersViewModel()
}
return View(presenter);
}
}
I also have an action called Index
that respond to the Post
request which apply some filters. So in the Post
request I do something like this
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(Presenter model)
{
int pageNumber = (id ?? 1);
if (ModelState.IsValid)
{
using(var connection = new Context())
{
model.Tasks = connection.Tasks
.Where(task => task.Status == 5)
.ToPagedList(pageNumber, 30);
}
}
return View(model);
}
This also work fine unless the user changed the page then the filters are rest.
Here is how my presentation class looks like
public class Presenter
{
public IPagedList<Task> Tasks { get; set; }
public TasksFiltersViewModel Form { get; set; }
public int PageNumber { get; set; }
public IEnumerable<SelectListItem> Statuses { get; set; }
}
How can I allow the users to use the pages while keeping the filters?
Here is my filters VM
public class TasksFiltersViewModel
{
public int Status { get; set; }
}
The view looks like this
@using (Html.BeginForm(\"Index\", \"Tasks\", FormMethod.Post, new { @class = \"form-horizontal\" }))
{
@Html.AntiForgeryToken()
<div class=\"form-group\">
@Html.LabelFor(m => m.Form.Status, new { @class = \"control-label col-sm-3\" })
<div class=\"col-sm-9\">
@Html.DropDownListFor(m => m.Form.Status, Model.Statuses, new { @class = \"form-control\" })
@Html.ValidationMessageFor(m => m.Form.Status, \"\", new { @class = \"text-danger\" })
</div>
</div>
<div class=\"row\">
<div class=\"col-sm-9 col-md-push-3\">
<div>
<button type=\"submit\" class=\"btn btn-default\">Filter</button>
</div>
</div>
</div>
}
foreach (var task in Model.Tasks)
{
<tr>
<td>@task.Name</td>
<td>@task.Type</td>
<td>@Html.ActionLink(\"Edit\", \"Details\", \"Task\", new { @id = task.Id }, new { @class = \"btn btn-primary btn-sm\" })</td>
</tr>
}
@Html.PagedListPager(Model.Tasks, id => Url.Action(\"Index\", new { id }))
回答1:
Your form needs to post back to the GET method, and that method needs to include parameters for your filter properties. Your PagedListPager
code in the view also need to include those filter properties so they are retained when you navigate to the next/previous page. Note that the Index()
POST method is not used and can be deleted.
Having your model contain a complex object to the filter properties and extra complexity when binding, so start by changing your model to
public class Presenter
{
public IPagedList<Task> Tasks { get; set; }
public int? Status { get; set; } // note nullable
... // add any other properties of TasksFiltersViewModel
public int PageNumber { get; set; }
public IEnumerable<SelectListItem> Statuses { get; set; }
}
Then change the Index()
method to
public ActionResult Index(int? id, int? status) // add any other parameters your filtering on
{
int pageNumber = (id ?? 1);
var tasks = db.Tasks; // IQueryable<Task>
if (status.HasValue)
{
tasks = tasks.Where(x => x.Status == status.Value)
}
if (otherParametersHaveValue)
{
tasks = tasks.Where(....);
}
Presenter model = new Presenter()
{
PageNumber = id ?? 1,
Status = status,
.... // set any other filter properties from the parameters
Statuses = new SelectList(...),
Tasks = tasks.ToPagedList(pageNumber, 30)
};
return View(model );
}
and change the view to
// Make the form a GET
@using (Html.BeginForm("Index", "Tasks", FormMethod.Get, new { @class = "form-horizontal" }))
{
....
// Modify expression based on revised model properties
@Html.DropDownListFor(m => m.Status, Model.Statuses, ...)
}
....
// Add filter parameters to url so they are retained
@Html.PagedListPager(Model.Tasks, id => Url.Action("Index", new { id, status = Model.Status })) // add other filter properties as required
回答2:
I think a better way should be passing back to the view the filters in ViewBag
.
You can make something like below:
@Html.PagedListPager(
Model.Tasks, id =>
Url.Action("Index", new { id,
Status = ViewBag.Status , AnotherFilterValue = ViewBag.AnotherFilterValue, ... }))
But keep in mind to test ViewBag.Status
for null value. If it does have a value put it in the route parameter list else set a default ActionLink
.
Then inside the POST action you expect a nullable int like below:
public ActionResult Index(int? id, int? status, ...)
{
int pageNumber = (id ?? 1);
if (ModelState.IsValid)
{
using(var connection = new Context())
{
if(status != null)
{
ViewBag.Status = status.value;
model.Tasks = connection.Tasks
.Where(task => task.Status == status.value)
.ToPagedList(pageNumber, 30);
}
else
{
model.Tasks = connection.Tasks
.ToPagedList(pageNumber, 30);
}
}
}
return View(model);
}
回答3:
Two ways to do this.
The quick way: Cookies.
Just set a cookie for the filter options. On the actions where filtering is required, all you need to do is read the cookies and filter accordingly.
I dislike cookies and sessions, and would avoid them. But sometimes it could turn out to be what you need.
Use GET parameters
In your example you've used POST
to filter. Each time you click on a link, it fires a GET
request, not a POST
. So the filtering doesn't happen. The tricky part is making sure the GET
parameters are set each time. Could create a custom extension similar to Html.Action()
to start with. If you'll be checking the filter options in multiple actions, consider using an action Filter
.
来源:https://stackoverflow.com/questions/42450257/how-to-i-apply-filter-while-paginating-in-asp-net-mvc-and-entity-framework