I am trying to learn Asp.net mvc. I know its different from forms and i need to change my way of thinking probably. My problem is about webgrid . When i add webgrid to my page and hit search button with Post it renders table with pager and so on. But links on the pager is not posting form they are just links and i lost all my form's data.
Controller has two index methods one is for get and other is for post. For get i do nothing, I just create new viewmodel in this case Search class and set it to view. For my post method i grab my view model do search and set filled viewmodel to view.
problem : webgrid renders pager as links so it will enter to the Index for get but since it is not a post request i dont have any form fields filled and my search will not provide the very same result set.
Maybe example code can explain it better.
View:
<form action="" method="post">
Esas no : @Html.TextBoxFor(x=>x.Name)
Yil : @Html.TextBoxFor(x=>x.Year)
<input type="submit" value="Search" />
<hr />
@ViewBag.Message
<hr />
@{ var grid = new WebGrid(Model.Results,rowsPerPage:5);}
@grid.GetHtml(tableStyle:"table",htmlAttributes:new {id="tbl"} )
</form>
Here is My Controller: Search occures in Index Post method and it has just my viewmodel class.
private ISearchContext _sc;
public MyController(ISearchContext sc)
{
_dc = dc;
}
//
// GET: /Dava/
public ActionResult Index()
{
var search = new Search();
ViewBag.Message = "";
return View(search);
}
[HttpPost]
public ActionResult Index(Search search)
{
Search sres = _dc.SearchFromRepository(search);
ViewBag.Message = String.Format("Count:{0} ",sres.Results.Count);
return View(sres);
}
Search model Class is like:
public class Search
{
public int Year { get; set; }
public string Name { get; set; }
public IList<Item> Results { get; set; }
public Search()
{
Results = new List<Item>();
}
}
One way to solve this issue is to use javascript and subscribe for the click event of any of the pager links and then fetch the value of the desired page, inject it into a hidden field on the form and submit the form to the server so that the other two values are also sent.
So start by adding a Page
nullable integer property on your Search
view model and a corresponding hidden field into the form which will contain the selected page number:
@Html.HiddenFor(x => x.Page, new { id = "page" })
Then all you need is a small javascript snippet into the page to subscribe for the .click event of the pager links:
$(function () {
$('tfoot a').click(function () {
// when the user clicks on any of the pager links
// try to extract the page number from the link and
// set the value of the hidden field
var page = this.href.match(/page=([0-9])+/)[1];
$('#page').val(page);
// submit the form so that the POST action is invoked
// passing along the search criteria (Name and Year) along
// with the page hidden field value to the Index action
$('form').submit();
// cancel the default action of the link which is to simply redirect
// to the Index action using a GET verb.
return false;
});
});
Here is a workaround that doesn't use JavaScript.
The problem as I see it, the paging links do not receive any route information that must be preserved, like a search filter. IMO this is a blatant oversight! A little extra thought here would have saved lots of headache!
This technique "throws away" the WebGrid's built-in paging, and uses a Helper to generate the paging links, along with the precious route data we want.
Once completed, you simply render the WebGrid as the grid only, and use the Helper to make the paging links. One advantage here is you can put those at the top and bottom, which we like to do.
I attempted to use similar CSS to what is provided in the Pager.css that NuGet puts into your solution. The helper should be complete enough for some of you, but it is easily extended.
New New New I just updated the helper with an Ajax version. I am a little n00b with Razor helpers, so i couldn't figure out how to re-factor it to use a common template; anyone please? The important extra detail there is to pass in the AjaxOptions
and make sure to use POST
as the verb, otherwise you may not end up in the correct controller method.
Helper (App_Code/LocalHelpers.cshtml):
@helper DoPager(System.Web.Mvc.HtmlHelper hh, string pageActionName, WebGrid grid, int maxPageLinks, object rvd) {
<div class="pager">
<div class="pageof">Page <b>@(grid.PageIndex + 1)</b> of <b>@grid.PageCount</b></div>
@if (grid.PageCount > 1) {
<ul>
<li>
@{ RouteValueDictionary rvdp1 = new RouteValueDictionary(rvd);
rvdp1.Add("Page", 1);
}
@hh.ActionLink("<<", pageActionName, rvdp1)
</li>
@{ int start = Math.Max(0, grid.PageIndex - maxPageLinks / 2); }
@for (int ix = 0; ix + start < grid.PageCount; ix++) {
int pageno = start + ix + 1;
var css = hh.Raw(pageno - 1 == grid.PageIndex ? " class=\"highlighted\"" : "");
RouteValueDictionary rvdp = new RouteValueDictionary(rvd);
rvdp.Add("Page", pageno);
<li@css>
@hh.ActionLink(pageno.ToString(), pageActionName, rvdp)
</li>
if (ix >= maxPageLinks) { break; }
}
<li>
@{ RouteValueDictionary rvdpX = new RouteValueDictionary(rvd);
rvdpX.Add("Page", grid.PageCount);
}
@hh.ActionLink(">>", pageActionName, rvdpX)
</li>
</ul>
}
</div>
}
@helper DoAjaxPager(System.Web.Mvc.AjaxHelper aa, System.Web.Mvc.Ajax.AjaxOptions aopts, System.Web.Mvc.HtmlHelper hh, string pageActionName, WebGrid grid, int maxPageLinks, object rvd) {
<div class="pager">
<div class="pageof">Page <b>@(grid.PageIndex + 1)</b> of <b>@grid.PageCount</b></div>
@if (grid.PageCount > 1) {
<ul>
<li>
@{ RouteValueDictionary rvdp1 = new RouteValueDictionary(rvd);
rvdp1.Add("Page", 1);
}
@aa.ActionLink("<<", pageActionName, rvdp1, aopts)
</li>
@{ int start = Math.Max(0, grid.PageIndex - maxPageLinks / 2); }
@for (int ix = 0; ix + start < grid.PageCount; ix++) {
int pageno = start + ix + 1;
var css = hh.Raw(pageno - 1 == grid.PageIndex ? " class=\"highlighted\"" : "");
RouteValueDictionary rvdp = new RouteValueDictionary(rvd);
rvdp.Add("Page", pageno);
<li@css>
@aa.ActionLink(pageno.ToString(), pageActionName, rvdp, aopts)
</li>
if (ix >= maxPageLinks) { break; }
}
<li>
@{ RouteValueDictionary rvdpX = new RouteValueDictionary(rvd);
rvdpX.Add("Page", grid.PageCount);
}
@aa.ActionLink(">>", pageActionName, rvdpX, aopts)
</li>
</ul>
}
</div>
}
View:
<center>
@LocalHelpers.DoPager(Html, "Index", grid, 10, new { CurrentFilter = ViewBag.CurrentFilter })
</center>
@grid.Table(
tableStyle: "centerit",
columns: grid.Columns(
grid.Column(format: @<span>@Html.ActionLink("Edit", "Edit", new { id = item.ID }) | @Html.ActionLink("Details", "Details", new { id = item.ID }) | @Html.ActionLink("Delete", "Delete", new { id = item.ID })</span>),
grid.Column("PartNumber", "Part Number"),
grid.Column("Description", "Description"),
grid.Column("Regex", "Regex")
)
)
<center>
@LocalHelpers.DoPager(Html, "Index", grid, 10, new { CurrentFilter = ViewBag.CurrentFilter })
</center>
In my view, I am recycling the "CurrentFilter" to know what to filter on. This connects to the Controller Action (not pictured).
Ok. I have a more elegant solution using AJAX and Partial Views that should resolve this issue once and for all
This is my model:
public class SearchResultModel
{
public string SearchText{ get; set; }
public List<YourObject> Results { get; set; }
public int TotalResults { get; set; }
}
The search view is structured like this:
@model SearchResultModel
@using (Ajax.BeginForm("SearchAction", "SearchController", new AjaxOptions{UpdateTargetId = "data-grid", HttpMethod="Post"}))
{
@Html.TextBoxFor(m => m.SearchText)
<input class="myButton" type="submit" value="Search" />
}
<br />
<div id="data-grid">
@Html.Partial("SearchResults", new SearchResultModel())
</div>
The SearchResults partial view is:
@model SearchResultModel
@{
if (Model.Results != null && Model.Results.Count > 0)
{
var grid = new WebGrid(canPage: true, rowsPerPage: 10, canSort: true, ajaxUpdateContainerId: "grid");
grid.Bind(Model.Results, rowCount: Model.TotalResults, autoSortAndPage: false);
grid.Pager(WebGridPagerModes.All);
@grid.GetHtml(htmlAttributes: new { id = "grid" },
columns: grid.Columns(
grid.Column("YourColumn1"),
grid.Column("YourColumn2"),
grid.Column("YourColumn3")
),
tableStyle: "datatable",
rowStyle: "datatable-normal",
alternatingRowStyle: "datatable-alt"
);
}
else
{
<span>No Results</span>
}
}
Finally, the Controller is:
public class SearchController
{
public ActionResult SearchAction(SearchResultModel model)
{
return RedirectToAction("SearchResults", new { id = model.SearchText });
}
public ActionResult SearchResults(string id)
{
string searchText = id;
int page = 1;
if(Request["page"] != null)
int.TryParse(Request["page"], out page);
SearchResultModel model = new SearchResultModel();
//Populate model according to search text and page number
//........
//........
return PartialView(model);
}
}
Hope this will help save someone some time and angst!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With