ASP.net MVC3: Advanced search with paging
I'm relatively new to asp.net and MVC 3, but for most problems they have been pretty easy to solve, until a recent issue has caused me all sort of annoyance.
The web app that I'm creating holds a lot of data; at least 10,000 rows of data on products alone, so obviously two important things are required for treatment of the data:
Paging, without this I think the little hamster in the server wouldn't do to well!
Search, more important an advance search, with plenty of filters, so that the users can get too their content without having to troll through thousands of products.
Now helpfully pagination and searching, alone is quite easy, in fact together it's relatively easy, but I found that (maybe down to inexperience) that it wasn't as easy as I had hoped.
So using the asp.net tutorials I got about creating a search function. There are about 5 or 6 filters, 2 of which are drop down that are region and OEM.
So following the My First MVC app tutorial I used the method which was more or less multiple IF's.
As you can imagine this turned into a large method, but I thought "Hey I can re factor this later!" The issues with this method shown in the tutorial is that
Horrible long chunks of code
Finally all the rows are on show before you search
But anyway I carried on as is; it did the job for now.
So, now the task of paging the data. Again back to the MS ASP.net tutorials for the answer. So the PagedList module is great and works well! And the filters worked.... until I went to page 2,3,4 etc...
Now the tutorial does have an answer for this, just save all the variables passed through into the search into variables that retain that data when a new page is loaded. This may work for one search term, but for 5 or 6 it horrible!!
So what now!? Well I found this tutorial which actually used the strengths of MVC. Using a ViewModel, all of the search terms can be stored in there, which meant less bulk when passing around parameters.
What this also means is that I can use LINQ for filtering through the data, which is great as it’s far more compact then multiple IF’s and (as I’m lead to believe) faster! So here is my answer to my large filter and pagination conundrum!
public class SearchViewModel { public int? Page { get; set; } public string blah { get; set; } public string blah1 { get; set; } public string blah2 { get; set; } public string blah3 { get; set; } public string blah4 { get; set; } public IPagedList SearchResults { get; set; } public string SearchButton { get; set; } }
public ActionResult Index(SearchViewModel model) { var RegionList = new List(); var RegionQry = from d in db.places orderby d.PlacesRegion select d.PlcaesRegion; RegionList.AddRange(RegionQry.Distinct()); ViewBag.Location = new SelectList(RegionList); var OEMList = new List(); var OEMQry = from d in db.Products orderby d.OEMName select d.OEMName; OEMList.AddRange(OEMQry.Distinct()); ViewBag.OEM = new SelectList(OEMList); if (!string.IsNullOrEmpty(model.SearchButton) || model.Page.HasValue) { var results = from p in db.Products where (string.IsNullOrEmpty(model.blah) || p.bleep.LocationRegion.Contains(model.blah)) && (string.IsNullOrEmpty(model.blah1) || p.bleep1.Contains(model.blah1)) && (string.IsNullOrEmpty(model.blah2) || p.bleep2.Contains(model.blah2)) && (string.IsNullOrEmpty(model.blah3) || p.bleep3.Contains(model.OemName)) && (string.IsNullOrEmpty(model.blah4) || p.bleep4.Contains(model.blah4)) orderby p.blah2 select p; var pageIndex = model.Page ?? 1; model.SearchResults = results.ToPagedList(pageIndex, 25); } return View(model); }
For the HTML the code is more or less the same as the tutorials (using label for (model =>model.blah1) sort of thing. The one problem I had with this method was the drop down boxes. I'd originally used this:
@Html.DropDownListFor(model => model.blah, "All")
Which causes the page not to load because it cant find locations from the current model, but of course, as you can see in the controller above, there are lists available to use. So using
@Html.DropDownListFor(model => model.blah, (SelectList)ViewBag.Location, "All")
Meant it used the viewbag which contains a list of locations (or blah!).
The great thing about this method also means the search results are hidden until a search takes places, which is brilliant, and something my client wanted.
Another great snippet is how the data is displayed. Instead of using
(result is the variable in the foreach (see the tutorial links above)), which does the same thing, but the code is a lot smaller! Yay!
So now what about moving the filter results to other pages? Well of course just pass through the model items:
@Html.PagedListPager(Model.SearchResults, Page => Url.Action("Index", new RouteValueDictionary() { { "Page", Page }, { "blah", Model.blah }, { "blah1", Model.blah1 }, { "blah2", Model.blah2 }, { "blah3", Model.blah3 }, { "blah4", Model.blah4 } }), PagedListRenderOptions.PageNumbersOnly)
Anyway I hope you found this post relatively informative, and please give feedback/suggestions.