tags:

views:

846

answers:

1

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!

+1  A: 

I suppose there are a few ways to try and accomplish this.

  1. Pass the search parameters via query string instead of post. Understandably, this could be complicated and messy for advanced search parameters
  2. Store the results of the POST to hidden elements. Have your paging control POST to the same action each time, instead of the separate Results action.
  3. Store the query parameters in an object that is persisted in your session.

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:

  1. Maintain the form between page requests.
  2. Manage the page state with a hidden element in the form.
  3. Have JavaScript intercept your Paging links, update the page number hidden element, and resubmit the form.

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>
HackedByChinese
I guess what i am trying to hammer at is, is there another way for me to accomplish this. All I am trying to do is display search results and page thru them.As a build out the query, I would have to pass alot more than TypeOfListing.Would AJAX help me here?
j0nscalet
I think I am going to go the Session route. Has anyone does this type of thing before? It seems like a pretty improper use of Session to me...
j0nscalet
AJAX is definitely an option, and Session isn't so bad (however that all depends on what all you need to do), but you have a lot of options. I updated my answer to include an example on how to maintain the search through pages by persisting the original form through each page.
HackedByChinese