Tracking the number of times Ajax.ActionLink was called - asp.net-mvc

In brief: is it possible to track the number of times an Ajax.ActionLink method was called?
Now for context. I've got a simple model:
public class Person {
public string Name { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address {
public string City { get; set; }
public string Country { get; set; }
}
So, a person can have many addresses. On the Create page, I want the user to click a button that allows them to add as many Addresses as they want, dynamically.
I used this page as a reference in learning how to bind dynamically to a list: http://haacked.com/archive/2008/10/23/model-binding-toa-list.aspx,
With that as a reference, here are my classes:
HomeController:
//
// GET: /Home/
public ActionResult Index() {
return View();
}
[HttpPost]
public ActionResult Create(Person p) {
return View(p);
}
[OutputCache(NoStore = true, Duration = 0)]
public ActionResult AjaxAddAddress() {
TempData["key"] = DateTime.Now.Ticks.GetHashCode();
return PartialView("~/Views/Shared/EditorTemplates/Address.cshtml", new Address());
}
Index view:
#model ModelTest.Models.Person
<div>
#using (Html.BeginForm("Create", "Home")) {
<div>Name: #Html.TextBoxFor(m => m.Name)</div>
<div id="ajaxAddressBox"></div>
<p>#Ajax.ActionLink("Add Another Address", "AjaxAddAddress", new AjaxOptions {
UpdateTargetId = "ajaxAddressBox",
InsertionMode = InsertionMode.InsertAfter,
HttpMethod = "GET" })</p>
<input id="btnSubmit" type="submit" value="Create" />
}
</div>
Create View (just to confirm the model binded okay):
#model ModelTest.Models.Person
<div>
<p>You entered person: #Model.Name.</p>
<p>He has #Model.Addresses.Count total addresses.
#foreach (var c in Model.Addresses) {
<p>City: #c.City, Country: #c.Country</p>
}
</div>
Address editor template:
#model ModelTest.Models.Address
<p><input type="hidden" name="Addresses.Index" value="#TempData["key"]" />
City: #Html.TextBoxFor(x => x.City, new { Name = "Addresses[" + TempData["key"] + "].City" } )
Country: #Html.TextBoxFor(x => x.Country, new { Name = "Addresses[" + TempData["key"] + "].Country" })</p>
It seems to work ok, so I hope I'm doing this right so far. I'm new to MVC so please let me know if anything is totally wrong.
But I need it to do more. Ideally, I'd like to add a label that says "Address #(index)" for each line. But more important, I need to restrict the user to only adding, eg, 5 addresses. Either way, I'd like to track the number of times that Ajax.ActionLink, or the method AjaxAddAddress was called. Plus, in the future I'll need an edit page that also requires that restriction. Thus, if an existing person has 3 addresses, they can add only 2 more.
Any advice? It seems simple but I'm not sure how best to approach it. If I used a hidden field, how do you pass that value in Ajax.ActionLink and read it in my AjaxAddAddress method? Can you make a local client variable somehow?
I suppose a Session variable would work, but I always get nervous using that, not sure how long it lives or how reliable it is.

Here's one possible solution I came up with, with help from http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/.
Instead of using Ajax.ActionLink, I'm using Html.ActionLink and calling Ajax manually myself. That way, I can have it grab values right from Javascript. Whatever value I want, really: an expando, jquery.data, a hidden field, anything.
So my Ajax.ActionLink becomes:
#Html.ActionLink("Add Another Address", "AjaxAddAddress", null, new { id = "addItem" })
Then, within the same view I added this script:
<script type="text/javascript">
$("#addItem").click(function () {
$.ajax({
url: this.href + "?index=" + $("#ajaxAddressBox").children('div').size(),
cache: false,
success: function (html) {
$("#ajaxAddressBox").append(html);
}
});
return false;
});
</script>
I'm manually passing in an Index value to my AjaxAddAddresses method, and I'm basing that index value off the total number of div children currently in the ajaxAddressBox, or put another way, the current total number of addresses added. Thus, in the future when I build an Edit view, and it'll initially populate with existing addresses, this function will know how many addresses there are from the start.
AjaxAddAddresses becomes:
[OutputCache(NoStore = true, Duration = 0)]
public ActionResult AjaxAddAddress(int? index) {
if (index >= 5) return null;
TempData["key"] = DateTime.Now.Ticks.GetHashCode();
TempData["index"] = index + 1;
return PartialView("~/Views/Shared/EditorTemplates/Address.cshtml", new Address());
}
Thus, if the index is >= 5, I return null so that the user can't add more. (This could be done in the script block as well to save the wasted Ajax call, but at least when done server-side it can't be spoofed.)
And the Address Editor Template becomes:
#model ModelTest.Models.Address
<div><p><input type="hidden" name="Addresses.Index" value="#TempData["key"]" />
Address ##TempData["index"] ---
City: #Html.TextBoxFor(x => x.City, new { Name = "Addresses[" + TempData["key"] + "].City" } )
Country: #Html.TextBoxFor(x => x.Country, new { Name = "Addresses[" + TempData["key"] + "].Country" })</p></div>
Of course, other solutions are still welcome. This still feels needlessly complicated to me...
-ps, As it turns out, using a Session variable in my AjaxAddAddress method does work, but I can't shake the feeling that it could fail under some circumstances.

Related

MVC unobtrusive validation queries

I'm using unobtrusive validation in MVC for a search application, and on the whole it works well, except for 2 things that I can't find any answers for. I've used it before, and I don't remember these being an issue, so it could just be my setup in this application.
The first issue is that it immediately tells me that the search query input is invalid, or at least shows the error message for it, despite it being valid to begin with. It has some text added to it via the model, so it has a value when the page loads, therefore I can't understand why the unobtrusive validation fails it and shows the error message it when it loads.
The second issue is that it doesn't see a whitespace (" ") string as an error despite being 'required' and having 'AllowEmptyStrings' set to false, along with a 'ConvertEmptyStringToNull' DisplayFormat. I thought this would catch the whitespace, but it isn't doing.
I've overcome both these issues, the first by calling the validation manually in Document.Ready, which proves that the input is indeed valid. And the second by adding a manual check before the form is submitted. Both of these are convoluted, especially the first issue, I'd like to know why it doesn't work as expected, and avoid this issue in the future
The relevant parts of the code are below:
The SearchVM view model with the searchTerm property and the annotations.
public class SearchVM : PageDetails
{
public string SpellingSuggestion { get; set; }
public List<Result> SearchResults { get; set; }
public int ResultsCount { get; set; }
private string searchTerm = "";
[Display(Name = "Search the website")]
[Required(ErrorMessage = "Please enter a search term", AllowEmptyStrings = false)]
[DisplayFormat(ConvertEmptyStringToNull = false)]
public string SearchTerm { get
{
return searchTerm;
}
set {
//Strip out bad characters from the search term
if (value != null) {
searchTerm = Regex.Replace(value, #"[‘’#!#\$%]", "");
} else
{
searchTerm = "";
}
}
}
}
The Index view where the query is displayed:
#model SearchVM
#{
Model.Title = "Search the website";
Model.Description = "Search the website.";
Model.Keywords = ", website, search, google, find, websearch";
Model.Class = "search";
}
<main class="search">
#using (Ajax.BeginForm("Results", new AjaxOptions { UpdateTargetId = "results", OnComplete= "moreInfoDropDown(), spellingSuggestion()" }))
{
#Html.LabelFor(m => m.SearchTerm)
<div>
#Html.TextBoxFor(m => m.SearchTerm, new { maxlength = 30, autocomplete = "off" })
</div>
<input type="image" src="~/Images/search/icon.png" alt="Search" tabindex="3" />
#Html.ValidationMessageFor(m => m.SearchTerm)
}
<div id="results">
#if (Model.SearchResults != null)
{
#Html.Partial("_ResultsIntro", Model)
}
</div>
</main>
And the controller that calls the view (in this scenario, the query is null, so it always calls the Index with the searchTerm set to "Search the website"):
// GET: Search
public ActionResult Index(SearchVM searchVM, string query)
{
// If there is a query added to the URL, use it
if (!string.IsNullOrWhiteSpace(query)) {
searchVM.SearchTerm = query;
}
// Re-load the typical index with no results if the search term is empty
if (string.IsNullOrWhiteSpace(searchVM.SearchTerm))
{ return View(new SearchVM() { SearchTerm = "Search the website" });
}
// Return the index with the queried result (if applicable)
return View(GSA.Query(searchVM.SearchTerm, 0));
}
Thank you in advance for any help you can give. I don't think any other parts of the code are relevant, but please let me know if you need to see more, and I will add it to the question.
As Stephen Muecke said in his comment, I was incorrectly passing the searchVM model to the controller, and therefore it was already invalid before being passed to the view. Removing that parameter, and instead initialising a new instance of the model solved the issue.
Thanks to Stephen for pointing this out.

edit form not working, data is being passed to server but not the httpPost method

I had asked question yesterday, which can be found here and
based upon the answer I got from asp.net/mvc forum which can be found here, I was told to clear my modelstate, as by default my form tends to hold its default value, and not the value I just tried to update. So, I added Modelstate.Clear(), which still doesn't work. Can anyone tell me if i'm using the ModelState.Clear() in a wrong place or if I have to change something?
So, here is the problem, I have a edit form which shows its current values in textboxes, when user clicks edit button. If a user wants to edit some current value which is shown in textbox he edits the value in text box and clicks the save changes button. What currently is happening is in my HttpPost method when i check the values that are being passed, I don't get the new value user just provided, rather I get the value that was shown as current value in form.
But when I check in the developer tools in chrome, it shows the new value user just provided as the value that is being passed to server.
Here is my view
#using BootstrapSupport
#model AdminPortal.Areas.Hardware.Models.EditModule
#{
ViewBag.Title = "Edit";
Layout = "~/Views/shared/_BootstrapLayout.basic.cshtml";
}
<fieldset>
<legend>Module <small>Edit</small></legend>
#using (Html.BeginForm("Edit", "Module"))
{
#Html.ValidationSummary(true)
#Html.HiddenFor(m=>m.Id)
for(var i = 0; i < Model.Properties.Count(); i++)
{
#Html.HiddenFor(model=>model.HiddenProperties[i].Name)
#Html.HiddenFor(model=>model.HiddenProperties[i].Value)
<label class="label">#Model.Properties[i].Name</label>
<div class="input-block-level">#Html.TextBoxFor(model => model.Properties[i].Value)</div>
}
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
</fieldset>
<p>
#Html.ActionLink("Back to List", "ModuleList")
</p>
Here is the get and post method in controller
[HttpGet]
public ActionResult Edit(long id)
{
var module = _repository.GetModuleProperties(id);
ModelState.Clear();
return View(module);
}
[HttpPost]
public ActionResult Edit(EditModule module)
{
ModelState.Clear();
if (ModelState.IsValid)
{
_repository.SaveModuleEdits(module);
Information("Module was successfully edited!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Edit was unsuccessful, if the problem persists please contact Merijn!");
return RedirectToAction("ModuleList", "Module", new { area = "Hardware" });
}
The problem is with your model:
public class EditModule
{
public long Id { get; set; }
public List<PropertyViewModel> Properties { get; set; }
public List<PropertyViewModel> HiddenProperties
{
get { return Properties; }
set { Properties = value; }
}
}
You're posting back both Properties and HiddenProperties, but only changing Properties. The modelbinder sets the new values in Properties and then sets the values for HiddenProperties which in turn sets Properties and you've just overwritten your changes.
I'm not sure what exactly you're trying to do with HiddenProperties, but it's completely broken as it's currently set up.
UPDATE: Suggested changes
Model
public class EditModule
{
public long Id { get; set; }
public List<PropertyViewModel> Properties { get; set; }
}
Removed HiddenProperties property
Controller Action
[HttpPost]
public ActionResult Edit(long id, EditModule module)
{
var originalModule = _repository.GetModuleProperties(id);
// do whatever comparisons you want here with originalModule.Properties / module.Properties
if (ModelState.IsValid)
{
_repository.SaveModuleEdits(module);
Information("Module was successfully edited!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Edit was unsuccessful, if the problem persists please contact Merijn!");
return RedirectToAction("ModuleList", "Module", new { area = "Hardware" });
}
Edit POST version takes the id just like the GET version. You use this id to lookup the original version of the module from the database and then you can compare original and posted versions of Properties.
View
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.HiddenFor(m=>m.Id)
for(var i = 0; i < Model.Properties.Count(); i++)
{
<label class="label">#Model.Properties[i].Name</label>
<div class="input-block-level">#Html.TextBoxFor(model => model.Properties[i].Value)</div>
}
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
The Html.BeginForm() syntax tells Razor to just use the current page's URL as the action for the form. HiddenProperties form fields have been removed.

how to get partialview data in controller

I am using three partialview on a single view, I have a submit button on clicking of which I want to send information to database, I have to retrieve data from all the partialview.
Can You please provide me correct information to do it.
Darin I m using L2S so when I drag my stored procedure, I get code some thing like this in
[global::System.Data.Linq.Mapping.FunctionAttribute(Name="SP_Name")]
public int SP_Name(
[global::System.Data.Linq.Mapping.ParameterAttribute(Name="EmployeeID", DbType="Int")] System.Nullable<int> EmployeeID
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), EmployeeID);
encounterID = ((System.Nullable<int>)(result.GetParameterValue(293)));
return ((int)(result.ReturnValue));
}
}
Updated
<script language="javascript" type="text/javascript">
$(function () {
$('#Form1').submit(function () {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (data) {
var message = data.Result;
$('#Result').html(message);
}
});
return false;
});
});
</script>
In my Controller I am using
public ActionResult Index(FormCollection frm)
{
My Code ---------------------
return Json(new { Result = "Success" });
}
When I return this I m getting a file in post back and it ask me to save it.
I have checked using flidder, in URL it shows me that the path as / only
where as If I fill any particular partialview It shows something like /Controller Name/Partialview
Can You help me with this problem
Well, sending data to a controller action is usually done by performing an HTTP request to this controller action. There are different ways of performing an HTTP request:
Use a <form> tag pointing to this action
Use AJAX
So if you go with the first approach you could have a single <form> wrapping all the partials which would have multiple submit buttons (with different names). Then when you click on one submit buttons all the input fields will be sent to the controller action and then inside the controller action you could process the data based on which submit button was clicked.
If you use the second option, well, then simply harvest the values you need to be sent uipon button click and send them along the AJAX request.
UPDATE:
As requested in the comments section here's how the first technique could be put into action. It uses two partials instead of three but it could be easily extrapolated.
As always you start by defining a view model which will represent the data you would like to work with on this particular view:
public class MyViewModel
{
public Partial1ViewModel Model1 { get; set; }
public Partial2ViewModel Model2 { get; set; }
}
public class Partial1ViewModel
{
public string Foo { get; set; }
}
public class Partial2ViewModel
{
public string Bar { get; set; }
}
Then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
Model1 = new Partial1ViewModel { Foo = "foo" },
Model2 = new Partial2ViewModel { Bar = "bar" },
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// Here you have access to model.Model1.Foo and model.Model2.Bar =>
var button = "";
if (!string.IsNullOrEmpty(Request["submit1"]))
{
// submit1 button was used
button = "submit1";
}
else if (!string.IsNullOrEmpty(Request["submit2"]))
{
// submit2 button was used
button = "submit2";
}
var result = string.Format("thanks for submitting using {0}", button);
return Content(result, "text/plain");
}
}
and then a main view (~/Views/Home/Index.cshtml):
#model MyViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Model1)
#Html.EditorFor(x => x.Model2)
}
and the two corresponding editor templates (or partials if you will):
~/Views/Home/EditorTemplates/Partial1ViewModel.cshtml:
#model Partial1ViewModel
<h2>Partial 1</h2>
<div>
#Html.LabelFor(x => x.Foo)
#Html.EditorFor(x => x.Foo)
<input type="submit" value="Submit me!" name="submit1" />
</div>
~/Views/Home/EditorTemplates/Partial2ViewModel.cshtml:
#model Partial2ViewModel
<h2>Partial 2</h2>
<div>
#Html.LabelFor(x => x.Bar)
#Html.EditorFor(x => x.Bar)
<input type="submit" value="Submit me!" name="submit2" />
</div>

Understanding how DropDownListFor is working in MVC3

I'm new to MVC3 and have been working on a small site using EF and 'Code First'. I'm trying to do a few things in a view dealing with a drop down list and am wondering what the best way to go about them is. I want a user to be able to select a rule from the dropdownlist, and depending upon which rule was selected, I would like a label on the page to show the rule name (without posting). I also need to be able to send the selected rule onto the next page. I haven't added all of the necessary fields to the view yet because I'm really at a loss on how it should work. How should I go about trying to do this?
I've got my model:
public class D1K2N3CARule
{
public int ID { get; set; }
[Required]
public int Name { get; set; }
[Required]
public string Rule { get; set; }
public D1K2N3CARule(int name, string rule)
{
Name = name;
Rule = rule;
}
public D1K2N3CARule()
{
Name = 0;
Rule = "";
}
}
My ViewModel:
public class D1K2N3CARuleViewModel
{
public string SelectedD1K2N3CARuleId { get; set; }
public IEnumerable<D1K2N3CARule> D1K2N3CARules { get; set; }
}
My Controller:
public ActionResult Index()
{
var model = new D1K2N3CARuleViewModel
{
D1K2N3CARules = db.D1K2N3DARules
};
return View(model);
}
and my View:
'#model CellularAutomata.Models.D1K2N3CARuleViewModel
#{
ViewBag.Title = "Index";
}
<asp:Content id="head" contentplaceholderid="head" runat="server">
<script type="text/javascript">
</script>
</asp:Content>
<h2>Index</h2>
<table>
<tr>
<td>
#Html.DropDownListFor(
x => x.D1K2N3CARules,
new SelectList(Model.D1K2N3CARules, "ID","Rule")
)
</td>
</tr>
</table>'
I want a user to be able to select a rule from the dropdownlist, and depending upon which rule was selected, I would like a label on the page to show the rule name (without posting)
You will need javascript here. jQuery would be perfect for the job. I would start by providing a deterministic id for the dropdown because if you run this view inside a template there could be prefixes added to the id which would ruin our javascript id selectors (see below):
#Html.DropDownListFor(
x => x.D1K2N3CARules,
new SelectList(Model.D1K2N3CARules, "ID", "Rule"),
new { id = "ruleDdl" }
)
then provide some container which will receive the selected value:
<div id="ruleValue" />
and finally in a separate javascript file subscribe for the change event of the dropdown list and update the container with the selected value/text:
$(function() {
// subscribe for the change event of the dropdown
$('#ruleDdl').change(function() {
// get the selected text from the dropdown
var selectedText = $(this).find('option:selected').text();
// if you wanted the selected value you could:
// var selectedValue = $(this).val();
// show the value inside the container
$('#ruleValue').html(selectedText);
});
});
I also need to be able to send the selected rule onto the next page.
You could put your dropdown inside a form
#using (Html.BeginForm("NextPage", "Foo"))
{
#Html.DropDownListFor(
x => x.D1K2N3CARules,
new SelectList(Model.D1K2N3CARules, "ID","Rule")
)
<input type="submit" value="Go to the next page" />
}
and the NextPage controller action will receive the selected value.

ASP.NET MVC Paging for a search form

I've read several different posts on paging w/ in MVC but none describe a scenario where I have something like a search form and then want to display the results of the search criteria (with paging) beneath the form once the user clicks submit.
My problem is that, the paging solution I'm using will create <a href="..."> links that will pass the desired page like so: http://mysite.com/search/2/ and while that's all fine and dandy, I don't have the results of the query being sent to the db in memory or anything so I need to query the DB again.
If the results are handled by the POST controller action for /Search and the first page of the data is rendered as such, how do I get the same results (based on the form criteria specified by the user) when the user clicks to move to page 2?
Some javascript voodoo? Leverage Session State? Make my GET controller action have the same variables expected by the search criteria (but optional), when the GET action is called, instantiate a FormCollection instance, populate it and pass it to the POST action method (there-by satisfying DRY)?
Can someone point me in the right direction for this scenario or provide examples that have been implemented in the past? Thanks!
My method is to have an Action that handles both the post and the get scenarios.
This is my which can be handled by both GET and POST methods:
public ViewResult Index([DefaultValue(1)] int page,
[DefaultValue(30)] int pageSize,
string search,
[DefaultValue(0)] int regionId,
[DefaultValue(0)] int eventTypeId,
DateTime? from,
DateTime? to)
{
var events = EventRepo.GetFilteredEvents(page, pageSize, search, regionId, eventTypeId, from, to);
var eventFilterForm = EventService.GetEventFilterForm(from, to);
var eventIndexModel = new EventIndexModel(events, eventFilterForm);
return View("Index", eventIndexModel);
}
The eventFilterForm is a presentation model that contains some IEnumerable<SelectListItem> properties for my search form.
The eventIndexModel is a presentation model that combines the eventFilterForm and the results of the search - events
The events is a special type of IPagedList. You can get more information and code for that here and here. The first link talks about IPagedList where as the second link has an Advanced Paging scenario which you should need.
The advanced paging has the following method that I use:
public static string Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount, RouteValueDictionary valuesDictionary)
And I use it like so:
<%= Html.Pager(Model.Events.PageSize,
Model.Events.PageNumber,
Model.Events.TotalItemCount,
new
{
action = "index",
controller = "search",
search = ViewData.EvalWithModelState("Search"),
regionId = ViewData.EvalWithModelState("RegionId"),
eventTypeId = ViewData.EvalWithModelState("EventTypeId"),
from = ViewData.EvalDateWithModelState("From"),
to = ViewData.EvalDateWithModelState("To")
}) %>
This creates links that look like:
/event/search?regionId=4&eventTypeId=39&from=2009/09/01&to=2010/08/31&page=3
HTHs,
Charles
Ps. EvalWithModelState is below:
PPs. If you are going to put dates into get variables - I would recommend reading my blog post on it... :-)
/// <summary>
/// Will get the specified key from ViewData. It will first look in ModelState
/// and if it's not found in there, it'll call ViewData.Eval(string key)
/// </summary>
/// <param name="viewData">ViewDataDictionary object</param>
/// <param name="key">Key to search the dictionary</param>
/// <returns>Value in ModelState if it finds one or calls ViewData.Eval()</returns>
public static string EvalWithModelState(this ViewDataDictionary viewData, string key)
{
if (viewData.ModelState.ContainsKey(key))
return viewData.ModelState[key].Value.AttemptedValue;
return (viewData.Eval(key) != null) ? viewData.Eval(key).ToString() : string.Empty;
}
Make the Search parameter part of your View Model:
public SearchViewModel
{
string SearchParameters { get; set; }
List<SearchObjects> SearchResults { get;set; }
}
Then just set the Search Textbox equal to SearchParameters.
You cannot "store" the search query unless you bring back ALL results and then store those in the page somehow. That is horribly inefficient. The web is stateless, so you will have to go back to the database and re-query for more results.
I understand what you are saying; you could change the form to use buttons and post the page back everytime. Or, you could pass all the criteria in the URL for the paging as querystring variables. Or you could use JQuery to do the post (it has a $.post method that can be invoked from a link click or other click (http://api.jquery.com/jQuery.post/).
HTH.
This problem goes away if you include the search text, as well as the current results page, in your querystring instead of POSTing the search text. As an added benefit, your users can then bookmark their search results.
To do this your search button just needs to build the GET request URL using the current value of the search box. This can be done either in javascript or by using GET as your search form's method attribute, e.g. <form method="get" action="/search">.
I recommend cacheing your search results and giving them an ID. Then for each paging link, you can reference the search ID as a parameter (on each search page link) and in your action, pull it from cache, then query over it.
Using this method, you don't need to worry about anything other than the first POST submit of the search form.
Refer to my post for more details.
I had this same problem and here's what I did.
Download PagedList from Nuget
Change your form to do a GET and create a ViewModel type similiar to this (if you love AdventureWorks and Model Binding as much as I do):
`
using PagedList;
namespace SearchFormResultPagingExample.Models {
public class SearchViewModel {
public int? Page { get; set; }
public string EmailAddress { get; set; }
public string LastName { get; set; }
public IPagedList<Contact> SearchResults { get; set; }
public string SearchButton { get; set; }
}
}
`
3.Use the ViewModel as the parameter to your controller's action method
using System.Linq;
using System.Web.Mvc;
using SearchFormResultPagingExample.Models;
using PagedList; //NOTE: use Nuget to reference PagedList
namespace SearchFormResultPagingExample.Controllers {
public class SearchController : Controller {
const int RecordsPerPage = 25;
public ActionResult Index(SearchViewModel model) {
if (!string.IsNullOrEmpty(model.SearchButton) || model.Page.HasValue) {
var entities = new AdventureWorksEntities();
var results = entities.Contacts.Where(c => c.LastName.StartsWith(model.LastName) && c.EmailAddress.StartsWith(model.EmailAddress))
.OrderBy(o => o.LastName);
var pageIndex = model.Page ?? 0;
model.SearchResults = results.ToPagedList(pageIndex, 25);
}
return View(model);
}
}
}
Use the pager on in your View:
#model SearchFormResultPagingExample.Models.SearchViewModel
#using PagedList.Mvc;
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("Index", "Search", FormMethod.Get)) {
#Html.ValidationSummary(false)
<fieldset>
<legend>Contact Search</legend>
<div class="editor-label">
#Html.LabelFor(model => model.EmailAddress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.EmailAddress)
#Html.ValidationMessageFor(model => model.EmailAddress)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<p>
<input name="SearchButton" type="submit" value="Search" />
</p>
</fieldset>
}
#if (Model.SearchResults != null && Model.SearchResults.Count > 0) {
foreach (var result in Model.SearchResults) {
<hr />
<table width="100%">
<tr>
<td valign="top" width="*">
<div style="font-weight: bold; font-size:large;">#result.LastName, #result.FirstName</div>
#result.Title<br />
#result.Phone<br />
#result.EmailAddress
</td>
</tr>
</table>
}
<hr />
#Html.PagedListPager(Model.SearchResults,
page => Url.Action("Index", new RouteValueDictionary() {
{ "Page", page },
{ "EmailAddress", Model.EmailAddress },
{ "LastName", Model.LastName }
}),
PagedListRenderOptions.PageNumbersOnly)
}
MVC will coerce the querystring to and from your ViewModel type parameter. It's very slick!

Resources