I have below Model structure
public class Quiz
{
public List<Question> Questions { get; set; }
}
public class Question
{
public int Id { get; set; }
public String QuestionText { get; set; }
public List<Option> Options { get; set; }
public int AnswerId { get; set; }
}
public class Option
{
public int Id { get; set; }
public String OptionText { get; set; }
public int DisplayOrder { get; set; }
}
And my view is like below where I am displaying all the questions and options
foreach (var question in Model.Questions)
{
#Html.DisplayFor(modelItem => question.QuestionText) <br />
foreach (var option in question.Options)
{
#Html.RadioButton("Id", option.Id)#option.OptionText
}
}
<input type="submit" value="Submit" />
I am not getting the values for all selected radiobuttons, It always returns one value in form collection
[HttpPost]
public ActionResult Quiz(Quiz userResponse, FormCollection form)
{
foreach (var item in form.AllKeys)
{
string value = form[item];
// save data
}
//var selectedOption = frm["Id"];
return View();
}
Can you please help?
You creating radio buttons that have no relationship to you model and all have the same name="Id" attribute (and you would only ever be able to select one option from all the questions). You need to use a for loop or EditorTemplate for typeof Question (refer this answer for a more detailed explanation). Using an EditorTemplate, your code will be
/Views/Shared/EditorTemplates/Question.cshtml
#model Question
#Html.HiddenFor(m => m.Id)
#Html.DisplayFor(m => m.QuestionText)
#foreach(var option in Model.Options)
{
<div>
<label>
#Html.RadioButtonFor(m => m.AnswerId , option,Id, new { id = "" })
<span>#option.OptionText</span>
</label>
</div>
}
and in the main view
#model Quiz
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.Questions)
<input type="submit" value="Save" />
}
and the controller method
[HttpPost]
public ActionResult Quiz(Quiz model)
The model in the POST method will be bound with each question containing its Id and AnswerId (the Id of the selected Option) properties.
Side note: Your current POST method has return View() but this would fail because the value of each Options property will be null. If you need to return the view (which should only be necessary if ModelState is invalid), then you need to repopulate those collections.
Related
How do you properly bind a Dictionary and it's values per key to checkboxes?
I can display them in the HTTPGET but binding the selected values again to HTTPPOST doesn't seem to work.
viewmodel
public class EditViewModel
{
public Foo Foo { get; set; }
public Dictionary<Bar, List<BarVersionEditVM>> Matrix { get; set; }
}
public class BarVersionEditVM
{
public int ID { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public bool IsSupported { get; set; }
}
view:
<form asp-action="Edit">
<div class="row">
#foreach (var kvp in Model.Matrix.OrderByDescending(x => x.Key.Name))
{
<div class="col-md-2 col-lg-2">
<fieldset>
<legend>#kvp.Key.Name</legend>
#foreach (var version in kvp.Value)
{
<div>
<input type="checkbox" id="#version.ID" value="#version.IsSupported" name="#version.Name" #(version.IsSupported ? "checked=\"checked\"" : "") />
<label>#version.Version:</label>
</div>
}
</fieldset>
</div>
}
</div>
<input type="hidden" asp-for="#Model.Foo.ID" />
<input type="submit" value="Save" class="btn btn-default" />
</form>
In the View I tried also to rewrite with foreach and using Html helpers, but without success:
#Html.CheckBoxFor(model => model.Matrix[kvpair.Key][i].IsSupported)
controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(EditViewModel vm) {
// vm is there but Matrix are null.
// and only the ID of Foo property is filled in.
}
any suggestions?
Unless your Dictionary has simple value types for both the Key and Value (e.g. public Dictionary<string, string>), the DefaultModelBinder requires that the form control name attributes be in the format
<input .... name="Matrix[0].Key" value="..." />
<input .... name="Matrix[0].Value[0].ID" value="..." />
<input .... name="Matrix[0].Value[0].Name" value="..." />
There are no HtmlHelper methods that will generate the correct html to allow binding to your Dictionary.
It is far simpler to create simple view model(s) to with IList<T> properties for the collections. Based on the view you have shown, those models would be
public class EditVM
{
public int FooID { get; set; }
public List<BarVM> Bars { get; set; }
}
public class BarVM
{
public string Name { get; set; }
public List<BarVersionVM> Versions { get; set; }
}
public class BarVersionVM
{
public int ID { get; set; }
public string Name { get; set; } // not clear where you use this property
public string Version { get; set; }
public bool IsSupported { get; set; }
}
and your view would then be
#model EditVM
....
#Html.HiddenFor(m => m.FooID)
#for(int i = 0; i < Model.Bars.Count; i++)
{
<fieldset>
<legend>#Model.Bars[i].Name</legend>
#Html.HiddenFor(m => m.Bars[i].Name) // in case you need to return the view in the POST method
#for(int j = 0; j < Model.Bars[i].Versions.Count; j++)
{
<div>
#Html.HiddenFor(m => m.Bars[i].Versions[j].ID)
#Html.CheckBoxFor(m => m.Bars[i].Versions[j].IsSupported)
#Html.LabelFor((m => m.Bars[i].Versions[j].IsSupported, Model.Bars[i].Versions[j].Version)
</div>
}
</fieldset>
}
<input type="submit" value="Save" />
Long time lurker... first time question asker...
I have a complex form which returns null when being submitted. Essentially I am trying to build a database driven forms.
The form contains a list of either sections or questions
A section contains a list of either another section, or questions
Model 1:
public FormViewModel {
public List<FormSetsViewModel> formSets { get; set; }
}
Model 2:
public FormSetsViewModel{
QAViewModel questionAnswerViewModel { get; set; }
SectionViewModel sectionViewModel { get; set; }
bool isQuestion { get; set; }
bool isSection { get; set; }
}
Model 3:
public SectionViewModel {
public List<FormSectionQuestionsViewModel> formSectionQuestions { get; set; }
}
Model 4:
public FormSectionQuestionsViewModel {
public QuestionAnswerViewModel questionAnswers;
public SectionViewModel childSection;
int orderNumber;
}
Model 5:
public QAViewModel {
int id { get; set; }
string answer { get; set; }
string question { get; set;}
}
The views are as follows:
FormViewModel.cshtml
#model FormViewModel
#using (Html.BeginForm("Save", "Forms"))
{
<div class="row">
#Html.EditorFor(model => model.formSetsViewModels)
</div>
<div class="controls">
<input type="submit" value="Confirm" class="button" name="save" />
</div>
}
#model FormSetsViewModel
<div class="control-group">
#if (Model.isQuestion)
{
#Html.EditorFor(m => m.questionViewModel);
}
else
{
#Html.EditorFor(m => m.sectionViewModel);
}
</div>
SectionViewModel.cshtml
#model SectionViewModel
#Html.EditorFor(m => m.formSectionQuestions)
FormSectionQuestionsViewModel.cshtml
#model FormSectionQuestionsViewModel
#if (Model.childSection != null)
{
#Html.EditorFor(m => m.childSection)
}
else
{
#Html.EditorFor(m => m.questionAnswers)
}
QAViewModel.cshtml
#model QAViewModel
<p><div class="question-text-edit">#Html.Raw(Model.questionText)</div>
#Html.TextAreaFor(m => m.answer, new { style = "width: 90%; height: 80px;" })
The controller:
[HttpPost]
public ActionResult Save(int caseID, List<FormSetsViewModel> formSets = null)
{
return Index(caseID);
}
The view works great as a database driven form. However, when I submit the form, it seems that the formsets cannot bind, and returns null.
From Html, it created an input like this:
<input id="formSetsViewModels_d762713a-7a2f-497a-9417-4c6e91d33cb8__sectionViewModel_formSectionQuestions_48e738da-10d3-4518-be59-2493e2b7a7cc__questionAnswers_answer" name="formSetsViewModels[d762713a-7a2f-497a-9417-4c6e91d33cb8].sectionViewModel.formSectionQuestions[48e738da-10d3-4518-be59-2493e2b7a7cc].questionAnswers.answer" type="text" value="">
Finally found the answer!
The variable name for the FormSetsViewModel in the
public ActionResult Save(int caseID, List<FormSetsViewModel> formSets = null)
needs to be formSetsViewModel for the model to be able to be binded.
The other thing is that, some public variables in the class does not have { get; set; } method.
All variables that we want to be bind needs the { get; set; } method. Adding this solve the issue.
Edit: Removed Partial View to make things simpler. Now I just need to find out why The View isn't Posting the Values
ViewModelProspectUsers
public class ViewModelProspectUsers
{
public int Id { get; set; }
public string User { get; set; }
public IEnumerable<ViewModelProspectSelect> Prospects { get; set; }
}
ViewModelProspectSelect
public class ViewModelProspectSelect
{
public int ProspectID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
View
#model OG.ModelView.ViewModelProspectUsers
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.Id)
<h5>Please Select Prospects you wish to assign to this User.</h5>
-----HERE is where the partial used to be, these values aren't being posted to the [Post] Method------
-----------------------------------------However they are populating just fine----------------------------------------
#foreach (var item in Model.Prospects)
{
#Html.HiddenFor(x => item.ProspectID)
#Html.DisplayFor(x => item.Name)
#Html.EditorFor(x => item.IsSelected)
}
#*#Html.Partial("_ShowProspectCheckedForUser", Model.Prospects)*#
#*#Html.Partial("_ShowProspectCheckedForuser", new OG.ModelView.ViewModelProspectSelect())*#
<input type="submit" value="Save changes" />
#Html.ActionLink("Cancel", "Index")
}
Post
[HttpPost]
public ActionResult UsersInProspect(ViewModelProspectUsers viewModel)
If i were to look at viewModel.Prospects(m=>m.isSelected) //<- this value is Null shouldn't be
My viewmodel Variableis showing Data but not for the Ienumerable.
When dealing with list-type objects, you must reference them with array notation to have the field names generated in a way that the modelbinder can parse them back, i.e:
for (var i = 0; i < Model.Count(); i++)
{
#Html.LabelFor(m => Model[i].SomeProperty)
#Html.EditorFor(m => Model[i].SomeProperty)
}
In your scenario, you'd be better served by using a view model to contain your list and adding a Selected property to the items so that you can track which ones were or were not selected.
public class ViewModelProspects
{
public List<ViewModelProspectSelect> Prospects { get; set; }
}
public class ViewModelProspectSelect
{
// Whatever else you have
public bool Selected { get; set; }
}
Then, in your view:
#model ViewModelProspects
#using (Html.BeginForm())
{
for (var i = 0; i < Model.Prospects.Count(); i++)
{
<label>
#Html.HiddenFor(m => Model.Prospects[i].Id)
#Html.CheckboxFor(m => Model.Prospects[i].Selected, true)
#Model.Prospects[i].Name
</label>
}
}
And finally, change your action method signature:
[HttpPost]
public ActionResult UsersInProspect(ViewModelProspects model)
Then, you can easily get the list of selected ids inside the action with:
var selectedIds = model.Prospects.Where(m => m.Selected).Select(m => m.Id)
I am having trouble getting the value of the selected item from a drop down list.
Apologies for how much code I've posted, but it is very simple code to read.
When I edit a vehicle my Get Edit method returns the vehicle and the dropdown list has the correct VehicleType selected in the view.
BUT
When my [HttpPost] Edit gets the model back, the the VehicleType is null, the other fields, name, description are correctly updated.
I have a class called Vehicle and one called VehicleType
public class Vehicle
{
public string Name { get; set; }
public string Description { get; set;
public virtual VehicleType VehicleType { get; set; }
}
public class VehicleType
{
public int VehicleTypeID { get; set; }
public string VehicleTypeDescription { get; set; }
}
in my controller I have an get and set edit methods
public ActionResult Edit(int id = 0)
{
Vehicle myVehicle = db.Vehicle.Find(id);
if (myVehicle == null)
{
return HttpNotFound();
}
PopulateVehicleTypeList(myVehicle.VehicleType.VehicleTypeID);
return View(myVehicle);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Vehicle myVehicle)
{
if (ModelState.IsValid)
{
db.Entry(myVehicle).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myVehicle);
}
And my view
//View
#model Vehicle
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Vehicle</legend>
#Html.HiddenFor(model => model.VehicleID)
<div class="editor-label">
#Html.LabelFor(model => model.VehicleType.VehicleTypeDescription)
</div>
<div class="editor-field">
#Html.DropDownList("VehicleTypeID", String.Empty)
#Html.ValidationMessageFor(model => model.VehicleType.VehicleTypeID)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
There's a few things you should do.
Firstly, create a view Model which has Vehicles and VehicleTypes
public class VehicleViewModel
{
public IEnumerable<SelectListItem> VehicleTypes { get; set; }
public Vehicle vehicle { get; set; }
}
Secondly, in your view , change to DropDownListFor:
#Html.DropDownListFor(
x => x.Vehicle.VehicleType,
Model.VehicleTypes,
"-- Select a vehicle type --", null)
Your controller will look something like this:
VehicleViewModel viewModel = new VehicleViewModel
{
Vehicle vehicle = <populate via Linq query>
VehicleTypes = VehicleTypes.Select(x => new SelectListItem
{
Value = x.VehicleTypeID,
Text = VehicleTypeDescription
}).ToList(),
vehicle = vehicle
};
Hope that helps.
I have a problem while passing an object with HttpPost...
Once the form is submitted, the model is set "null" on the controller side, and I don't know where is the issue..
Here is my controller :
public ActionResult AddUser(int id = 0)
{
Group group = db.Groups.Find(id);
List<User> finalList = db.Users.ToList() ;
return View(new AddUserTemplate()
{
group = group,
users = finalList
});
//Everything is fine here, the object is greatly submitted to the view
}
[HttpPost]
public ActionResult AddUser(AddUserTemplate addusertemplate)
{
//Everytime we get in, "addusertemplate" is NULL
if (ModelState.IsValid)
{
//the model is null
}
return View(addusertemplate);
}
Here is AddUserTemplate.cs :
public class AddUserTemplate
{
public Group group { get; set; }
public User selectedUser { get; set; }
public ICollection<User> users { get; set; }
}
Here is the form which return a null value to the controller (note that the dropdown list is greatly populated with the good values) :
#using (Html.BeginForm()) {
<fieldset>
<legend>Add an user</legend>
#Html.HiddenFor(model => model.group)
#Html.HiddenFor(model => model.users)
<div class="editor-field">
//Here, we select an user from Model.users list
#Html.DropDownListFor(model => model.selectedUser, new SelectList(Model.users))
</div>
<p>
<input type="submit" value="Add" />
</p>
</fieldset>
}
Thanks a lot for your help
I tried your code and in my case the addusertemplate model was not null, but its properties were all null.
That's because of a few model binding issues: Html.HiddenFor and Html.DropDownListFor do not work with complex types (such as Group or User) (at least that's how it is by default).
Also, Html.HiddenFor cannot handle collections.
Here's how to solve these issues:
instead of #Html.HiddenFor(model => model.group) there should be one #Html.HiddenFor for each property of the group that you need bound
instead of #Html.HiddenFor(model => model.users) you need to iterate through the list of users and for each object add #Html.HiddenFor for each property of the user that you need bound
instead of #Html.DropDownListFor(model => model.selectedUser [...], create a property like int SelectedUserId {get;set;} and use that in the DropDownList (as it cannot handle complex types).
Here's the code that works:
1. The User and Group classes, as I imagined them to be:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
}
2. The adjusted AddUserTemplate class:
public class AddUserTemplate
{
public Group Group { get; set; }
public IList<User> Users { get; set; }
public int SelectedUserId { get; set; }
public User SelectedUser
{
get { return Users.Single(u => u.Id == SelectedUserId); }
}
}
The adjustments:
Users was changed from ICollection to IList, because we'll need to access elements by their indexes (see the view code)
added SelectedUserId property, that will be used in the DropDownList
the SelectedUser is not a readonly property, that returns the currently selected User.
3. The adjusted code for the view:
#using (Html.BeginForm())
{
<fieldset>
<legend>Add an user</legend>
#*Hidden elements for the group object*#
#Html.HiddenFor(model => model.Group.Id)
#Html.HiddenFor(model => model.Group.Name)
#*Hidden elements for each user object in the users IList*#
#for (var i = 0; i < Model.Users.Count; i++)
{
#Html.HiddenFor(m => m.Users[i].Id)
#Html.HiddenFor(m => m.Users[i].Name)
}
<div class="editor-field">
#*Here, we select an user from Model.users list*#
#Html.DropDownListFor(model => model.SelectedUserId, new SelectList(Model.Users, "Id", "Name"))
</div>
<p>
<input type="submit" value="Add" />
</p>
</fieldset>
}
Another option that does not require a bunch of hidden fields is to simply specify that you want the model passed to the controller. I think this is much cleaner.
#using(Html. BeginForm("action","controller", Model, FormMethod.Post)){
...
}