Reputation: 207
Im at a loss for words, as I must be missing something. Just finished ASP.NET MVC 1.0 (WROX) and Im trying to implement a view that performs a simple search then renders the results in a table. I would then like to be able to page thru the results.
So I have a search action from ListingsController, takes some values from FormCollection and filters the results accordingly:
//
//POST: /Listings/Search
// /Listings/Page/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Search(FormCollection collection,int? page)
{
var listings = listingRepository.GetListings();
//filter
if (collection["TypeOfHouse"] != null)
{
string[] typeList = collection["TypeOfHouse"].Split(',');
foreach (string type in typeList)
{
listings = from l in listings
where l.TypeOfHouse == type
select l;
}
}
//display the first page of results
int pageSize = 25;
var paginatedListings = new PriviledgeV1.Helpers.PaginatedList<Listing>(listings, 0, pageSize);
return View("Results", paginatedListings);
}
Initially the Results view will be rendered with the first 25 records for page 1. Then I have a Results action that handles the "pagination":
public ActionResult Results(int? page)
{
int pageSize = 25;
var listings = listingRepository.GetListings();
var paginatedListings = new PriviledgeV1.Helpers.PaginatedList<Listing>(listings, page ?? 0, pageSize);
return View(listings);
}
Trouble is because I no longer have the FormCollection, I cannot properly filter results. So if I tried to move from say page 1 to page 2 using /Listings/Results?page=2, the results action would fire and it would return ALL results instead of the filtered result set from the Search action.
I'm really confused as to what to do here, and as to why there are no blogs/tutorials explaining this, which normally flags me that I am missing something.
Thanks!
Upvotes: 1
Views: 11461
Reputation: 1293
Basically change your form to do an HTTP GET as suggested by others, then download PagedList with Nuget, and use Model Binding in your action method to the submit button and page number coming from the PagedList's HTML helper function (and set the route values using a RouteValuesDictionary). This way everything gets persisted in the querystring, which is what you want with a search page anyway most likely. MVC will do the coersion to and from your ViewModel class. I prefer to use a ViewModel class instead of a bunch of arguments because I think it is cleaner that way, but just my $.02.
I have an example that I blogged about here.
Also, I went ahead and posted the code on a different thread
Upvotes: 0
Reputation: 42497
I suppose there are a few ways to try and accomplish this.
Results
action.I'm sure we could get more creative from there, but that should give you a start. It seems like you only care about one field from the search form, TypeOfListing
. You should be able to persist that via query string pretty easily, so that'd be method #1 above.
Update
Here's something simple I put together to maintain your search at the client. The technique involves three parts:
Here's the code for all the various pieces. Note that I use jQuery, in case you prefer something else. I fudged the data source, just sub in real data. Also, I included PagedList and PaginationHelper. Substitute with your own if you wish.
\Controllers\HomeController.cs (Search is the relevant part):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication2.Controllers
{
[HandleError]
public class HomeController : Controller
{
List<String> _data;
public HomeController()
{
_data = new List<String>();
_data.Add("Merry");
_data.Add("Metal");
_data.Add("Median");
_data.Add("Medium");
_data.Add("Malfunction");
_data.Add("Mean");
_data.Add("Measure");
_data.Add("Melt");
_data.Add("Merit");
_data.Add("Metaphysical");
_data.Add("Mental");
_data.Add("Menial");
_data.Add("Mend");
_data.Add("Find");
}
public ActionResult Search()
{
Int32 pageNumber, pageSize = 5, total, first;
String typeOfListing;
PagedList<String> results;
if (Request.HttpMethod == "GET")
{
return View();
}
if (!Int32.TryParse(Request.Form["PageNumber"], out pageNumber)) pageNumber = 1;
typeOfListing = Request.Form["TypeOfListing"];
first = (pageNumber - 1) * pageSize;
total = (from s in _data
where s.Contains(typeOfListing)
select s).Count();
results = new PagedList<String>(
(from s in _data
where s.Contains(typeOfListing)
select s)
.Skip(first)
.Take(pageSize),
total, pageNumber, pageSize);
return View(results);
}
}
}
\Helpers\PaginationHelper.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Web.Routing;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace MvcApplication2.Helpers
{
public static class PaginationHelper
{
public static String Pager(this HtmlHelper helper, Int32 pageSize, Int32 pageNumber, Int32 total, String actionName, RouteValueDictionary values)
{
StringBuilder output = new StringBuilder();
Int32 totalPages = (Int32)Math.Ceiling((Double)total / pageSize);
if (values == null)
values = helper.ViewContext.RouteData.Values;
if (pageNumber > 1)
output.Append(CreatePageLink(helper, values, "< Previous ", pageNumber - 1, pageSize));
for (Int32 i = 1; i <= totalPages; i++)
{
if (i == pageNumber)
output.Append(i);
else
output.AppendFormat(CreatePageLink(helper, values, i.ToString(), i, pageSize));
if (i < totalPages)
output.Append(" | ");
}
if (pageNumber < totalPages)
output.Append(CreatePageLink(helper, values, " Next >", pageNumber + 1, pageSize));
return output.ToString();
}
private static String CreatePageLink(HtmlHelper helper, RouteValueDictionary values, String text, Int32 pageNumber, Int32 pageSize)
{
RouteValueDictionary routeDictionary = new RouteValueDictionary(values);
routeDictionary.Add("page", pageNumber);
routeDictionary.Add("pageSize", pageSize);
return helper.ActionLink(text, null, routeDictionary);
}
}
}
\PagedList.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication2
{
public class PagedList<T> : List<T>
{
public Int32 TotalCount { get; protected set; }
public Int32 PageNumber { get; protected set; }
public Int32 PageSize { get; protected set; }
public PagedList(IEnumerable<T> items, Int32 total, Int32 pageNumber, Int32 pageSize)
: base(items)
{
TotalCount = total;
PageNumber = pageNumber;
PageSize = pageSize;
}
}
}
\Views\Home\Search.aspx:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<PagedList<String>>" %>
<%@ Import Namespace="MvcApplication2" %>
<%@ Import Namespace="MvcApplication2.Helpers" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Search
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script type="text/javascript">
$(function() {
var results = $("#searchResults");
if (results && results.children().length > 2) {
$("#searchForm").hide();
$("#searchResults .pager>a").click(submitForm);
}
});
function submitForm() {
var m = this.href.match(/page=(\d+)/i);
if (m) {
$("#PageNumber").attr("value", m[1]);
$("#searchForm").submit();
}
return false;
}
</script>
<form id="searchForm" action="<%= Url.Action("Search") %>" method="post">
<input type="hidden" value="1" id="PageNumber" name="PageNumber" />
<fieldset>
<legend>Search</legend>
<label for="TypeOfListing">Type of Listing</label>
<%= Html.TextBox("TypeOfListing", Request.Form["TypeOfListing"]) %>
<input type="submit" id="btnSubmit" name="btnSubmit" value="Search" />
</fieldset>
</form>
<% if (Model != null)
{
%>
<div id="searchResults">
<div class="results-count">Displaying <%=this.Model.Count %> of <%=this.Model.TotalCount %> results. <%=Html.ActionLink("Start a new search", "Search") %>.</div>
<%
foreach (String result in Model)
{
%>
<div class="result"><%=result %></div>
<% }
%>
<div class="pager"><%= Html.Pager(Model.PageSize, Model.PageNumber, Model.TotalCount, null, null) %></div>
</div>
<%
}
%>
</asp:Content>
Upvotes: 5