CheckBoxFor is not bounded when a property is defined in an object nested in the model?
Here is an example. I have a SearchOptions model that contains a List<Star> property. Each Star has a number, a name and a bool property that should be bounded:
public class SearchOptions
{
public SearchOptions()
{
// Default values
Stars = new List<Star>()
{
new Star() {Number=1, Name=Resources.Home.Index.Star1,
IsSelected=false},
new Star() {Number=2, Name=Resources.Home.Index.Star2,
IsSelected=false},
new Star() {Number=3, Name=Resources.Home.Index.Star3,
IsSelected=true},
new Star() {Number=4, Name=Resources.Home.Index.Star4,
IsSelected=true},
new Star() {Number=5, Name=Resources.Home.Index.Star5,
IsSelected=true},
};
}
public List<Star> Stars { get; set; }
}
In my strongly typed View (of SearchOptions) i loop over Stars property:
#using (Html.BeginForm("Do", "Home"))
{
<fieldset>
<legend>#MVC3TestApplication.Resources.Home.Index.Search</legend>
#{
foreach (Star s in Model.Stars)
{
#Html.CheckBoxFor(m => s.IsSelected)
<label>#s.Name</label>
}}
</fieldset>
<input type=submit value="Invia" />
}
The (relevant part of) controller is:
public ActionResult SearchOptions()
{
return View(new SearchOptions());
}
[HttpPost]
public ActionResult Do(SearchOptions s)
{
// Do some stuff
return View("SearchOptions", s);
}
It's because of how you're accessing the properties in the CheckBoxFor expression.
#for (int i = 0; i < Model.Stars.Count(); i++) {
#Html.CheckBoxFor(m => m.Stars[i].IsSelected)
<label>#Model.Stars[i].Name</label>
}
This should work for you.
Here's the output from the different methods:
//using the for loop
<input id="Stars_2__IsSelected" name="Stars[2].IsSelected" type="checkbox" value="true" />
//using the foreach
<input checked="checked" id="s_IsSelected" name="s.IsSelected" type="checkbox" value="true" />
You'll notice that the for foreach doesn't contain the proper name for it to match to when doing model binding.
Related
The answer I am sure is simple. I have a <select> with a list of values. For edit mode, I want the drop down to show the current value and have the selected when the view renders. And also when the form is submitted take a possible new selected value and pass it back to the controller. Any help would be greatly appreciated.
From the view:
<td style="padding:15px">
<label asp-for="OrganizationTypeId" class="form-control-label" style="font-weight:bold">Organization</label>
<select asp-for="OrganizationTypeId" class="form-control" style="width:450px" asp-items="#(new SelectList(Model.orgTypes, "Id", "OrganizationName"))">
<option value="" disabled hidden selected>Select Organization....</option>
</select>
</td>
Code in the controller:
dr = _electedOfficials.getDeputyReg(jurisdictionId, Id);
dr.orgTypes = _electedOfficials.GetOrganizationTypes(jurisdictionId);
return View(dr);
OrgTypes class
public int Id { get; set; }
public string OrganizationName { get; set; }
One of the solutions is preparing list of the SelectListItem and return the selected item Id to the controller:
public ActionResult Index()
{
// ...
dr.orgTypes = _electedOfficials.GetOrganizationTypes(jurisdictionId);
var model = dr.orgTypes.Select(d => new SelectListItem() { Selected = (d.Id == /* id of default selection*/), Text = d.OrganizationName, Value = d.Id.ToString() }).ToList();
return View(model);
}
[HttpPost]
public ActionResult Index(int? seletedId)
{
if (ModelState.IsValid && seletedId.HasValue)
{
// Processing the selected value...
}
return RedirectToAction("Index");
}
In the view:
#model IEnumerable<SelectListItem>
<script type="text/javascript">
$(document).ready(function () {
var e = document.getElementById("OrgTypesList");
$("#SeletedId").val(e.options[e.selectedIndex].value);
});
function changefunc(val) {
$("#SeletedId").val($("#OrgTypesList").val());
}
</script>
#using (Html.BeginForm("Index", "Home"))
{
#* To pass `SeletedId` to controller *#
<input id="SeletedId" name="SeletedId" type="hidden" />
<label asp-for="OrganizationTypeId" class="form-control-label" style="font-weight:bold">Organization</label>
#Html.DropDownList("OrgTypesList", Model, "Select Organization...", new { #class = "form-control", #onchange = "changefunc(this.value)" })
<button type="submit" class="btn btn-primary">Save</button>
}
I have my code as below.Can somebody help me in this I am not able to send model to view.
ViewModel class
IEnumerable<CarList> MyCarPositions.
In my view.
#model TestMVC.ViewModel.
foreach (var item in Model.MyCarPositions)
{
#Html.TextBoxFor(x => item.Name)
#Html.TextBoxFor(x => item.Brand)
}
My Controller
ViewModel carviewmodel = new carviewmodel();
[HttpGet]
public ActionResult Index()
{
carviewmodel.MyCarPositions = repository.GetCarPositions();
return View(carviewmodel);
}
[HttpPost]
public ActionResult Index(ViewModel carvmodel)
{
// Here in httppost carvmodel comes as null.
}
Your foreach loop is generating duplicate name attributes without indexers which cannot be bound to your model. Its also generating invalid html because of the duplicate id attributes. You need to generate the collection using a custum EditorTemplate for the type in your collection, or use a for loop.
You have not shown you models, but property MyCarPositions needs to be List<T> if using the for loop option.
#model TestMVC.ViewModel
#using Html.BeginForm())
{
for (int i = 0; i < Model.MyCarPositions.Count, i++)
{
#Html.TextBoxFor(x => x.MyCarPositions[i].Name)
#Html.TextBoxFor(x => x.MyCarPositions[i].Brand)
}
<input type="submit" .. />
}
This will generate the correct name attributes necessary for binding
<input type="text" name="MyCarPositions[0].Name" .... />
<input type="text" name="MyCarPositions[1].Name" .... />
<input type="text" name="MyCarPositions[2].Name" .... />
Side note: You should be initializing the model inside the controller method
// ViewModel carviewmodel = new carviewmodel(); remove this
[HttpGet]
public ActionResult Index()
{
ViewModel carviewmodel = new carviewmodel(); // initialize it here
carviewmodel.MyCarPositions = repository.GetCarPositions();
return View(carviewmodel);
}
Right away I can see a syntax error that might be causing your issue.
foreach (var item in Model.MyCarPositions)
{
#Html.TextBoxFor(x => item Name <--- missing a '.' and a closing bracket
#Html.TextBoxFor(x=>item.Brand)
}
Should be:
foreach (var item in Model.MyCarPositions)
{
#Html.TextBoxFor(x =>item.Name)
#Html.TextBoxFor(x=>item.Brand)
}
Tpa class is my base model.
public class Tpa
{
public bool selected { get; set; }
public int Id { get; set; }
}
Data class creates a list of Tpa objects.
public class Data
{
public List<Tpa> Tpas { set; get; }
public Data()
{
this.Tpas = new List<Tpa>();
this.Tpas.Add(new Tpa()
{
selected = false ,
Id = 1,
});
this.Tpas.Add(new Tpa()
{
selected = false,
Id = 2,
});
this.Tpas.Add(new Tpa()
{
selected = true,
Id = 3,
});
}
}
This is my Get.
[HttpGet]
public virtual ActionResult Index()
{
var model = new Data();
return View(model);
}
This is my view.
#model TpaUpload_2.Models.Data
#using (Html.BeginForm(MVC.TpaUpload_2.Home.ReceiveID(), FormMethod.Post))
<table class="table">
<tr>
#for (int count = 0; count < Model.Tpas.Count; count++)
{
var item = Model.Tpas[count];
<tr>
<td>
<input type="checkbox"
name=#Html.Raw("'s" + count + "CheckBox'")
id=#Html.Raw("'s" + count + "CheckBox'")
#*checked="#(item.selected == true)"*# />
<label for=#Html.Raw("'s" + count + "CheckBox'" )></label>
<input type='hidden'
id=#Html.Raw("'s" + count + "CheckBox'" )
name='item.selected'
value=#Html.Raw("'"+item.selected+"'")/>
</tr>
</table>
<input type="submit" value="Submit" />
This my Post.
[HttpPost]
public virtual ActionResult ReceiveID(Data myData)
{
...
}
I'm trying to use the checkbox value to change the "selected" on the model, and post back the model.
The problem is after the Form is submitted to the Post, the program will construct a new Data object, instead of using the Data model passed to the controller.
What did I do wrong? Any help will be greatly appreciated.
Your constructing html with name attributes that have absolutely no relationship to you model. When you submit, the DefaultModelBinder first initializes your Data model (which means that 3 new Tpa objects are added to its Tpas property. It then tries to find name/value pairs in the form collection that match you model properties but there are none.
First you need to modify you constructor to include only the initialization of the list, and remove the adding of the new items
public class Data
{
public List<Tpa> Tpas { set; get; }
public Data()
{
Tpas = new List<Tpa>();
}
}
And add the items in the GET method
public virtual ActionResult Index()
{
var model = new Data();
model.Tpas .Add(new Tpa(){ selected = false, Id = 1 });
// add other items
return View(model);
}
Then you need to construct you view correctly using the strongly typed html helpers so that your form controls are correctly named in relationship to your model
<table class="table">
#for (int i = 0; i < Model.Tpas.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(m => m.Tpas[i].Id)
#Html.CheckBoxFor(m => m.Tpas[i].selected)
#Html.LabelFor(m => m.Tpas[i].selected)
</td>
</tr>
}
</table>
This give your controls the correct name attribute for model binding, for example
<input type="hidden" name="Tpas[0].Id" ... />
<input type="hidden" name="Tpas[1].Id" ... />
<input type="hidden" name="Tpas[2].Id" ... />
I suggest you compare that with what your currently generating to understand the difference.
Note also your current html is invalid - you have multiple <tr> elements inside a <tr> elements and you need to include the hidden input for the Id property or else this will not post back and you will end up with 3 Tpa objects all with id = 0.
I've read and implemented many answers here and around the web but had no luck..
My model looks something like this:
public class CampaignModel : BaseModel
{
.
.
public List<TreeItem> Countries { get; set; }
.
.
}
In the view i have:
#foreach (var country in Model.Countries.Select((value,i)=> new {i, value}))
{
<input type="checkbox" name="campaign.Countries[#country.i].Id" value="#country.value.Id" #(country.value.IsSelected ? "checked=\"checked\"" : "") />
}
At the action I have:
[HttpPost]
public ActionResult UpdateTargeting(CampaignModel campaign)
{
return View(campaign);
}
But the 'Countries' property turns out null.
What am I doing wrong?
Thank you
First I think you need to use Model instead of campaign in the name attributes, like below:
name="#Model.Countries[#country.i].Id"
And right now your foreach loop will generate the html code like below:
<input type="checkbox" name="1" value="1"/>
<input type="checkbox" name="2" value="2" checked=""checked""/>
With above code, the model binding will not work, that's why you got null values when you submitted the form. You need something like below:
<input id="Countries_0__IsSelected" name="Countries[0].IsSelected" type="checkbox" value="true"/>
<input name="Countries[0].IsSelected" type="hidden" value="false"/>
<input checked="checked" id="Countries_1__IsSelected" name="Countries[1].IsSelected" type="checkbox" value="true"/>
<input name="Countries[1].IsSelected" type="hidden" value="false"/>
So I suggest you to use Razor syntax, like below:
foreach (var country in Model.Countries.Select((value,i)=> new {i, value}))
{
#Html.CheckBoxFor(m => m.Countries[#country.i].IsSelected )
}
I ended up adding 2 more hidden fields to identify the exact checkbox.
The TreeItem lolks like this:
public class TreeItem
{
public int Id { get; set; }
public string Title { get; set; }
public bool IsSelected { get; set; }
public List<TreeItem> Leafes { get; set; }
public TreeItem() { }
public TreeItem(int id, string title, bool selected = false, List<TreeItem> leafes = null)
{
Id = id;
Title = title;
IsSelected = selected;
Leafes = leafes;
}
}
The full solution looks like this (I demonstrate a a 2 level hierarchy):
<ul id="devicesList">
#foreach (var brand in Model.DeviceBrands.Select((value, i) => new { i, value }))
{
<li>
#Html.CheckBoxFor(m => m.DeviceBrands[#brand.i].IsSelected)
#Html.HiddenFor(m => m.DeviceBrands[#brand.i].Id)
#Html.HiddenFor(m => m.DeviceBrands[#brand.i].Title)
<label><b>#brand.value.Title</b></label>
<ul>
#foreach (var deviceModel in brand.value.Leafes.Select((value, j) => new { j, value }))
{
<li>
#Html.CheckBoxFor(m => m.DeviceBrands[#brand.i].Leafes[#deviceModel.j].IsSelected)
#Html.HiddenFor(m => m.DeviceBrands[#brand.i].Leafes[#deviceModel.j].Id)
#Html.HiddenFor(m => m.DeviceBrands[#brand.i].Leafes[#deviceModel.j].Title)
<label>#deviceModel.value.Title</label>
</li>
}
</ul>
</li>
}
</ul>
Thanks Lin!
Having trouble creating a list of radio buttons that are grouped together, in MVC 3 specifically, but this also applies to MVC 2.
The problem arises when radio buttons are generated using Html helpers and the model is part of an array.
Here is the cut down version of my code.
public class CollectionOfStuff {
public MVCModel[] Things { get; set }
}
/*This model is larger and represents a Person*/
public class MVCModel {
[UIHint("Hidden")]
public string Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
/*Assigned to new CollectionOfStuff property Things*/
var items = new[] {
new MVCModel() { Id="0" Name = "Name here" }, new MVCModel() { Id="1" Name = "Name there" }
}
My parent view
#model CollectionOfStuff
#for (int i = 0; i < Model.Things.Length; i++) {
#Html.EditorFor(m => m.Things[i]);
}
My view rendering individual MVCModel objects
#Model MVCModel
#{
var attr = new {
Checked = Model.IsSelected ? "checked=checked" : ""
};
}
#Html.RadioButtonFor(model => model, Model.Id, attr)
Produces this output:
<input type="radio" value="0" name="MVCModel[0]" id="MVCModel_0_" data-val-required="You need to choose" data-val="true" />
<input type="radio" value="1" name="MVCModel[1]" id="MVCModel_1_" data-val-required="You need to choose" data-val="true" />
The radio buttons are not grouped, however it has the obvious advantage of writing out the meta data for validation.
The other way is by calling:
#Html.RadioButton(name: "GroupName", value: Model.Id, isChecked: Model.IsSelected)
Produces:
<input type="radio" value="0" name="MVCModel[0].GroupName" id="MVCModel_0__GroupName">
<input type="radio" value="1" name="MVCModel[1].GroupName" id="MVCModel_1__GroupName">
Again, this doesn't produce the desired result. It's also missing the validation meta data.
Another other option is creating a custom template, but the problem with this approach is that all the meta data required for validation is not present.
Any ideas on how I can create grouped radio buttons or obtain meta data so I can create a template myself?
You haven't shown how does your view model look like but you could group them by some property. So let's take an example:
public class MyViewModel
{
[Required]
public string SomeProperty { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View:
#model AppName.Models.MyViewModel
#using (Html.BeginForm())
{
<div>A: #Html.RadioButtonFor(x => x.SomeProperty, "a")</div>
<div>B: #Html.RadioButtonFor(x => x.SomeProperty, "b")</div>
#Html.ValidationMessageFor(x => x.SomeProperty)
<input type="submit" value="OK" />
}
Now if you want to preselect some radio simply set the property of the view model to the corresponding value of the radio instead of writing some ugly C# code in your views:
public ActionResult Index()
{
var model = new MyViewModel
{
SomeProperty = "a" // select the first radio
};
return View(model);
}
Obviously this technique works with any simple property type (not only strings) and with any number of radio buttons that could be associated to this property.