如何在 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 项目中分离出来

asp.net mvc实体框架代码先将外键与不同名称关联

如何使用代码优先实体框架在 ASP.Net MVC3 中重新加载多对多导航属性

似乎无法使用 ASP.NET MVC 和实体框架返回我的模型