如何在 Asp.net MVC 和实体框架中分页时应用过滤器?
Posted
技术标签:
【中文标题】如何在 Asp.net MVC 和实体框架中分页时应用过滤器?【英文标题】:How to I apply filter while paginating in Asp.net MVC and entity Framework? 【发布时间】:2017-07-15 23:18:39 【问题描述】:我有一个使用 ASP.NET MVC 框架编写的 Web 应用程序。在我的Homecontroller
中,我有一个名为Index
的操作,它响应Get
请求。在此操作中,我使用IPagedList
库创建页面以将记录分成多个页面。我的Index@HttpGet
看起来像这样
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);
我还有一个名为Index
的操作,它响应Post
应用一些过滤器的请求。所以在Post
请求中我做了这样的事情
[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);
这也可以正常工作,除非用户更改页面然后过滤器休息。
这是我的演示课的样子
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;
如何在保留过滤器的同时允许用户使用页面?
这是我的过滤器虚拟机
public class TasksFiltersViewModel
public int Status get; set;
视图是这样的
@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 ))
【问题讨论】:
也许使用cookies? 同时命名DbContext
实例connection
不是我愿意接受的... .
您需要展示您的视图,我假设它有一个发布到Index()
方法的表单。它应该有FormMethod.Get
,所以它回发到 GET 方法,并且该方法包含过滤器的参数
同时显示您的TasksFiltersViewModel
模型
@StephenMuecke 我用你的要求更新了我的问题
【参考方案1】:
您的表单需要回发到 GET 方法,并且该方法需要包含过滤器属性的参数。您在视图中的PagedListPager
代码还需要包含这些过滤器属性,以便在您导航到下一页/上一页时保留它们。注意Index()
POST方法没有使用,可以删除。
让您的模型包含过滤器属性的复杂对象以及绑定时的额外复杂性,因此首先将您的模型更改为
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;
然后将Index()
方法改为
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 );
并将视图更改为
// 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
【讨论】:
谢谢。不必将每个过滤器作为参数传递(我有 4 个过滤器)没有办法将 TasksFiltersViewModel 模型传递给我的 Index@HttpGet 方法吗? TasksFiltersViewModel 已经封装了所有的过滤器。 如果您有很多过滤器属性,您可以将签名修改为public ActionResult Index(int? id, [Bind(Prefix = "Form")]TasksFiltersViewModel filters)
- 注意Bind
属性 - 然后在PagedListPager
中 - 它需要是new status = Model.Form.Status
等等。请注意,TasksFiltersViewModel
中的所有属性都应该可以为空,以便更轻松地构建查询。然后将Presenter
构造函数修改为Form = filters,
我现在遇到的一个小问题。使用[Bind(Prefix = "Form")]TasksFiltersViewModel filters)
非常棒,因为我从双向绑定中受益。但是,这将破坏来自寻呼机 URL 的参数。用户没有前缀“表格”。关于如何解决这个问题的任何想法?
你可以为它添加一个额外的参数 - [Bind(Prefix = "Form")]TasksFiltersViewModel filters, int? page)` 或设置 PageNumber
属性(我认为这就是它的用途 - 所以它真的应该命名为 Page
以匹配 PagedList
使用的名称)
我猜有没有办法绑定一个可选的前缀?所以请求中的参数“Form.Status”和“Status”将绑定到“Status”属性。这样我就可以满足这两个请求【参考方案2】:
我认为更好的方法应该是将ViewBag
中的过滤器传递回视图。
您可以制作如下内容:
@Html.PagedListPager(
Model.Tasks, id =>
Url.Action("Index", new id,
Status = ViewBag.Status , AnotherFilterValue = ViewBag.AnotherFilterValue, ... ))
但请记住测试 ViewBag.Status
的空值。如果它确实有一个值,请将其放入路由参数列表中,否则设置一个默认值ActionLink
。
然后在 POST 操作中,您期望一个可为空的 int,如下所示:
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);
【讨论】:
是的,这应该可以。如果我看到这个,我就不会写答案了:/ @gldraphael,这不起作用是有原因的,包括表单中的 OP 代码不会绑定到这些参数。如果有多个过滤器属性,则需要使用非常丑陋的if/elseif
语句。
@StephenMuecke 我明白...... OP 缺少的是没有传递过滤器参数。一旦 OP 知道他将能够解决他的问题:)。我感谢您回答中的详细信息 +1
我也讨厌代码中的许多if/else
语句,但他可以通过ViewBag
中的过滤器模型并进行转换。我认为这种情况在他必须传递两个模型时很有用,第一个用于视图中显示的数据,另一个用于过滤器。如果他不想将过滤器与其他数据封装在一个模型中,我认为这是推荐的……【参考方案3】:
有两种方法可以做到这一点。
快捷方式:Cookie。
只需为过滤器选项设置一个 cookie。对于需要过滤的操作,您需要做的就是读取 cookie 并进行相应的过滤。 我不喜欢 cookie 和会话,并且会避免使用它们。但有时它可能会成为您所需要的。
使用 GET 参数
在您的示例中,您使用了POST
进行过滤。每次单击链接时,它都会触发GET
请求,而不是POST
。所以过滤不会发生。棘手的部分是确保每次都设置GET
参数。可以创建一个类似于Html.Action()
的自定义扩展来开始。如果您要在多个操作中检查过滤器选项,请考虑使用操作Filter
。
【讨论】:
以上是关于如何在 Asp.net MVC 和实体框架中分页时应用过滤器?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用trackerenableddbcontext在asp.net mvc5和代码中的实体框架中实现审计跟踪
如何在 Asp.Net 5 (MVC 6) 中使用实体框架 6.x
ASP.NET 5 ,想要将实体框架从 Web 项目中分离出来