Portions of my models are not being correctly reconstructed on postback.
Models
public class DemographicsModel
{
public List<QuestionModel> Questions { get; set; }
}
public abstract class QuestionModel
{
[HiddenInput(DisplayValue = false)]
public int ID { get; set; }
[HiddenInput(DisplayValue = false)]
public string Title { get; set; }
}
public abstract class ChooseQuestionModel : QuestionModel
{
public abstract List<SelectListItem> Items { get; set; }
}
public class ChooseManyQuestionModel : ChooseQuestionModel
{
[Required]
[DataType("CheckBoxList")]
public override List<SelectListItem> Items { get; set; }
}
Views
ChooseManyQuestionModel.cshtml
#model X.Y.Z.ChooseManyQuestionModel
<div class="Form Wide NoLabel">
<div class="Title">#this.Model.Title</div>
#Html.TypeStamp()
#Html.EditorFor(m => m.ID)
#Html.EditorFor(m => m.Title)
#Html.EditorFor(m => m.Items)
</div>
CheckBoxList.cshtml
#model IEnumerable<SelectListItem>
#if (!this.Model.IsNullOrEmpty())
{
foreach (var item in this.Model)
{
<div>
#Html.HiddenFor(m => item.Value)
#Html.HiddenFor(m => item.Text)
#Html.CheckBoxFor(m => item.Selected)
#Html.LabelFor(m => item.Selected, item.Text)
</div>
}
}
I believe the issue lies within CheckBoxList.cshtml since these items are not being re-constituted on postback.
HTML Output
<div class="Form Wide NoLabel">
<div class="Title">Question title displays here?</div>
<input id="Questions_1___xTypeStampx_" name="Questions[1]._xTypeStampx_" type="hidden" value="Hrxh2HjDRorBAZWo18hsC0OvbJwyswpDkfTBfNF2NC8=" />
<input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="Questions_1__ID" name="Questions[1].ID" type="hidden" value="76" />
<input id="Questions_1__Title" name="Questions[1].Title" type="hidden" value="Question title displays here?" />
<div>
<input id="Questions_1__Items_item_Value" name="Questions[1].Items.item.Value" type="hidden" value="148" />
<input id="Questions_1__Items_item_Text" name="Questions[1].Items.item.Text" type="hidden" value="Organization Type 1" />
<input data-val="true" data-val-required="The Selected field is required." id="Questions_1__Items_item_Selected" name="Questions[1].Items.item.Selected" type="checkbox" value="true" /><input name="Questions[1].Items.item.Selected" type="hidden" value="false" />
<label for="Questions_1__Items_item_Selected">Organization Type 1</label>
</div>
</div>
</div>
Controller
public class AccountController : BaseController
{
public ActionResult Demographics()
{
return this.View(new DemographicsModel());
}
[HttpPost]
public ActionResult Demographics(DemographicsModel model)
{
return this.View(model);
}
}
On postback, the DemographicsModel is populated with the correct types (I'm using MvcContrib to handle abstract type binding). The List<Question> is populated with all of the correct data including the ID and Title of each question from the hidden fields. However, List<SelectListItem> within each question is set to null.
Update 1
The issue is definitely occurring because the fields are not named correctly. For instance, the "item" field names are being generated like this:
Questions_1__Items_item_Value
When they should really look like this (addition of item index and removal of erroneous "item"):
Questions_1__Items_1__Value
Similarly, the field IDs are being generated like this (addition of item index and removal of erroneous "item"):
Questions[1].Items.item.Value
Instead of:
Questions[1].Items[0].Value
Using Fiddler with the correct IDs being posted back, the model is constructed correctly with all radio buttons and checkboxes in place.
Try the following.
In ChooseManyQuestionModel.cshtml, change #Html.EditorFor(m => m.Items) to:
#Html.EditorForModel(m => m.Items)
Then, in CheckBoxList.cshtml, change #model IEnumerable<SelectListItem> to:
#model SelectListItem
Finally, in each item, modify each lambda expression, and change item to m, then remove the foreeach loop. This will allow the Editor to iterate through the collection, and should give you correct id generation for each element.
When foreach loop is used the ids generated in HTML are all same.
When for look is used the ids generated with the for loops index so binding is happening correctly and all the data is available after post back.
In this scenario, it seems the Helper class is not doing what you want it to do. I would suggest writing your own helper class to name your inputs exactly as you require them to be.
Related
I'm implementing asp.net core 3.1. I have three radio buttons in my razor view and with the following code, I want to send the selected radio button value to Index action in controller in order to show its related data. My problem is, I can't set one of those radio buttons to be checked by default.
#model CSD.ChartObjects
<form method="post">
#foreach (var year in Model.Years)
{
<input type="radio" asp-for="Year" value="#year" />#year<br />
}
<input type="submit" asp-action="Index" />
</form>
Here is my model object that is read in razor
public class ChartObjects
{
public List<ChartModel> Percent { get; set; }
public List<ChartModel> Time { get; set; }
public List<ChartModel> Avg { get; set; }
public List<ChartModel> Total { get; set; }
public string Year { get; set; }
public string[] Years = new[] { "1398", "1399", "1400" };
}
And here is the body of my HomeController:
[HttpGet]
public IActionResult Index()
{
return (BuildIndexModel("1399"));
}
[HttpPost]
public IActionResult Index([FromForm] string currentYear)
{
return (BuildIndexModel(currentYear));
}
public IActionResult BuildIndexModel(string currentYear)
{
...
}
I think this will work:
#foreach (var year in Model.Years)
{
var fi = (year == Model.Years[0]) ? true : false ;
<input type="radio" asp-for="Year" value="#year" checked="#fi" />#year<br />
}
My problem is, I can't set one of those radio buttons to be checked by default.
To set a default checked radio button, you can try following code snippet.
<form method="post">
#foreach (var year in Model.Years)
{
<input type="radio" asp-for="Year" value="#year" checked="#(year == Model.Years.FirstOrDefault() ? "checked" : null)"/>#year<br />
}
<input type="submit" asp-action="Index" />
</form>
Update:
my data by default is for 1399
You can pass default year through ViewData, like below.
In controller action
ViewData["defaultyear"] = "1399";
In view page
<input type="radio" asp-for="Year" value="#year" checked="#(year == ViewData["defaultyear"].ToString() ? "checked" : null)"/>#year<br />
I don't know how to use asp.net, but In JS, I just simply have to access the attributes of the HTML Input tag so you can then assign the attribute "checked" to true.
I guess is something like this:
HtmlElement Input1 = webBrowser1.Document.GetElementById("ID"); // consider adding an ID
Input1.Attributes.Add("checked", "true");
Check this two links:
How to: Set HTML Attributes for Controls in ASP.NET Web Pages
HtmlDocument.GetElementById(String) Method
Basically, i have a form with a textbox, radio button and a check box control. now i face problem with the checkbox control when i submit my page
I have a model like this
public class PersonDetails
{
public int personID { get; set; }
public string PersonName { get; set; }
public string Gender { get; set; }
public List<Education> Education { get; set; }
public string EmailID { get; set; }
public string Address { get; set; }
}
public class Education
{
public string Qualification { get; set; }
public bool Checked { get; set; }
public List<Education> GetQualification()
{
return new List<Education>{
new Education {Qualification="SSC",Checked=false},
new Education {Qualification="HSC",Checked=false},
new Education {Qualification="Graduation",Checked=false},
new Education {Qualification="PostGraduation",Checked=false}
};
}
}
and i have a view like this
#using (Html.BeginForm("GetDetails", "User", FormMethod.Post, new { id = "person-form" }))
{
<div class="col-xs-12">
<label>Person Name</label>
#Html.TextBoxFor(x => x.PersonName)
</div>
<div class="col-xs-12">
<label>Gender</label>
#Html.RadioButtonFor(x => x.Gender, "Male")
#Html.RadioButtonFor(x => x.Gender, "Female")
</div>
<div class="col-xs-12">
<label>Education</label>
#{
Html.RenderPartial("Qualification", new LearnAuthentication.Controllers.Education().GetQualification());
}
</div>
<div class="col-xs-12">
<input type="submit" value="Submit" />
</div>
}
and the partial view like this
#model List<LearnAuthentication.Controllers.Education>
<br />
#for (int i = 0; i < Model.Count(); i++)
{
#Html.HiddenFor(x => Model[i].Qualification)
#Html.CheckBoxFor(x => Model[i].Checked)
#Html.DisplayFor(x => Model[i].Qualification)
<br />
}
and my action method is this
[HttpPost]
public ActionResult GetDetails(PersonDetails personDetails)
{
return View();
}
now when i run my app i tend to get all the information but when i submit the page i get this property with null values
public List Education { get; set; }
can any of you guys help me on what i am doing wrong or could you direct me to the right path on how to achieve this.
Your use of a partial to generate the controls for Education is generating inputs such as
<input type="hidden" name="[0].Qualification" ... />
<input type="hidden" name="[1].Qualification" ... />
but in order to bind, they need to have name attributes which match your model
<input type="hidden" name="Education[0].Qualification" ... />
<input type="hidden" name="Education[1].Qualification" ... />
Rename you partial to Education.cshtml (to match the name of the class) and move it to your /Views/Shared/EditorTemplates folder (or /Views/yourControllerName/EditorTemplates if you want a specific template just for that controller)
Then change the partial to
#model LearnAuthentication.Controllers.Education
#Html.HiddenFor(m => m.Qualification)
#Html.LabelFor(m => m.Checked)
#Html.CheckBoxFor(m => m.Checked)
#Html.DisplayFor(m => m.Qualification)
and in the main view replace
<label>Education</label>
#{ Html.RenderPartial("Qualification", new LearnAuthentication.Controllers.Education().GetQualification()); }
with
<span>Education</span> // its not a label
#Html.EditorFor(m => m.Education)
which will correctly generate the correct html for each item in your collection
Side note: Other alternatives which would work would be to change the POST method signature to
[HttpPost]
public ActionResult GetDetails(PersonDetails personDetails List<Education> educationDetails)
or to pass the HtmlFieldPrefix to the partial as explained in getting the values from a nested complex object that is passed to a partial view
I have a form with 2 fields a dropdownlist and a checkbox. I have everything working correctly but i can not for some reason obtain the value of a checkbox if it is checked this is my code..
[HttpPost]
public ActionResult view(string pick)
{
switch (pick)
{
case "Deny":
// capture checkbox value here
break;
case "Accept":
// capture checkbox value here
break;
}
return View();
}
This is my view
#using (Html.BeginForm("view", "grouprequest", FormMethod.Post, new {}))
{
#Html.DropDownList("pick", new SelectList(new List<Object>{
new{ Text ="Accept", Value= "Accept"},new{ Text ="Deny", Value= "Deny"}}, "Value", "Text"), new {})
<input type="submit" name="work" id="work" value="Update" style="font-size:16px" />
foreach (var item in Model)
{
<input type="checkbox" id="#item.grouprequestID" name="#item.grouprequestID" value="#item.grouprequestID" />
}
}
Basically the dropdownlist has 2 options which are Accept and Deny I can capture which one the user chooses via the SWITCH-case in the controller now how can I capture the value of the checkboxes? If you notice the Checkboxes have a variable to them named #groupRequestID so every checkbox has a different unique value like 1,2,3 etc.. any help would be greatly appreciated !!
The Model
public class grouprequest
{
[Key]
public int grouprequestID { get; set; }
public int? profileID { get; set; }
public int? registrationID { get; set; }
public DateTime expires { get; set; }
public int? Grouplink { get; set; }
}
Check boxes when posted to the server act a little strange.
If a box is checked the browser will send name=value as in
<input type="checkbox" name="name" value="value" />
But if the checkbox is not checked the server doesn't send anything.
<input type="checkbox" name="Check1" id="Checks1" value="Hello" checked="checked"/>
<input type="checkbox" name="Check1" id="Checks1" value="Hello1" />
<input type="checkbox" name="Check1" id="Checks1" value="Hello2" />
Will result in Check1 = Hello
What this means is if all your check boxes are related, naming them the same will populate the same attribute of your ActionMethod. If that attribute is an enumeration it will contain only the ones that are checked.
If you have this in your view:
<input type="checkbox" name="MyValues" value="1" checked="checked"/>
<input type="checkbox" name="MyValues" value="2" />
<input type="checkbox" name="MyValues" value="3" />
and this as your controller action method:
public ActionMethod MyAction(IEumerable<int> myValues)
The myValues variable will look like this:
myValues[0] == 1
You should also note that if you are using the Html helper extension:
#Html.CheckBoxFor(m => m.MyValue)
Where MyValue is a bool the extension will create a checkbox input tag and also a hidden input tag with the same name, meaning a value will always be passed into the controller method.
Hope this helps.
I have a basic ViewModel with a property that is a List of complex types. When binding, I seem to be stuck with getting either the list of values, OR the other model properties depending on the posted values (i.e. the view arrangement).
The view model:
public class MyViewModel
{
public int Id { get; set; }
public string Property1 { get; set; }
public string Property2 { get; set; }
public List<MyDataItem> Data { get; set; }
}
public class MyDataItem
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
The controller actions:
public ActionResult MyForm()
{
MyViewModel model = new MyViewModel();
model.Id = 1;
model.Data = new List<MyDataItem>()
{
new MyDataItem{ Id = 1, ParentId = 1, Name = "MyListItem1", Value = "SomeValue"}
};
return View(model);
}
[HttpPost]
public ActionResult MyForm(MyViewModel model)
{
//...
return View(model);
}
Here is the basic view (without the list mark-up)
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>My View Model</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.Property1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Property1)
#Html.ValidationMessageFor(model => model.Property1)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Property2)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Property2)
#Html.ValidationMessageFor(model => model.Property2)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
When posted back to the controller, I get the 2 property values and a null value for the 'Data' property as expected.
If I add the mark-up for the List as follows (based on the information in this Scott Hanselman post and Phil Haack's post):
<div class="editor-field">
#for (int i = 0; i < Model.Data.Count(); i++)
{
MyDataItem data = Model.Data[i];
#Html.Hidden("model.Data[" + i + "].Id", data.Id)
#Html.Hidden("model.Data[" + i + "].ParentId", data.ParentId)
#Html.Hidden("model.Data[" + i + "].Name", data.Name)
#Html.TextBox("model.Data[" + i + "].Value", data.Value)
}
</div>
The 'Data' property of the model is successfully bound but the other properties are null.
The form values posted are as follows:
Id=1&Property1=test1&Property2=test2&model.Data%5B0%5D.Id=1&model.Data%5B0%5D.ParentId=1&model.Data%5B0%5D.Name=MyListItem1&model.Data%5B0%5D.Value=SomeValue
Is there a way to get both sets of properties populated or am I just missing something obvious?
EDIT:
For those of you who are curious. Based on the answer from MartinHN, the original generated mark-up was:
<div class="editor-field">
<input id="model_Data_0__Id" name="model.Data[0].Id" type="hidden" value="1" />
<input id="model_Data_0__ParentId" name="model.Data[0].ParentId" type="hidden" value="1" />
<input id="model_Data_0__Name" name="model.Data[0].Name" type="hidden" value="MyListItem1" />
<input id="model_Data_0__Value" name="model.Data[0].Value" type="text" value="SomeValue" />
</div>
The new generated mark-up is:
<div class="editor-field">
<input id="Data_0__Id" data-val="true" name="Data[0].Id" type="hidden" value="1" data-val-number="The field Id must be a number." data-val-required="The Id field is required." />
<input id="Data_0__ParentId" name="Data[0].ParentId" type="hidden" value="1" data-val="true" data-val-number="The field ParentId must be a number." data-val-required="The ParentId field is required." />
<input id="Data_0__Name" name="Data[0].Name" type="hidden" value="MyListItem1" />
<input id="Data_0__Value" name="Data[0].Value" type="text" value="SomeValue" />
</div>
Which results in the following posted values:
Id=1&Property1=test1&Property2=test2&Data%5B0%5D.Id=1&Data%5B0%5D.ParentId=1&Data%5B0%5D.Name=MyListItem1&Data%5B0%5D.Value=SomeValue
Notice there's no 'model.' in the name and posted values...
Try to change the code for the Data collection to this, and let MVC take care of the naming:
<div class="editor-field">
#for (int i = 0; i < Model.Data.Count(); i++)
{
#Html.HiddenFor(m => m.Data[i].Id)
#Html.HiddenFor(m => m.Data[i].ParentId)
#Html.HiddenFor(m => m.Data[i].Name)
#Html.TextBoxFor(m => m.Data[i].Value)
}
</div>
Alternatively you could have created an EditorTemplate for your nested ViewModel as follows.
#model MyDataItem
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.ParentId)
#Html.HiddenFor(model => model.Name)
#Html.TextBoxFor(model => model.Value)
Create a folder named 'EditorTemplates' in your 'Shared' folder and save the above as 'MyDataItem.cshtml'.
Then in your View, just call the following instead of the foreach loop:
#Html.EditorFor(model => model.Data)
Feels a bit less hackier IMO :)
Just my 2 cents but something worth noting with this issue - the data member in the View Model must be defined as a public property for the postback model binding to work.
I had a very similar problem to the above but used a public data member in my View Model. The same HTML is generated as shown above and all looks well but the model binder threw back an empty collection. Worth watching for...
I have a List<Task> in my model. This list contains 2 tasks(say) in it
public List<Task> Tasks { get; set; }
public class Task {
public Task()
{
Title="";
Total= 0;
}
public string Title{ get; set; }
public int Total{ get; set; }
}
Now in my razor view, I want render 2 text boxes for each of the Tasks in the List<Tasks> of my model.
I didn't loop, just placed direct text boxes like:
#Html.TextBoxFor(m => m.Tasks[0].Title, new { #maxlength = "50"})
#Html.TextBoxFor(m => m.Tasks[0].Total, new { #maxlength = "2"})
<br>
#Html.TextBoxFor(m => m.Tasks[1].Title, new { #maxlength = "50"})
#Html.TextBoxFor(m => m.Tasks[1].Total, new { #maxlength = "2"})
This renders the form fine, but clicking the submit button doesn't do anything in FF.
However it posts fine in IE9.
View source shows this html generated like this:
<input id="Tasks_0__Title" maxlength="50" name="Tasks[0].Title" type="text" value="" />
<input data-val="true" data-val-number="The field Total must be a number." data-val-required="The Total field is required." id="Tasks_0__Total" maxlength="2" name="Tasks[0].Total" type="text" value="" />
This HTML doesn't look right. It has name="Tasks[0].Total" which seems odd.
How should I do this so that I can access the text box values from List<> in my controller after post?
Thanks
EDIT:
I just kept one row for test. This is the html I see in FF.
<input id="Tasks_0__Title" type="text" value="" name="Tasks[0].Title" maxlength="50">
<input id="Tasks_0__Total" type="text" value="" name="Tasks[0].Total" maxlength="2" data-val-required="The Total field is required." data-val-number="The field Total must be a number." data-val="true">
This doesn't post when I click the submit button.
Now if I change name="Tasks[0].Title" to name="Tasks_0__Title" and name="Tasks_0__Total"
in FIREBUG it posts fine.
If I delete name completely it also posts fine in FF
You should use Tasks[0].Total and Tasks[1].Total instead of Items:
#Html.TextBoxFor(m => m.Tasks[0].Title, new { #maxlength = "50"})
#Html.TextBoxFor(m => m.Tasks[0].Total, new { #maxlength = "2"})
<br/>
#Html.TextBoxFor(m => m.Tasks[1].Title, new { #maxlength = "50"})
#Html.TextBoxFor(m => m.Tasks[1].Total, new { #maxlength = "2"})
name="Tasks[0].Total" is not odd. That's exactly how the input should be named in order for the model binder to fetch the value back in the POST action. See this blog post for the wire format used by the default model binder when dealing with lists and dictionaries.
This being said I would recommend you using editor templates => instead of writing those 5 lines of code in your view replace them with:
#Html.EditorFor(x => x.Tasks)
and then inside the corresponding editor template (~/View/Shared/EditorTemplates/Task.cshtml) which will be automatically rendered for each element in the Tasks collection:
#model Task
#Html.TextBoxFor(m => m.Title, new { #maxlength = "50"})
#Html.TextBoxFor(m => m.Total, new { #maxlength = "2"})
<br/>
Now you can leave the editor templates worry about proper naming convention, etc...
As far as your POST action is concerned:
[HttpPost]
public ActionResult Foo(MyViewModel model)
{
// model.Tasks should be correctly bound here
...
}
In case you want to have multiple elements with textboxes.
for (int i = 0; i < Model.Students.Count; i++)
{
#Html.HiddenFor(modelIem => Model.Students[i].StudentId)
<tr>
<td>
#Html.DisplayFor(modelItem => Model.Students[i].Student.FirstNames)
</td>
<td>
#Html.DisplayFor(modelItem => Model.Students[i].Student.LastNames)
</td>
<td>
#Html.TextBoxFor(modelItem => Model.Students[i].Score, new { #type = "number" })
</td>
</tr> }
This is the ViewModel
public class MyModel
{
public List<StudentGrade> Students { get; set; }
}
public class StudentGrade {
public ApplicationUser Student { get; set; }
[Range(1, 100)]
public int? Score { get; set; } = null;
public string Description { get; set; } = null;
public string StudentId { get; set; }
}
At the End it will look like this.