MVC Model binding of complex objects - asp.net-mvc

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!

Related

Input type checkbox with MVC razor

Why is the value of my checkbox not passed to my ViewModel?
My View (I omitted input tags not relevant for this post):
#model Pro.WebUI.ViewModels.UserViewModel
#using (Html.BeginForm("ManageUsers", "Administration", FormMethod.Post,
new { id = "request-form", #class = "form-horizontal" }))
{
<div class="form-group">
<label for="inputAuthorize" class="col-lg-2 control-label">Authorize</label>
<div class="col-lg-8">
<input type="checkbox" id="Authorized" name="Authorized" value="#Model.Authorized" />
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<br /><br />
<button type="submit" class="btn btn-primary">Submit Request</button>
</div>
</div>
}
My ViewModel:
public class UserViewModel
{
[Key]
public string UserID { get; private set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Authorized { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Notes { get; set; }
}
My Controller:
[HttpPost]
public ActionResult ManageUsers(UserViewModel model)
{
if (ModelState.IsValid)
{
ProcurementUser obj = new ProcurementUser();
obj.UserName = model.Email;
obj.FirstName = model.FirstName;
obj.LastName = model.LastName;
obj.Email = model.Email;
obj.Phone = model.Phone;
obj.Authorized = model.Authorized;
UserRepository.SaveUser(obj);
//success message
}
return View(model);
}
I did not include all input tags but when I step through the code without the checkbox, all values are passed. I looked at other checkbox questions on SOF but they mostly use the #Html.Checkbox or #Html.CheckboxFor. I would like to just use input type="checkbox"
If we need to use <input> filed instead of #Html.CheckboxFor, we can use "checked=\"checked\"" syntax as in this code:
<input type="checkbox" id="Authorized" name="Authorized" value="true" #(Model.Authorized ? "checked=\"checked\"" : "") />
As has been hinted at in the comments the issue you're having is that you're not really creating your checkbox correctly:
Assuming your model has Authorized = true your mark-up would be:
<input type="checkbox" id="Authorized" name="Authorized" value="true" />
Similarly the false state would result in:
<input type="checkbox" id="Authorized" name="Authorized" value="false" />
But these aren't "checked" checkboxes - they're still "unchecked", and need the checked attribute setting:
<input type="checkbox" id="Authorized" name="Authorized" value="true" checked />
As Stephen points out - an unchecked checkbox will not send any data back to the server so that you don't get confused about which options where selected.
Finally, as has also been noted, your <label> element is for an non-existent field looking for inputAuthorize instead of Authorized.
All of these issues would be taken care of for you if you were to use the #Html.CheckboxFor and #Html.LabelFor helper classes.

MVC post a list of complex objects

I have a FeedbackViewModel that contains a list of questions:
public class FeedbackViewModel
{
public List<QuestionViewModel> Questions { get; set; }
}
This QuestionViewModel is an object that can be inherited by 5 different types of questions
public class QuestionViewModel
{
public string QuestionText { get; set; }
public string QuestionType { get; set; }
}
An example of one of the inheriting question types:
public class SingleQuestionViewModel : QuestionViewModel
{
public string AnswerText { get; set; }
}
In the HttpGet of the Index action in the controller I get the questions from the database and add the correct question type in list of question in the FeedbackViewModel Then I render this model in the view:
#using (Html.BeginForm())
{
//foreach (var item in Model.Questions)
for (int i = 0; i < Model.Questions.Count; i++)
{
<div class="form-group">
#Html.DisplayFor(modelItem => Model.Questions[i].QuestionText, new { #class = "control-label col-md-4" })
<div class="col-md-6">
#if (Model.Questions[i].QuestionType == "Single")
{
#Html.EditorFor(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText)
}
else if (Model.Questions[i].QuestionType == "Multiple")
{
#Html.TextAreaFor(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText)
}
else if (Model.Questions[i].QuestionType == "SingleSelection")
{
#Html.RadioButtonForSelectList(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleSelectionQuestionViewModel).SelectedAnswer,
(Model.Questions[i] as OpenDataPortal.ViewModels.SingleSelectionQuestionViewModel).SelectionAnswers)
}
else if (Model.Questions[i].QuestionType == "MultipleSelection")
{
#Html.CustomCheckBoxList((Model.Questions[i] as OpenDataPortal.ViewModels.MultipleSelectionQuestionViewModel).AvailableAnswers)
}
else if (Model.Questions[i].QuestionType == "UrlReferrer")
{
#Html.EditorFor(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText)
}
</div>
</div>
<br />
}
<br />
<button type="submit">Submit</button>
}
Now, I simply can't get it to post the list of questions in the model. Is it even possible to post a list of different object types?
Edit: Following is the list of data within the post that I discovered using Fiddler:
After much research I've found two solutions:
One is to write HTML that has hardcoded Id's and Names
Two is to convert your ICollection/IEnumerable to an Array or List (i.e IList something with an 'index'), and have an Array object in your BindingModel in your Controller POST Action.
Thanks to Phil Haack's (#haacked) 2008 blog post http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
Which is still relevant to how the default ModelBinder works today for MVC.
(NB: the links in Phil's article to sample porject and extension methods are broken)
HTML snippet that inspired me:
<form method="post" action="/Home/Create">
<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />
<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />
<input type="submit" />
</form>
Post array looks a bit like:
products.Index=cold&products[cold].Name=Beer&products[cold].Price=7.32&products.Index=123&products[123].Name=Chips&products[123].Price=2.23
Model:
public class CreditorViewModel
{
public CreditorViewModel()
{
this.Claims = new HashSet<CreditorClaimViewModel>();
}
[Key]
public int CreditorId { get; set; }
public string Comments { get; set; }
public ICollection<CreditorClaimViewModel> Claims { get; set; }
public CreditorClaimViewModel[] ClaimsArray {
get { return Claims.ToArray(); }
}
}
public class CreditorClaimViewModel
{
[Key]
public int CreditorClaimId { get; set; }
public string CreditorClaimType { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:N2}")]
public Decimal ClaimedTotalAmount { get; set; }
}
Controller GET:
public async Task<ActionResult> Edit(int id)
{
var testmodel = new CreditorViewModel
{
CreditorId = 1,
Comments = "test",
Claims = new HashSet<CreditorClaimViewModel>{
new CreditorClaimViewModel{ CreditorClaimId=1, CreditorClaimType="1", ClaimedTotalAmount=0.00M},
new CreditorClaimViewModel{ CreditorClaimId=2, CreditorClaimType="2", ClaimedTotalAmount=0.00M},
}
};
return View(model);
}
Edit.cshtml:
#Html.DisplayNameFor(m => m.Comments)
#Html.EditorFor(m => m.Comments)
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().CreditorClaimType)
</th>
<th>
#Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().ClaimedTotalAmount)
</th>
</tr>
<!--Option One-->
#foreach (var item in Model.Claims)
{
var fieldPrefix = string.Format("{0}[{1}].", "Claims", item.CreditorClaimId);
<tr>
<td>
#Html.DisplayFor(m => item.CreditorClaimType)
</td>
<td>
#Html.TextBox(fieldPrefix + "ClaimedTotalAmount", item.ClaimedTotalAmount.ToString("F"),
new
{
#class = "text-box single-line",
data_val = "true",
data_val_number = "The field ClaimedTotalAmount must be a number.",
data_val_required = "The ClaimedTotalAmount field is required."
})
#Html.Hidden(name: "Claims.index", value: item.CreditorClaimId, htmlAttributes: null)
#Html.Hidden(name: fieldPrefix + "CreditorClaimId", value: item.CreditorClaimId, htmlAttributes: null)
</td>
</tr>
}
</table>
<!--Option Two-->
#for (var itemCnt = 0; itemCnt < Model.ClaimsArray.Count(); itemCnt++)
{
<tr>
<td></td>
<td>
#Html.TextBoxFor(m => Model.ClaimsArray[itemCnt].ClaimedTotalAmount)
#Html.HiddenFor(m => Model.ClaimsArray[itemCnt].CreditorClaimId)
</td></tr>
}
Form is processed in the Controller:
Post Model:
public class CreditorPostViewModel
{
public int CreditorId { get; set; }
public string Comments { get; set; }
public ICollection<CreditorClaimPostViewModel> Claims { get; set; }
public CreditorClaimPostViewModel[] ClaimsArray { get; set; }
}
public class CreditorClaimPostViewModel
{
public int CreditorClaimId { get; set; }
public Decimal ClaimedTotalAmount { get; set; }
}
Controller:
[HttpPost]
public ActionResult Edit(int id, CreditorPostViewModel creditorVm)
{
//...
Make sure you are rendering your view in order so that Model.Questions[i] renders in order.
For example, Model.Questions[0], Model.Questions[1], Model.Questions[2].
I noticed that if the order is not correct mvc model binder will only bind the first element.
Thanks for pointing me in the right direction with this post. I was struggling to get the syntax right for binding a non-sequential IDictionary<string, bool> object. Not sure this is 100% correct, but this Razor code worked for me:
<input type="hidden" name="MyDictionary.Index" value="ABC" />
<input type="hidden" name="MyDictionary[ABC].Key" value="ABC" />
#Html.CheckBox(name: "MyDictionary[ABC].Value", isChecked: Model.MyDictionary["ABC"], htmlAttributes: null)
If you need a checkbox, be sure to use Html.CheckBox instead of a standard HTML checkbox. The model will blow up if a value is not provided, and Html.CheckBox generates a hidden field to ensure a value is present when the checkbox is not checked.
Using Razor you can implement the for loop using a dictionary as follows without making changes to your object:
#foreach (var x in Model.Questions.Select((value,i)=>new { i, value }))
{
if (Model.Questions[x.i].QuestionType == "Single")
{
#Html.EditorFor(modelItem => (modelItem.Questions[x.i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText)
}
...
}
The collection needs to be either a List or Array for this to work.
I use this code maybe its can help
<input type="hidden" name="OffersCampaignDale[#(item.ID)].ID" value="#(item.ID)" />
#Html.Raw(Html.EditorFor(modelItem => item.NameDale, new { htmlAttributes = new { #class = "form-control" } })
.ToString().Replace("item.NameDale", "OffersCampaignDale[" + item.ID+ "].NameDale").Replace("item_NameDale", "NameDale-" + item.ID))
#Html.ValidationMessageFor(modelItem => item.NameDale, "", new { #class = "text-danger" })

Passing values of checkboxes from View to Controller

I have a view with a number of checkboxes in it. I want to be able to pass the values of the checkboxes to the controller, then output a list of the OfficeNames that have been ticked. I am not sure how to pass the values of multiple checkboxes back to the controller, or how to output the OfficeNames based on which boxes have been ticked
View:
<p>
#using (Html.BeginForm())
{
<p>
Start Date: #Html.TextBox("StartDate") <br />
<br />
End Date: #Html.TextBox("EndDate") <br />
<br />
<input type="submit" value="Filter" />
</p>
}
<p>
#foreach (var item in Model.BettingOffices)
{
<label>#Html.DisplayFor(modelItem => item.OfficeName)</label>
<input type="checkbox" name="selectedShops" value="#item.OfficeName">
}
</p>
Controller:
public class DailyReportController : Controller
{
private RiskEntities _db = new RiskEntities();
// GET: /DailyReport/
public ActionResult Index(DateTime? startDate, DateTime? endDate)
{
if (startDate == null || endDate == null)
{
var dailyReportModelBlank = new DailyReportModel();
dailyReportModelBlank.BettingOffices = (from bo in _db.BettingOffices orderby bo.OfficeName select bo ).ToList();
//dailyReportModelBlank.DailyReports.Add(new DailyReport());
return View(dailyReportModelBlank);
}
var endDateToUse = (DateTime) endDate;
endDateToUse = endDateToUse.AddDays(+1);
var dailyReportModel = new DailyReportModel
{
DailyReports = (from dr in _db.DailyReports
where dr.DailyReportDate >= startDate
&& dr.DailyReportDate <= endDateToUse
select dr).ToList(),
BettingOffices = (from bo in _db.BettingOffices select bo).ToList()
};
return View(dailyReportModel);
}
Model:
public class DailyReportModel
{
private List<DailyReport> _dailyReports = new List<DailyReport>();
private List<BettingOffice> _bettingOffices = new List<BettingOffice>();
public List<DailyReport> DailyReports
{
get { return _dailyReports; }
set { _dailyReports = value; }
}
public List<BettingOffice> BettingOffices
{
get { return _bettingOffices; }
set { _bettingOffices = value; }
}
}
BettingOffice Class:
public partial class BettingOffice
{
public int BettingOfficeID { get; set; }
public string OfficeName { get; set; }
public string OfficeCode { get; set; }
public string IpAddress { get; set; }
public Nullable<bool> SupportOnly { get; set; }
public Nullable<int> SisSrNumer { get; set; }
public Nullable<bool> Local { get; set; }
public string Server { get; set; }
}
try this :
<p>
#using (Html.BeginForm())
{
<p>
Start Date: #Html.TextBox("StartDate")
<br />
<br />
End Date: #Html.TextBox("EndDate")
<br />
<br />
<input type="submit" value="Filter" />
</p>
}
</p>
<p>
#foreach (var item in Model.BettingOffices)
{
<label>#Html.DisplayFor(modelItem => item.OfficeName)</label>
<input type="checkbox" name="bettingOfficeIDs" value="#item.BettingOfficeID">
}
</p>
And in your Action you can get the selected office ids in bettingOfficeIDs variable:
public ActionResult YourActionName(int[] bettingOfficeIDs)
Few things that need to change here.
If you want values to be passed to action method they need to be within form not outside
For MVT to 'understand' checkbox values as array (or more complex object) you need to work with their html name attribute.
I will do demonstration application below that should help you understand how it works:
CsHtml: Notice that you need to add value attribute to checkboxes to be able to read their values, checkbox gets true only when checkbox is ticked and value is true, hence the javascript. You can add as many of complex object properties as hidden fields as long as you give them names that match to the object property names in viewModel. In this case I am only passing BettingOfficeID
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
$(document).on("click", "[type='checkbox']", function(e) {
if (this.checked) {
$(this).attr("value", "true");
} else {
$(this).attr("value","false");}
});
<p>
#using (Html.BeginForm())
{
<p>
Start Date: #Html.TextBox("StartDate") <br />
<br />
End Date: #Html.TextBox("EndDate") <br />
<br />
</p>
<p>
<input type="checkbox" name="BettingOffices[0].Selected" value="true">
<input type="hidden" name="BettingOffices[0].BettingOfficeID" value="1">
<input type="checkbox" name="BettingOffices[1].Selected" value="false">
<input type="hidden" name="BettingOffices[1].BettingOfficeID" value="2">
<input type="checkbox" name="BettingOffices[2].Selected" value="true">
<input type="hidden" name="BettingOffices[2].BettingOfficeID" value="3">
<input type="checkbox" name="BettingOffices[3].Selected" value="false">
<input type="hidden" name="BettingOffices[3].BettingOfficeID" value="4">
<input type="checkbox" name="BettingOffices[4].Selected" value="true">
<input type="hidden" name="BettingOffices[4].BettingOfficeID" value="5">
</p>
<input type="submit" value="submit"/>
}
Post Action method to add to controller
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(BettingViewModel viewModel)
{
return null;
}
BettingViewModel: I have added Selected property to BettingOffice class.
public class BettingViewModel
{
public string StartDate { get; set; }
public string EndDate { get; set; }
public List<BettingOffice> BettingOffices { get; set; }
}
public class BettingOffice
{
public bool Selected { get; set; }
public int BettingOfficeID { get; set; }
public string OfficeName { get; set; }
public string OfficeCode { get; set; }
public string IpAddress { get; set; }
public Nullable<bool> SupportOnly { get; set; }
public Nullable<int> SisSrNumer { get; set; }
public Nullable<bool> Local { get; set; }
public string Server { get; set; }
}
Hope this saves you some time.
View:
#using (Html.BeginForm("Createuser", "User", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Create a new account.</h4>
<div class="form-group">
#Html.LabelFor(m => m.city, new { #class = "col-md-2 control-label" })
</div>
<div class="col-md-10">
<table>
<tr>
<td><input type="checkbox" name="city" value="Pune" id="1" />Pune</td>
<td><input type="checkbox" name="city" value="Banglore" id="2" />Banglore</td>
<td><input type="checkbox" name="city" value="Mumbai" id="3" />Mumbai</td>
</tr>
</table>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Create" />
</div>
</div>
}
[HttpPost]
public ActionResult Createuser(user user, string [] city)
{
var UserInfo = new user
{ Email =user.Email,Password=user.Password,Firstname=user.Firstname };
return View();
}
1. First of all, you are generating checkboxes with same name. So how you will be able to retrieve them on server end separately?
So declare some counter that gets incremented and name checkboxes uniquely.
#foreach (var item in Model.BettingOffices)
{
int counter=1;
var checkboxName = "selectedShops" + counter;
<label>#Html.DisplayFor(modelItem => item.OfficeName)</label>
<input type="checkbox" name="#checkboxName" value="#item.OfficeName">
counter++;
}
2. Now on submission of Form in your controller, get checkboxes as -
//Loop through the request.forms
for (var i = 0; i <= Request.Form.Count; i++)
{
var checkboxValue = Request.Form["selectedShops[" + i + "]"];
// Do whatever you want to with this checkbox value
}
For ticked values, you will probably get True value. Debug the retrieved value to write further code accordingly.
Try the following
your View is:
#foreach (var item in Model.BettingOffices)
{
<label>#Html.DisplayFor(modelItem => item.OfficeName)</label>
<input type="checkbox" name="selectedShops" value="#item.OfficeName">
}
Controller
[HttpPost]
public ActionResult Index(FormCollection collection)
{
if(!string.IsNullOrEmpty(collection["selectedShops"]))
{
string strSelectedShops = collection["selectedShops"];
}
}
Hi you can get the selected checkbox value using the bellow code it seem working fine fore me,
<script>
$(document).ready(function()
{
$("input[type=checkbox]").click(function()
{
var categoryVals = [];
categoryVals.push('');
$('#Category_category :checked').each(function() {
categoryVals.push($(this).val());
});
$.ajax({
type:"POST",
url:"<?php echo $this->createUrl('ads/searchresult'); ?>", //url of the action page
data:{'category': categoryVals},
success : function(response){
//code to do somethng if its success
}
});
}
}
</script>

How to pass list from view to controller

I have problem when pass a list in an object from view to controller, the list is always null. How should I do this?
My model:
public class ImageModel
{
public int id { get; set; }
public string url { get; set; }
public bool delete { get; set; }
}
public class SchoolImages
{
public Nullable<int> schoolid { get; set; }
public IList<ImageModel> images;
}
The view:
#model SchoolAppManager.Models.SchoolImages
#using (Html.BeginForm("DeleteImages", "Images"))
{
#Html.HiddenFor(x => x.schoolid)
<table>
#for (int i = 0; i < Model.images.Count(); i += 4) {
<tr>
<td>
<img src="#Url.Content(Model.images[i].url)" width="50%" />
<br />
#Html.CheckBoxFor(x => x.images[i].delete)
#Html.HiddenFor(x => x.images[i].id)
</td>
<td>
#if (i + 1 < Model.images.Count())
{
<img src="#Url.Content(Model.images[i + 1].url)" width="50%" />
<br />
#Html.CheckBoxFor(x => x.images[i + 1].delete)
#Html.HiddenFor(x => x.images[i + 1].id)
}
</td>
<td>
#if (i + 2 < Model.images.Count())
{
<img src="#Url.Content(Model.images[i + 2].url)" width="50%" />
<br />
#Html.CheckBoxFor(x => x.images[i + 2].delete)
#Html.HiddenFor(x => x.images[i + 2].id)
}
</td>
<td>
#if (i + 3 < Model.images.Count())
{
<img src="#Url.Content(Model.images[i + 3].url)" width="50%" />
<br />
#Html.CheckBoxFor(x => x.images[i + 3].delete)
#Html.HiddenFor(x => x.images[i + 3].id)
}
</td>
</tr>
}
</table>
<input type="submit" value="delete" />
}
When I click delete, SchoolImages is passed to the controller, SchoolImages.schoolId has value in the controller, however, SchoolImages.images is null. How can I pass SchoolImages.images to controller?
Default model binder doesn't work with fields, so make images a property:
public IList<ImageModel> images { get; set; }
I guess you will have problem binding complex types to your model. In case you don't get that working. Here is the alternative way.
You have a bunch of checkboxes, possibly with the same name and different values. You could post them to a method that takes a FormCollection, ie.
public ActionResult Test(FormCollection collection)
{
string results = collection["Blanks"];
}
This would give you a comma-delimited list of values (or null, where no checkboxes are ticked).
Alternatively, if you have the possible values as an array on the server then you could give the checkboxes names according to their array values, which would mean you could do something like this:
#using (Html.BeginForm("Test","Home"))
{
#Html.CheckBox("Blanks[0]", false);
#Html.CheckBox("Blanks[1]", false);
#Html.CheckBox("Blanks[2]", false);
<input type="submit" value="Submit" />
}
giving you an array of booleans in your Test method:
public ActionResult Test(bool[] Blanks)
{ }
Try changing your model "SchoolImages"; use array instead of IList<>.
It's null because the default model binder doesn't know how to initialize it.
Change your field images to be a property and add a constructor like this:
public class SchoolImages
{
public SchoolImages()
{
images = new List<ImageModel>();
}
public Nullable<int> schoolid { get; set; }
public IList<ImageModel> images { get; set; }
}
It should work.

Why does the selected radio button at runtime differ from my programmatic setup?

I have data model classes as follows:
public class QuizItem
{
public int QuizItemId { get; set; }
public string Question { get; set; }
public IEnumerable<Choice> Choices { get; set; }
}
and
public class Choice
{
public int ChoiceId { get; set; }
public string Description { get; set; }
public bool IsCorrect { get; set; }
}
I made a setup in a controller action as follows:
public class HomeController : Controller
{
public ActionResult Index()
{
IEnumerable<Choice> choices = new Choice[]
{
new Choice{ChoiceId=1,Description="Black",IsCorrect=true},
new Choice{ChoiceId=2,Description="Red",IsCorrect=false},
new Choice{ChoiceId=3,Description="Yellow",IsCorrect=false}
};
QuizItem qi = new QuizItem { QuizItemId = 1,
Question = "What color is your hair?",
Choices = choices };
return View(qi);
}
The last, here is my view:
#model MvcApplication1.Models.QuizItem
#{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>QuizItem</legend>
<div class="display-label">
Question</div>
<div class="display-field">#Model.Question</div>
#foreach (var x in Model.Choices)
{
<text>#Html.RadioButtonFor(y => Model.QuizItemId, x.Description, new { #checked = x.IsCorrect })
#x.Description<br /></text>
}
</fieldset>
At runtime, the selected option should be Black. But Yellow gets selected. How to resolve this issue?
You need to set the #checked attribute to the string "checked", not true/false.
new { #checked = x.IsCorrect ? "checked" : string.Empty }
JK is right at the same time he is wrong.
The checked attribute should really be used with the "checked" value, the correct and valid W3C html markup for a checked radio is:
<input type="radion" name="something" value="1" checked="checked">
But, when you output this:
<input type="radion" name="something" value="1" checked="">
The browser still renders it as a checked radio. Your own solution is the best so far.
I find that the radio button helper is too much trouble to get to work correctly.
You are best off with writing the raw HTML for the the radio buttons yourself. This is especially true when you have multiple options:
<input type="radio" id="ques1_choice1" name="quizQuestion1" value="1" #(x.IsCorrect ? "checked=\"checked\"" : null) />
<input type="radio" id="ques1_choice2" name="quizQuestion1" value="2" #(x.IsCorrect ? "checked=\"checked\"" : null) />
I found the solution as follows:
<fieldset>
<legend>#Model.Question</legend>
#foreach (var x in Model.Choices)
{
<text>#Html.RadioButton(Model.QuizItemId.ToString(), x.Description, x.IsCorrect)
#x.Description<br /></text>
}
</fieldset>

Resources