Search code examples
c#asp.net-mvcpaginationfiltering

Passing filter as object from View to Controller


I'm trying to implement filtering and paging in a MVC controller. I pass information to View using ViewBag.

Filter class is this:

public class Criteria
{
    public int? SnapshotKey { get; set; }
    public string Delq { get; set; }

    public override string ToString()
    {
        string[] Ares = new string[2];
        if (SnapshotKey.HasValue)
            Ares[0] = "SnapshotKey=" + SnapshotKey.ToString();
        if (!String.IsNullOrWhiteSpace(Delq))
            Ares[1] = "Delq=" + Delq;

        return String.Join("&", Ares.Where(s => !string.IsNullOrWhiteSpace(s)));
    }
}

My controller method is like this:

public ActionResult Search(Criteria filter, string filterString, 
                           int pageNo = 1, int pageSize = 5)
{
    using (DemoDBEntities db = new DemoDBEntities())
    {
        var list = db.BMH.AsQueryable();
        if (filter != null)
        {
            if (filter.SnapshotKey.HasValue)
                list = list.Where(r => r.SnapshotKey == filter.SnapshotKey.Value);
            if (!String.IsNullOrWhiteSpace(filter.Delq))
                list = list.Where(r => r.Delq == filter.Delq);
        }
        list = list.OrderBy(r=>r.SnapshotKey).Skip((pageNo - 1) * pageSize).Take(pageSize);
        ViewBag.Criteria = filter.ToString();
        ViewBag.CriteriaString = filterString;
        ViewBag.PageNo = pageNo;
        return View(list.ToList());
    }
}

AFAIK, I cannot pass ViewBag as an object to controller, that's why I used filter.ToString() to store the current filter.

In View, I have below link to go to specific page, while preserving current filter.

@Html.ActionLink("Next Page", "Search", new { filter = ViewBag, filterString = ViewBag.CriteriaString, pageNo = ViewBag.PageNo + 1, pageSize = 5 })

So when coming back from View, I get current filter as string. Now in controller I need to convert string to Criteria class. It is doable but I'm looking for a more decent way to do what I need.


Solution

  • The value of filterString in the Search() method will be a string in the format name=value&name=value... so you could first using String.Split() (on the & character) to create an array of name=value items, and then split again (on the = character) to get the property names and values, but that is all getting messy and it would be easier to just build the whole query string and have it bind directly to your Criteria model.

    Change the model to include all properties, including pageNo and pageSize`

    public class Criteria
    {
        public Criteria() // perhaps add some defaults?
        {
            PageNo = 1;
            PageSize = 5;
        }
        public int? SnapshotKey { get; set; }
        public string Delq { get; set; }
        public int PageNo { get; set; }
        public int PageSize { get; set; }
        .... // see method below
    }
    

    Then to make it flexible and allow you to add more 'criteria' properties, use reflection to build the query string

        public string ToQueryString(int pageIncrement)
        {
            List<string> propValues = new List<string>();
            foreach(var prop in GetType().GetProperties())
            {
                var name = prop.Name;
                var value = prop.GetValue(this);
                if (name == "PageNo")
                {
                    value == (int)value + pageIncrement;
                }
                if (value != null)
                {
                    propValues .Add(String.Format("{0}={1}", name, value));
                }
            }
            return "?" + String.Join("&", propValues);
        }
    

    The code in the controller will then be

    public ActionResult Search(Criteria filter)
    {
        using (DemoDBEntities db = new DemoDBEntities())
        {
            var list = db.BMH.AsQueryable();
            if (filter != null)
            {
                if (filter.SnapshotKey.HasValue)
                    list = list.Where(r => r.SnapshotKey == filter.SnapshotKey.Value);
                if (!String.IsNullOrWhiteSpace(filter.Delq))
                    list = list.Where(r => r.Delq == filter.Delq);
            }
            list = list.OrderBy(r => r.SnapshotKey).Skip((filter.PageNo - 1) * pageSize).Take(filter.PageSize);
            ViewBag.Criteria = filter;
            return View(list.ToList());
        }
    }
    

    and then in the view

    <a href="@Url.Action("Search")@ViewBag.Criteria.ToQueryString(-1)">Previous</a>
    <a href="@Url.Action("Search")@ViewBag.Criteria.ToQueryString(1)">Next</a>
    

    Note also that you could just use

    @Html.ActionLink("Next", "Search", (yourAssembly.Criteria)ViewBag.Criteria)
    

    assuming that Criteria contains only simple properties, meaning the ToQueryString() method is not required. However you would need to increment/decrement the value of the PageNo property before using ActionLink(), for example

    @{ var criteria = (yourAssembly.Criteria)ViewBag.Criteria; }
    @{ criteria.PageNo = @criteria.PageNo - 1; }
    @Html.ActionLink("Previous", "Search", criteria)
    @{ criteria.PageNo = @criteria.PageNo + 2; }
    @Html.ActionLink("Next", "Search", criteria)