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>
}
Related
I have a index in controller and view following.
When I enter student ID on textbox search, I want to display information of student and list all activities of them. How to do that.
Thank you so much!
Controller:
public ActionResult Index(string Sid)
{
var grad = db.Gradings.Include(g => g.Activity).Include(g => g.Student).Where(g => g.StudentID == Sid).ToList();
}
View:
<div class="col-xs-4">
<p>
#using (Html.BeginForm())
{
<p>
Student ID: #Html.TextBox("Sid",ViewBag.FilterValue as string)
<input type="submit" value="Search" />
</p>
}
<table class="table">
<tr>
<th>Activity Name</th>
....
</tr>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.Activity.Name)</td>
<td>#Html.DisplayFor(modelItem => item.Responsibility)</td>
....
</tr>
}
</table>
</div>
<h3> Total score: #ViewBag.Total</h3>
Thanks for your help!
You need one action to serve your view initially when the user requests it:
public ActionResult Index()
{
return View();
}
Then you need another action to process the user's request when the request submits the form:
public ActionResult Index(string Sid)
{
var grad = db.Gradings.Include(g => g.Activity).Include(g => g.Student).Where(g => g.StudentID == Sid).ToList();
return View(grad);
}
Now grad is a List<Grading> and above we are passing it to the view as the model so make sure you use it in your view. You may need to include the namespace for List and Grading:
#model List<Grading>
Finally instead of using foreach in your view, use a for loop so your HTML tags have unique IDs. Right now (with foreach), all Grading records will have the same name and id attributes.
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
I have a view model which holds the available products and after i show them in a view i have to get the particular item which he clicks on Add to Cart
My Action
public ActionResult Test1()
{
DUPVM obj = new DUPVM();
MembersipEntities Entity = new MembersipEntities();
obj.test = Entity.Dups.ToList();
return View(obj);
}
[HttpPost]
public ActionResult Test1(Dup model)
{
return View(model);
}
and the viewmodel
public class DUPVM
{
public IEnumerable<Dup> test { get; set; }
}
And in my view i will display the all products available by looping through them
#model MVC4.Models.DUPVM
#using (Html.BeginForm("Test1", "PDF", FormMethod.Post))
{
foreach (var item in Model.test)
{
#Html.DisplayFor(modelitem => item.ID)
<br>
#Html.DisplayFor(modelitem => item.Rate)
#Html.DisplayFor(modelitem => item.AgentID)
#Html.TextBoxFor(modelitem => item.ID)
<br>
<input type="submit" value="Add to cart" />
<br>
}
}
Then If a user clicks on Add to cart then i want to pass all details of that products to controller using model. But i am not able to do this.
First change your view to create the form inside the loop. This way you are getting a discrete form with only the values you require.
#model MVC4.Models.DUPVM
foreach (var item in Model.test)
{
#using (Html.BeginForm("Test1", "PDF", FormMethod.Post))
{
#Html.DisplayFor(modelitem => item.ID)
<br>
#Html.DisplayFor(modelitem => item.Rate)
#Html.DisplayFor(modelitem => item.AgentID)
#Html.TextBoxFor(modelitem => item.ID)
<br>
<input type="submit" value="Add to cart" />
<br>
}
}
Your view is strongly-typed to the DUPVM class but your controller action is expecting a single Dup. The default behaviour of the form Razor helpers is to post the model back in the same format it was accepted.
To get this to work you will need to create a partial view for each item, strongly type that partial view to a Dup (not a DUPVM) and either write some javascript that posts your selected item to the controller, or have a seperate form for each item.
You partial view might look something like this
#model MVC4.Models.Dup
#using (Html.BeginForm("Test1", "PDF", FormMethod.Post))
{
#Html.DisplayFor(x => x.ID)
<br>
#Html.DisplayFor(x => x.Rate)
#Html.DisplayFor(x => x.AgentID)
#Html.TextBoxFor(x => x.ID)
<br>
<input type="submit" value="Add to cart" />
<br>
}
And your main view like this:
#model MVC4.Models.DUPVM
#foreach (var item in Model.test)
{
Html.RenderPartial("_YourPartialView", item);
}
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
I have created my own custom ValidationAttribute:
public class UrlValidationAttribute : ValidationAttribute
{
public UrlValidationAttribute() {}
public override bool IsValid(object value)
{
if (value == null)
return true;
var text = value as string;
Uri uri;
return (!string.IsNullOrWhiteSpace(text) &&
Uri.TryCreate(text, UriKind.Absolute, out uri));
}
}
I am using that on one of my models and it works perfectly. However, now I am attempting to use it on a view model:
public class DeviceAttribute
{
public DeviceAttribute(int id, attributeDefinition, String url)
{
ID = id;
Url = url;
}
public int ID { get; set; }
[UrlValidation]
public String Url { get; set; }
}
The view model is used in the partial view like this:
#model List<ICMDB.Models.DeviceAttribute>
<table class="editor-table">
#foreach (var attribute in Model)
{
<tr>
#Html.HiddenFor(a => attribute.ID)
<td class="editor-label">
#Html.LabelFor(a => attribute.Url)
</td>
<td class="editor-field">
#Html.TextBoxFor(a => attribute.Url)
#Html.ValidationMessageFor(a => attribute.Url)
</td>
</tr>
}
</table>
For some unknown reason, while the constructor for UrlValidationAttribute fires, the IsValid function doesn't fire. Any ideas?
Edit: On further investigation, it seems this is happening because the DeviceAttribute view model is actually the view model for a partial. The full page is passed a different view model that contains the list of DeviceAttribute view models. So when my controller action is called, the full page view model is constructed and its values filled, but no DeviceAttribute view models are constructed, hence why no validation is run.
I would recommend you using editor templates instead of writing foreach loops. I suppose that your main view model looks something like this:
public class MyViewModel
{
public List<DeviceAttribute> Devices { get; set; }
...
}
Now in your main view:
#model MyViewModel
#using (Html.BeginForm())
{
<table class="editor-table">
#Html.EditorFor(x => x.Devices)
</table>
<input type="submit" value="OK" />
}
and in the corresponding editor template (~/Views/Shared/EditorTemplates/DeviceAttribute.cshtml):
#model DeviceAttribute
<tr>
#Html.HiddenFor(x => x.ID)
<td class="editor-label">
#Html.LabelFor(x => x.Url)
</td>
<td class="editor-field">
#Html.TextBoxFor(x => x.Url)
#Html.ValidationMessageFor(x => x.Url)
</td>
</tr>
And your POST action takes the view model back:
[HttpPost]
public ActionResult Index(MyViewModel model)
{
...
}
Now the default model binder will successfully bind all values in the view model and kick validation.
Here's a nice blog post about templates.