MVC Model Binding with Dynamic Collection - c#

According to the seminal Scott Hanselman article on the complexities of the ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries:
We read in the properties by looking for parameterName[index].PropertyName
The index must be zero-based and unbroke
So this HTML:
<input type="text" name="People[0].FirstName" value="George" />
<input type="text" name="People[1].FirstName" value="Abraham" />
<input type="text" name="People[2].FirstName" value="Thomas" />
Which will post like this:
However, if I load a new person into my model over AJAX, I lose the context for building that person into the model and get the following output:
<input type="text" name="FirstName" value="New" />
Which won't get picked up by the model binder.
Q: How can I preserve the expression tree when dynamically adding new elements over AJAX?
Here's an MVCE
Model: /Model/Person.cs
public class PersonViewModel
{
public List<Person> People { get; set; }
}
public class Person
{
public String FirstName { get; set; }
public String LastName { get; set; }
}
Controller: Controllers/PersonController.cs
[HttpGet]
public ActionResult Index()
{
List<Person> people = new List<Person> {
new Person { FirstName = "George" , LastName = "Washington"},
new Person { FirstName = "Abraham" , LastName = "Lincoln"},
new Person { FirstName = "Thomas" , LastName = "Jefferson"},
};
PersonViewModel model = new PersonViewModel() {People = people};
return View(model);
}
[HttpPost]
public ActionResult Index(PersonViewModel model)
{
return View(model);
}
public ActionResult AddPerson(String first, String last)
{
Person newPerson = new Person { FirstName = first, LastName = last };
return PartialView("~/Views/Person/EditorTemplates/Person.cshtml", newPerson);
}
View: Views/Person/Index.cshtml
#model PersonViewModel
#using (Html.BeginForm()) {
<table id="table">
<thead>
<tr>
<th>#Html.DisplayNameFor(model => model.People.First().FirstName)</th>
<th>#Html.DisplayNameFor(model => model.People.First().LastName)</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.People.Count; i++)
{
#Html.EditorFor(model => model.People[i])
}
</tbody>
</table>
<input type="button" value="Add Person" id="add"/>
<input type="submit" value="Save" />
}
<script type="text/javascript">
$("#add").click(function() {
var url = "#Url.Action("AddPerson")?" + $.param({ first: "", last: "" });
$.ajax({
type: "GET",
url: url,
success: function(data) {
$("#table tbody").append(data);
}
});
});
</script>
View: Views/Person/EditorTemplates/Person.cshtml
#model Person
<tr>
<td>#Html.EditorFor(model => model.FirstName)</td>
<td>#Html.EditorFor(model => model.LastName)</td>
</tr>
NOTE: There are other complexities when deleting an item that I'm not looking to address here per se. I'd just like to add an element and know that it belongs in a nested context alongside other properties.

You can install the Html.BeginCollectionItem utility like this:
PM> Install-Package BeginCollectionItem
Then wrap your collection item partial view like this:
#model Person
<tr>
#using (Html.BeginCollectionItem("people"))
{
<td>#Html.EditorFor(model => model.FirstName)</td>
<td>#Html.EditorFor(model => model.LastName)</td>
}
</tr>
Which will generate a GUID-driven collection like this:
<tr>
<input type="hidden" name="people.index" autocomplete="off"
value="132bfe2c-75e2-4f17-b54b-07e011971d78">
<td><input class="text-box single-line" type="text" value="Abraham"
id="people_132bfe2c-75e2-4f17-b54b-07e011971d78__FirstName"
name="people[132bfe2c-75e2-4f17-b54b-07e011971d78].FirstName"></td>
<td><input class="text-box single-line" type="text" value="Lincoln"
id="people_132bfe2c-75e2-4f17-b54b-07e011971d78__LastName"
name="people[132bfe2c-75e2-4f17-b54b-07e011971d78].LastName"></td>
</tr>
Now we get posted form data that look like this:
This leverages the DefaultModelBinder which allows for Non-Sequential Indices as explained by Phil Haack:
The good news is that by introducing an extra hidden input, you can allow for arbitrary indices. Just provide a hidden input with the .Index suffix for each item we need to bind to the list. The name of each of these hidden inputs is the same, which will give the model binder a nice collection of indices to look for when binding to the list.
Right away, your model should build just fine, but you'll also be able to add and remove items as well.
Further Reading
A Partial View passing a collection using the Html.BeginCollectionItem helper
Submit same Partial View called multiple times data to controller?
Model Binding To A List - Phil Haack

Related

IEnumerable Error on View with Partial Load

I am trying to create a view in my application that performs basic CRUD commands in ASP.NET Core to teach myself some new skills. I am however stuck and would appreciate some assistance please.
I would like to have each "component" of the application sitting in a partial view for maintenance going forward. I initially had my Index view use a declaration of type IEnumerable (for the for each loop):
#model IEnumerable<Project.Web.Models.Sample.SampleModel>
Which worked perfect for returning the list and rendering the page but then when trying to have my Modal window partially loaded into the page and insert data using the "CreateSample" function on the controller it was not picking up the function and failed the insert (no form action found). If I then try to add:
#model Project.Web.Models.Sample.SampleModel
to the CreateModal view page it throws an error and wont even let me render the page, I presume because its being partial loaded the app is seen as having two SampleModel declarations. If I create this page completely separate and not partially loaded with the normal #model declaration it works.
I have the basic setup going so far and have included my code for each below.
Model - SampleModel
public class SampleModel
{
public int Id { get; set; }
public string SampleText { get; set; }
}
Controller - SampleController
public class SampleController : Controller
{
public const string ControllerName = "Sample";
//Open Database Connection
private _DBContext DBDatabase = new _DBContext ();
public ActionResult Index()
{
var Model = DBDatabase.Sample.Select(s => new SampleModel
{
Id = s.Id,
SampleText = s.SampleText
}).ToList();
return PartialView(Model);
}
[ActionName("_CreateModal")]
public ActionResult InsertNewRecord()
{
var Model = DBDatabase.Sample.Select(s => new SampleModel
{
Id = s.Id,
SampleText = s.SampleText
}).ToList();
return PartialView("_CreateModal", Model);
}
Views - Index, View, Create
Index - Calls Partial Views for View and Create
#using Project.Web.Controllers
#model Project.Web.Models.Sample.SampleModel
<!--html stuff here -->
#await Html.PartialAsync("_CreateModal")
<!--more html stuff here -->
#await Html.PartialAsync("_ViewData")
View - Foreach to Loop Records
#model Project.Web.Models.Sample.SampleModel
<table style="width: 100%;" id="example">
<thead>
<tr>
<th>#</th>
<th>Sample Text</th>
<th class="text-center">Status</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
#foreach (var sample in Model)
{
<tr>
<th scope="row">#sample.Id</th>
<td>#sample.SampleText</td>
<td class="text-center">
<div class="badge badge-success">Active</div>
</td>
<td class="text-center">
<div role="group" class="btn-group-sm btn-group">
<button class="btn-shadow btn btn-primary">Edit</button>
<button class="btn-shadow btn btn-primary">Delete</button>
</div>
</td>
</tr>
}
</tbody>
</table>
Create - Insert New Record
#model Project.Web.Models.Sample.SampleModel
<form method="post" asp-action="/SampleModel/CreateSample">
<div class="form-group">
<label for="CreationTime">SampleText</label>
<div>
<input type="text" class="form-control" id="SampleText" name="SampleText" placeholder="SampleText">
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Sign up</button>
</div>
</form>
As per Ammar's comment, you've just copy-pasted the Index Controller's data access. When building a form allowing the user to create a single new item, then the pattern is to typically pre-instantiate an empty model and pass it to the view:
[ActionName("_CreateModal")]
public ActionResult InsertNewRecord()
{
var model = new SampleModel(); // If Id is a GUID, then you could assign one here
return PartialView("_CreateModal", model);
}

Create more than one object of the same type in the same view

in my create view I want to give the user the possibility to create a list of objects (of the same type). Therefore I created a table in the view including each inputfield in each row. The number of rows respective "creatable" objects is a fixed number.
Lets say there is a class Book including two properties title and author and the user should be able two create 10 or less books.
How can I do that?
I don't know how to pass a list of objects (that are binded) to the controller. I tried:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ICollection<Book> bookList)
{
if (ModelState.IsValid)
{
foreach(var item in bookList)
db.Books.Add(item);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(articlediscounts);
}
And in the view it is:
<fieldset>
<legend>Book</legend>
<table id="tableBooks" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < 10 ;i++ )
{
<tr>
<td>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
</td>
<td>
<div class="editor-field">
#Html.EditorFor(model => model.Author)
#Html.ValidationMessageFor(model => model.Author)
</div>
</td>
</tr>
}
</tbody>
</table>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
As booklist is null, it doesn't work and I don't know how to put all created objects in this list.
If you have any suggestions I would be very thankful.
Scott Hanselman has some details on passing arrays to MVC control binding: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
Which is essentially: ensure your controls have the correct names: using an index for lists
Change your for loop to something like:
#for (int i = 0; i < 10 ; i++)
{
<tr>
<td>
<div class="editor-field">
<input type="text" name="book[" + i + "].Title" />
</div>
</td>
<td>
<div class="editor-field">
<input type="text" name="book[" + i + "].Author" />
</div>
</td>
</tr>
}
this will then bind to your post action automatically.
[HttpPost]
public ActionResult Create(IList<Book> bookList)
You can then show/hide these as required or use js/jquery to add them dynamically
Edit: As correctly observed by Stephen Muecke, the above answer only regards the binding from the form+fields to the HttpPost, which appears to be the emphasis of the question.
The post action in the original post is not compatible with the view. There's quite a bit of missing code in the OP that may or may not be relevant, but worth observing that if your view is for a single model, then your fail code on ModelState.IsValid needs to return a single model or your view needs to be for an IList (or similar), otherwise you won't get server-side validation (but you can still get client-side validation if you manually add it to the <input>s)
The fact you use #Html.EditorFor(model => model.Title) suggests that you have declared the model in the view as
#model yourAssembly.Book
Which allows to to post back only one Book so the POST method would need to be
public ActionResult Create(Book model)
Note that you current implementation create inputs that look like
<input id="Title" name="Title" ... />
The name attributes do not have indexers (they would need to be name="[0].Title", name="[1].Title" etc.) so cannot bind to a collection, and its also invalid html because of the duplicate id attributes.
If you want to create exactly 10 books, then you need initialize a collection in the GET method and pass the collection to the view
public ActionResult Create()
{
List<Book> model = new List<Book>();
for(int i = 0; i < 10;i++)
{
model.Add(new Book());
}
return View(model);
}
and in the view
#model yourAssembly.Book
#using (Html.BeginForm())
{
for(int i = 0; i < Model.Count; i++)
{
#Html.TextBoxFor(m => m[i].Title)
#Html.ValidationMessageFor(m => m[i].Title)
.... // ditto for other properties of Book
}
<input type="submit" .. />
}
which will now bind to your collection when you POST to
public ActionResult Create(List<Book> bookList)
Note the collection should be List<Book> in case you need to return the view.
However this may force the user to create all 10 books, otherwise validation may fail (as suggested by your use of #Html.ValidationMessageFor()). A better approach is to dynamically add new Book items in the view using either the BeginCollectionItem helper method (refer example) or a client template as per this answer.
You'd need to send a JSON object that has the list of books in it. So the first thing is to create a Model class like this:
public class SavedBooks{
public List<Book> Books { get; set; }
}
Then the Book class would have to have these 2 props:
public class Book {
public string Title { get; set; }
public string Author { get; set; }
}
Next, change your controller to use this model:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(SavedBooks model)
Then create a javascript method (using jQuery) to create a JSON object that matches the structure of the controllers SavedBooks class:
var json = { Books: [ { Title: $('#title_1').val(), Author: $('#Author_1').val() } ,
{ as many items as you want }
]
};
$.ajax(
{
url: "/Controller/Create",
type: "POST",
dataType: "json",
data: json
});

ASP MVC Model List<object> Returned Empty in POST action

I have got this problem that I am having a difficulty to solve. I am creating a page where the user will be presented with a list of items (Product Types). Each item will have a dropdown list next to it so that the user can make appropriate selection to create a mapping. After making selection then the user submits the form, and the value will be written to the database.
The problem is that when it is submitted, I am not getting any values back. Specifically, 'Mappings' is empty in the model that is returned by the POST action. The GET action works fine. The following is the essence of what I have written:
Model:
public class ProductTypeMappingViewModel
{
//this is empty in the POST object
public List<ProductTypeMapping> Mappings { get; set; }
public ProductTypeMappingViewModel()
{
Mappings = new List<ProductTypeMapping>();
}
public ProductTypeMappingViewModel(string db)
{
//use this to populate 'Mappings' for GET action
//works fine
}
public void UpdateDB()
{
//to be called on the object
//returned from POST action
foreach(var mapping in Mappings)
{
//Mappings is always empty after POST
//Suppose to add to db
}
}
}
public class ProductTypeMapping
{
public string ProductTypeName { get; set; }
public int SelectedStandardProductTypeKey { get; set; }
public SelectList StandardProductTypes { get; set; }
public ProductTypeMapping()
{
StandardProductTypes = new SelectList(new List<SelectListItem>());
}
public int GetSelectedProductTypeKey() { //return selected key}
public string GetSelectedProductTypeName() { //return selected name}
}
View:
#model CorporateM10.Models.ProductTypeMappingViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<table class="table">
#foreach (var dept in Model.Mappings)
{
<tr>
<td>
#Html.DisplayFor(model => dept.ProductTypeName, new { })
</td>
<td>
#Html.DropDownListFor(model => dept.SelectedStandardProductTypeKey, dept.StandardProductTypes, "(Select Department)", new { })
</td>
</tr>
}
</table>
<div>
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
Any insight will be greatly appreciated.
foreach here causes select element in final HTML to have incorrect name attribute. Thus nothing is posted to the server. Replace this with for loop:
<table class="table">
#for (int i=0; i<Model.Mappings.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(model => model.Mappings[i].ProductTypeName, new { })
</td>
<td>
#Html.DropDownListFor(model => model.Mappings[i].SelectedStandardProductTypeKey, model.Mappings[i].StandardProductTypes, "(Select Department)", new { })
</td>
</tr>
}
</table>
As #Andrei said the problem relies on the name attribute.
But to add a little bit to his answer, here's the parameter names in the request that the default model binder expects for your case.
Mappings[0].SelectedStandardProductTypeKey
Mappings[1].SelectedStandardProductTypeKey
Mappings[2].SelectedStandardProductTypeKey
...
Without any breaks in the numbering, i.e.:
Mappings[0].SelectedStandardProductTypeKey
Mappings[2].SelectedStandardProductTypeKey
Won't work because of the missing Mapping[1]...
When you use the dropdown helper like this:
#Html.DropDownListFor(model => dept.SelectedStandardProductTypeKey, dept.StandardProductTypes, "(Select Department)", new { })
It generates an input with name="SelectedStandardProductTypeKey" (you need it to be Mappings[0].SelectedStandardProductTypeKey)
If you use a for loop and use the dropdown helper like this:
#Html.DropDownListFor(model => model.Mappings[i].SelectedStandardProductTypeKey
You'll get the input with the correct name.
Any parameter in the request for which the model binder cannot find a property in the model, it will ignore, that's why the Mappings property is null in your case.
Here are two great resource that explain all this (and that provide alternative ways to represent collections that might be useful if you can't a the for loop to generate a numbered index without breaks):
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

MVC Form not able to post List of objects

so I have an MVC Asp.net app that is having issues. Essentially, I have a View that contains a form, and its contents are bound to a list of objects. Within this loop, it loads PartialView's with the items being looped over. Now everything works up till the submittion of the form. When it gets submitted, the controller is sent a null list of objects. The code below demonstates the problems.
Parent View:
#model IEnumerable<PlanCompareViewModel>
#using (Html.BeginForm("ComparePlans", "Plans", FormMethod.Post, new { id = "compareForm" }))
{
<div>
#foreach (var planVM in Model)
{
#Html.Partial("_partialView", planVM)
}
</div>
}
_partialView:
#model PlanCompareViewModel
<div>
#Html.HiddenFor(p => p.PlanID)
#Html.HiddenFor(p => p.CurrentPlan)
#Html.CheckBoxFor(p => p.ShouldCompare)
<input type="submit" value="Compare"/>
</div>
And these are the classes for the above code:
PlanViewModel:
public class PlansCompareViewModel
{
public int PlanID { get; set; }
public Plan CurrentPlan { get; set; }
public bool ShouldCompare { get; set; }
public PlansCompareViewModel(Plan plan)
{
ShouldCompare = false;
PlanID = plan.PlanId;
CurrentPlan = plan;
}
public PlansCompareViewModel()
{
// TODO: Complete member initialization
}
public static IEnumerable<PlansCompareViewModel> CreatePlansVM(IEnumerable<Plan> plans)
{
return plans.Select(p => new PlansCompareViewModel(p)).AsEnumerable();
}
}
Controller:
public class PlansController : MyBaseController
{
[HttpPost]
public ActionResult ComparePlans(IEnumerable<PlanCompareViewModel> model)
{
//the model passed into here is NULL
}
}
And the problem is in the controller action. As far as I am aware, it should be posting an enumerable list of PlanCompareViewModels, yet it is null. When in inspect the post data being sent, it is sending the correct params. And if I were to change 'IEnumerable' to 'FormCollection', it contains the correct values. Can anyone see why the binder is not creating the correct object? I can get around this using javascript, but that defeats the purpose! Any help would be greatly appreciated!
Your model is null because the way you're supplying the inputs to your form means the model binder has no way to distinguish between the elements. Right now, this code:
#foreach (var planVM in Model)
{
#Html.Partial("_partialView", planVM)
}
is not supplying any kind of index to those items. So it would repeatedly generate HTML output like this:
<input type="hidden" name="yourmodelprefix.PlanID" />
<input type="hidden" name="yourmodelprefix.CurrentPlan" />
<input type="checkbox" name="yourmodelprefix.ShouldCompare" />
However, as you're wanting to bind to a collection, you need your form elements to be named with an index, such as:
<input type="hidden" name="yourmodelprefix[0].PlanID" />
<input type="hidden" name="yourmodelprefix[0].CurrentPlan" />
<input type="checkbox" name="yourmodelprefix[0].ShouldCompare" />
<input type="hidden" name="yourmodelprefix[1].PlanID" />
<input type="hidden" name="yourmodelprefix[1].CurrentPlan" />
<input type="checkbox" name="yourmodelprefix[1].ShouldCompare" />
That index is what enables the model binder to associate the separate pieces of data, allowing it to construct the correct model. So here's what I'd suggest you do to fix it. Rather than looping over your collection, using a partial view, leverage the power of templates instead. Here's the steps you'd need to follow:
Create an EditorTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\EditorTemplates).
Create a strongly-typed view in that directory with the name that matches your model. In your case that would be PlanCompareViewModel.cshtml.
Now, everything you have in your partial view wants to go in that template:
#model PlanCompareViewModel
<div>
#Html.HiddenFor(p => p.PlanID)
#Html.HiddenFor(p => p.CurrentPlan)
#Html.CheckBoxFor(p => p.ShouldCompare)
<input type="submit" value="Compare"/>
</div>
Finally, your parent view is simplified to this:
#model IEnumerable<PlanCompareViewModel>
#using (Html.BeginForm("ComparePlans", "Plans", FormMethod.Post, new { id = "compareForm" }))
{
<div>
#Html.EditorForModel()
</div>
}
DisplayTemplates and EditorTemplates are smart enough to know when they are handling collections. That means they will automatically generate the correct names, including indices, for your form elements so that you can correctly model bind to a collection.
Please read this: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
You should set indicies for your html elements "name" attributes like planCompareViewModel[0].PlanId, planCompareViewModel[1].PlanId to make binder able to parse them into IEnumerable.
Instead of #foreach (var planVM in Model) use for loop and render names with indexes.
In my case EditorTemplate did not work. How I did -
ViewModel file -
namespace Test.Models
{
public class MultipleFormViewModel
{
public int Value { get; set; }
public string Title { get; set; }
}
}
Main View (cshtml) file -
#model List<MultipleFormViewModel>
#{
var list = new List<MultipleFormViewModel>();
ViewData["index"] = 0;
}
#using (Html.BeginForm("SaveDataPoint", "MultipleForm", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
for (int i = 0; i < 3; i++)
{
var dataPoint = new MultipleFormViewModel();
list.Add(dataPoint);
ViewData["index"] = i;
#Html.Partial("_AddDataPointFormPartial", ((List<MultipleFormViewModel>)list)[i], ViewData)
}
<input type="submit" value="Save Data Points" />
}
Partial View (under Shared folder) file -
#model MultipleFormViewModel
#{
var index = ViewData["index"];
}
<div>
<div>
<label>Value:</label>
<input asp-for="Value" name="#("["+index+"].Value")" id="#("z"+index+"__Value")" />
</div>
<div>
<label>Title:</label>
<input asp-for="Title" name="#("["+index+"].Title")" id="#("z"+index+"__Title")" />
</div>
</div>
And Finally Controller -
[HttpPost]
public IActionResult SaveDataPoint(List<MultipleFormViewModel> datapoints)
{
Console.WriteLine(datapoints.Count);
//Write your code
return Content("hello..");
}

Holding values of the search after refreshing the page

I have asked this question, I didn't get my answer. So I did much more researches about this and still couldn't find a proper good answer for this.
My controller (shortened):
public ActionResult SearchResult(String sortOrder, String carMake, String carModel)
{
var cars = from d in db.Cars
select d;
if (!String.IsNullOrEmpty(carMake))
{
if (!carMake.Equals("All Makes"))
{
cars = cars.Where(x => x.Make == carMake);
}
}
if (!String.IsNullOrEmpty(carModel))
{
if (!carModel.Equals("All Models"))
{
cars = cars.Where(x => x.Model == carModel);
}
}
switch (sortOrder)
{
case "Model":
cars = cars.OrderBy(s => s.Model);
break;
default:
cars = cars.OrderBy(s => s.Make);
break;
}
return View(cars);
}
My Index view (shortened - this is where user filters cars by different inputs):
#model IEnumerable<Cars.Models.Car>
#using (Html.BeginForm("SearchResult", "Home", FormMethod.Post))
{
<label>Make</label>
<select id="MakeID" name="carMake">
<option>All Makes</option>
</select>
<label>Model</label>
<select id="ModelID" name="carModel">
<option>All Models</option>
</select>
<button type="submit" name="submit" value="search" id="SubmitID">Search</button>
}
My SearchResult view where shows the search results (shortened):
#model IEnumerable<Cars.Models.Car>
<table>
#foreach (var item in Model)
{
<tr>
<td>
<label>Make:</label>
<p>#Html.DisplayFor(modelItem => item.Make)</p>
</td>
<td>
<label>Model:</label>
<p>#Html.DisplayFor(modelItem => item.Model)</p>
</td>
</tr>
}
</table>
Model
My goal: When user clicks on the sort by Model the page will sort the results by Model.
Problem: When the sortby is clicked, all the parameters of action SearchResult will be null since the search value do not exist in SearchResult View.
Question: How to fix this?
Thanks, Amin
UPDATE: Any example would be deeply appreciated. I'm stuck with back-end the whole process of sending and fetching data between controller and view.
You can create one class like below
public CarFilter CarFilter;
public byte PageSize;
public short PageNumber;
public int TotalRows;
where CarFilter is another class where you can store your filters enter by users.
and use this class as a model for your view.so that when you view is loading you can load car filter data.
You can retain your search filters by doing any of the following:
Use sessions to store the filters on your search result view. This way, when you post on the same view using Sort you would still have the search filters.
(I prefer this one) Wrap your model in a class that has properties for your search filters. This way, you will still be able to retrieve the search filters. Provided that you have a POST for SearchResult that accepts the Wrapper Model as parameter.
Create Classes as follow:
class ManageCarSearch
{
public CarFilter CarFilter;
public byte PageSize;
public short PageNumber;
public int TotalRows;
public String sortOrder;
}
Class CarFilter
{
public String carMake,
public String carModel
}
your searchResult view look like below:
#model IEnumerable<car>
<table>
#foreach (var item in Model)
{
<tr>
<td>
<label>Make:</label>
<p>#Html.DisplayFor(modelItem => item.Make)</p>
</td>
<td>
<label>Model:</label>
<p>#Html.DisplayFor(modelItem => item.Model)</p>
</td>
</tr>
}
</table>
Model
and controller as below:
public ActionResult SearchResult(String sortOrder, String carMake, String carModel)
{
var cars = from d in db.Cars
select d;
ManageCarSearch objsearch = new ManageCarSearch();
////your Logic
return View(objsearch);
}
and you serach view look like below:
#model IEnumerable<ManageCarSearch>
#using (Html.BeginForm("SearchResult", "Home", FormMethod.Post))
{
<label>Make</label>
<select id="MakeID" name="carMake">
<option>All Makes</option>
</select>
<label>Model</label>
<select id="ModelID" name="carModel">
<option>All Models</option>
</select>
<button type="submit" name="submit" value="search" id="SubmitID">Search</button>
}

Categories

Resources