Passing values of checkboxes from View to Controller - asp.net-mvc

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>

Related

Binding in Razor returns null (OnPostAsync)

In the code below, we all values in QuestionViewModel are null. Any ideas what I am doing wrong about binding?
cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using VerityLearn.DataAccess;
using VerityLearn.Domain;
using VerityLearn.DataAccess.UOW;
using VerityLearn.WebUI.ViewModels;
namespace VerityLearn.WebUI.Pages.Questions
{
[Authorize]
public class StudentQuestionsModel : PageModel
{
private readonly SignInManager<VerityUser> _signInManager;
private readonly UserManager<VerityUser> _userManager;
private readonly IStudentQuesionsUOW _studentQuesionsUOW;
private readonly VerityLearn.DataAccess.VerityContext _context;
public StudentQuestionsModel(
VerityContext context,
SignInManager<VerityUser> signInManager,
UserManager<VerityUser> userMrg,
IStudentQuesionsUOW studentQuesionsUOW
)
{
_context = context;
_signInManager = signInManager;
_userManager = userMrg;
_studentQuesionsUOW = studentQuesionsUOW;
} // end public StudentQuestionsModel(VerityContext context, SignInManager<VerityUser> signInManager, UserManager<VerityUser> userMrg)
#region User Properties
public VerityUser VerityUser { get; set; }
//[TempData]
//public string UserId { get; set; }
public Student Student { get; set; }
public Prospect Prospect { get; set; }
public Parent Parent { get; set; }
public ExamUserViewModel ExamUserViewModel { get; set; }
#endregion // User Properties
public DateTime DateStarted { get; set; }
public Exam Exam { get; set; }
[TempData]
public int ExamId { get; set; }
ExamQuestion ExamQuestion { get; set; }
public List<ExamQuestion> ExamQuestions { get; set; }
[TempData]
public int NbrOfExamQuestions { get; set; }
public ExamViewModel ExamViewModel { get; set; }
[TempData]
public int QuestionNdx { get; set; }
[BindProperty]
public QuestionViewModel QuestionViewModel { get; set; }
[ViewData]
public string Message { get; set; }
[BindProperty]
public Question Question { get; set; }
public async Task OnGetAsync(int? examId, int? questionNdx)
{
Message = string.Empty;
if (_signInManager.IsSignedIn(HttpContext.User))
{
string email = HttpContext.User.Identity.Name;
VerityUser = await _studentQuesionsUOW.GetVerityUser(email);
//UserId = VerityUser.Id.ToString();
// TODO: Setup priorities of setting Student, Prospect and Parent properties, might involve Enrollments 4/30/2020
//Student = await _context.Students.Where(s => s.UserId == VerityUser.Id).FirstOrDefaultAsync<Student>();
//Prospect = await _context.Prospects.Where(p => p.UserId == VerityUser.Id).FirstOrDefaultAsync<Prospect>();
//Parent = await _context.Parents.Where(p => p.UserId == VerityUser.Id).FirstOrDefaultAsync<Parent>();
DateStarted = DateTime.Now;
if ((examId != null) && (examId.Value > 0))
{
ExamId = examId.Value;
Exam = await _context.Exams.Where(e => e.ExamId == examId)
.Include(e => e.Course).ThenInclude(c => c.Subject).FirstOrDefaultAsync<Exam>();
if (Exam != null)
{
ExamUserViewModel = new ExamUserViewModel
{
ExamUserId = 0,
ExamId = Exam.ExamId,
TimeStarted = DateTime.Now,
Status = ExamUserStatus.InProgress,
StudentId = VerityUser.StudentId,
ProspectId = VerityUser.ProspectId,
ParentId = VerityUser.ParentId
};
// TODO: If this is a new ExamUser, we must insert it to VerityLearnDB2.ExamUsers
if (NbrOfExamQuestions == 0)
{
ExamQuestions = await _context.ExamQuestions
.Where(eq => eq.ExamId == examId)
.ToListAsync<ExamQuestion>();
NbrOfExamQuestions = ExamQuestions.Count;
TempData.Keep("NbrOfExamQuestions");
} // endif (NbrOfExamQuestions == 0)
if ((questionNdx == null) || (questionNdx.Value == 0))
{
questionNdx = 1;
} // endif ((questionNdx == null) || (questionNdx.Value == 0))
QuestionNdx = questionNdx.Value;
TempData.Keep("QuestionNdx");
ExamQuestion = await _context.ExamQuestions
.Include(eq => eq.Question)
.ThenInclude(q => q.Options)
.Where(eq => eq.ExamQuestionOrder == questionNdx)
.FirstOrDefaultAsync<ExamQuestion>();
QuestionViewModel = new QuestionViewModel
{
QuestionId = ExamQuestion.QuestionId,
ExamQuestionOrder = ExamQuestion.ExamQuestionOrder,
QuestionText = ExamQuestion.Question.QuestionText,
IsSingleSelection = ExamQuestion.Question.IsSingleSelection,
Options = new List<OptionViewModel>()
};
ExamViewModel = new ExamViewModel
{
ExamId = Exam.ExamId,
ExamName = Exam.ExamName,
Questions = new List<QuestionViewModel>()
};
ExamViewModel.Questions.Add(QuestionViewModel);
ExamViewModel.ExamUserViewModel = ExamUserViewModel;
List<AnswerOption> answerOptions = _context.AnswerOptions
.Where(ao => ao.QuestionId == ExamQuestion.QuestionId)
.ToList<AnswerOption>();
foreach (AnswerOption ao in answerOptions)
{
OptionViewModel ovm = new OptionViewModel
{
OptionId = ao.OptionId,
OptionText = ao.OptionText,
IsCorrect = ao.IsCorrect
};
ovm.UserExamOptionViewModel = new UserExamOptionViewModel
{
UserExamOptionId = ExamUserViewModel.ExamUserId,
UserExamId = 0,
OptionId = ao.OptionId,
IsSelected = false
};
QuestionViewModel.Options.Add(ovm);
}
}
else
{
Message = String.Format("Error: Exam with Identifier, {0}, was not found.", examId);
}
}
else
{
Message = String.Format("Error: Exam with Identifier, {0}, was not found.", examId);
}
}
else
{
Message = "Error: Login is required.";
}
}
public async Task<IActionResult> OnPostAsync(QuestionViewModel QuestionViewModel)
{
var t = QuestionViewModel;
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Question).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
}
return RedirectToPage("./Index");
}
}
}
cshtml file:
#page "{examId:int?}"
#model VerityLearn.WebUI.Pages.Questions.StudentQuestionsModel
#{
ViewData["Title"] = "StudentQuestions";
}
#if (String.IsNullOrEmpty(#Model.Message))
{
<div class="row" style="background-color: #5D2685; color: #FFFF80;">
<div class="col-md-6">
<h4>Exam: #Html.DisplayFor(model => model.Exam.ExamName)</h4>
</div>
<div class="col-md-6">
<h4>Course: #Html.DisplayFor(model => model.Exam.Course.CourseName)</h4>
</div>
</div>
<br />
<br />
<div>
<h4>Question</h4>
<hr />
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Question.QuestionId" />
<div class="form-group">
<label asp-for="Question.QuestionText" class="control-label"></label>
<input asp-for="Question.QuestionText" class="form-control" />
<span asp-validation-for="Question.QuestionText" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Question.KeyToAnswer" class="control-label"></label>
<input asp-for="Question.KeyToAnswer" class="form-control" />
<span asp-validation-for="Question.KeyToAnswer" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Question.IsSingleSelection" class="control-label"></label>
<input asp-for="Question.IsSingleSelection" class="form-control" />
<span asp-validation-for="Question.IsSingleSelection" class="text-danger"></span>
</div>
<label asp-for="QuestionViewModel.QuestionText" class="control-label"></label>
#if (Model.QuestionViewModel.IsSingleSelection.Value)
{
<p>Select the correct option.</p>
#foreach (OptionViewModel opt in Model.QuestionViewModel.Options)
{
<input type="radio" name="option" value="#opt.UserExamOptionViewModel.IsSelected"><label for=" #opt.OptionText"> #opt.OptionText </label><br />
}
}
else
{
<p>Select the correct options (More than one).</p>
#foreach (OptionViewModel opt in Model.QuestionViewModel.Options)
{
<input type="checkbox" name="option" value="#opt.UserExamOptionViewModel.IsSelected"><label for=" #opt.OptionText"> #opt.OptionText </label><br />
}
}
#{
var prevDisabled = (Model.QuestionNdx <= 1) ? "disabled" : "";
var nextDisabled = (Model.QuestionNdx >= Model.NbrOfExamQuestions) ? "disabled" : "";
}
<button type="submit" asp-route-questionIndex="#(Model.QuestionNdx - 1)" class="btn btn-primary #prevDisabled">Previous</button>
<button type="submit" asp-route-questionIndex="#(Model.QuestionNdx + 1)" class="btn btn-primary #nextDisabled">Next</button>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
}
else
{
<div class="row" style="background-color: #5D2685; color: #FFFF80;">
<div class="col-md-6">
<h4>#Model.Message</h4>
</div>
</div>
}
UPDATE (5/11/2020):
I have simplified the view like this:
#foreach(OptionViewModel opt in Model.QuestionViewModel.Options)
{
optionIndex++;
#Html.RadioButtonFor(model => model.QuestionViewModel.Options[optionIndex].OptionText, #opt.OptionText);
#opt.OptionText<br />
}
I see 4 options but all of them are selected. I would expect only one radio button to be selected at a time. I click Next anyway
I now see the option texts are bound. Question: How to figure out which one is selected?
The problem is you are using a complex model, but your HTML does not indicate that the model is complex. I have created a demo to highlight the issue:
Here Student contains Address and List<Subject> (it is complex model)
public class Subject
{
public string SubjectName { get; set; }
}
public class Address
{
public string StreetAddress { get; set; }
}
public class Student
{
public string Name { get; set; }
public Address HomeAddress { get; set; }
public List<Subject> Subjects { get; set; }
}
When displaying a complex model in a form, you need to use the entire model in the html input tag, this way ASP.NET MVC ModelBinder knows how to bind your HTML inputs to your server-side model.
The above demo has 3 inputs, I am using HTML.TextBoxFor which is the older equivalent of asp-for Tag Helper.
Input 1:
#Html.TextBoxFor(model => model.Name)
Generates the following HTML:
<input id="Name" name="Name" type="text" value="John Smith">
Model binder uses name and binds the above input to Student.Name
Input 2
#Html.TextBoxFor(model => model.HomeAddress.StreetAddress)
Generates the following HTML:
<input id="HomeAddress_StreetAddress" name="HomeAddress.StreetAddress" type="text" value="some address">
Here Model binder knows that this input should be bound to Student.HomeAddress.StreetAddress because that's what the input name indicates
Input 3
#Html.TextBoxFor(model => model.Subjects[0].SubjectName)
Generates the following HTML:
<input id="Subjects_0__SubjectName" name="Subjects[0].SubjectName" type="text" value="Math">
Model binder will bind the above input to Student.Subjects[0].SubjectName
See this article for more info.
In your example, Model Binder has no way to know that option belongs to QuestionViewModel.Options because you are not indicating it in the input name:
<input type="checkbox" name="option" value="#opt.UserExamOptionViewModel.IsSelected">
Make sure you're using [BindProperty] attribute.

Get The Name Field Class Razor?

public class ViewModel_Index
{
public int Id { get; set; }
public string Name { get; set; }
[UIHint("_Date")]
public DateTime bd { get; set; }
[UIHint("_EnumRadioButton")]
public Gender sex{ get; set; }
}
File _EnumRadioButton.cshtml
#model Enum
#{
List<SelectListItem> LstT = new List<SelectListItem>();
Array ev = Enum.GetValues(Model.GetType());
foreach (Enum item in ev)
{
LstT.Add(new SelectListItem
{
Text = Sample.Service.CustomExtension.EnumExtension.ToDescription(item),
Value = item.ToString()
});
}
#Helpers.RadioButtonList(Model.GetType().Name , LstT)
}
File Helpers.cshtml
#helper RadioButtonList(string groupName, IEnumerable<System.Web.Mvc.SelectListItem> items)
{
<div class="RadioButtonList">
#foreach (var item in items)
{
#item.Text
<input type="radio" name="#groupName" value="#item.Value"
#if (item.Selected) { <text> checked="checked" </text> } />
}
</div>
}
index
#using (#Html.BeginForm("test", "Home", FormMethod.Post))
{
#Html.EditorForModel()
<button type="submit">send</button>
}
controller
[HttpPost]
public ActionResult test(ViewModel_Index a)
{
return View();
}
view source
<div class="RadioButtonList">
زن<input type="radio" name="Gender" value="Woman"/>
مرد<input type="radio" name="Gender" value="Man"/>
هیچکدام<input type="radio" name="Gender" value="Non"/>
</div>
File _EnumRadioButton.cshtml
prablem:
name radio must be same name field
radio Name="gender"
field Name="sex"
#Helpers.RadioButtonList(Model.GetType().Name , LstT)

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" })

MVC 4 Model Binding to Radio button list on Submit

My project is MVC 4 with jquery mobile. I'm trying to figure out how to fill my model with data on submit with the following:
From a hidden value that will be populated on the get request
Checked Radio button value from a list on the view
Here is my Model:
public class ResultsModel
{
[Display(Name = "PatientFirstName")]
public string PatientFirstName { get; set; }
[Display(Name = "PatientLastName")]
public string PatientLastName { get; set; }
[Display(Name = "PatientMI")]
public string PatientMI { get; set; }
public List<QuestionModel> QuestionList = new List<QuestionModel>();
}
public class QuestionModel
{
public string Question { get; set; }
public int QuestionID { get; set; }
public int AnswerID { get; set; }
}
It has a collection of questions that is filled with data on the get request. Here is the controller code:
public class ResultsController : Controller
{
//
// GET: /Results/
public ActionResult Index()
{
if (Request.IsAuthenticated)
{
ResultsModel resultsModel = new ResultsModel();
//Get data from webservice
myWebService.TestForm inrform;
var service = new myWebService.myService();
testform = service.TestForm(id);
if (testform != null)
{
//Render the data into results data model
int count = 1;
string text = string.Empty;
foreach (myWebService.Question questiontext in testform.QuestionList)
{
QuestionModel newquestion = new QuestionModel();
text = "Question " + count + ": " + questiontext.text;
if (questiontext.text != null)
{
newquestion.Question = text;
newquestion.QuestionID = questiontext.id;
}
resultsModel.QuestionList.Add(newquestion);
count += 1;
}
}
else
{
//Error
}
return View(resultsModel);
}
// Error
return View();
}
[AllowAnonymous]
[HttpPost]
public ActionResult Index(ResultsModel model,FormCollection fc)
{
if (fc["Cancel"] != null)
{
return RedirectToAction("Index", "Home");
}
if (ModelState.IsValid)
{
in the post action, the model.QuestionList is always empty. Here is my View:
#model Models.ResultsModel
#{
ViewBag.Title = Resources.Account.Results.Title;
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<li data-role="fieldcontain">
#Html.LabelFor(m => m.INRTestDate)
#Html.EditorFor(m => m.INRTestDate)
#Html.ValidationMessageFor(model => model.INRTestDate)
</li>
<li data-role="fieldcontain">
#Html.LabelFor(m => m.INRValue)
#Html.TextBoxFor(m => m.INRValue)
</li>
<li>
#for (int i = 0; i < Model.QuestionList.Count; i++)
{
<div data-role="label" name="question" >
#Model.QuestionList[i].Question</div>
#Html.HiddenFor(m => m.QuestionList[i].QuestionID)
<div data-role="fieldcontain">
<fieldset data-role="controlgroup" data-type="horizontal" data-role="fieldcontain" >
<input id="capture_type_yes" name="answer_type" type="radio" data-theme="c" value="1" />
<label for="capture_type_yes" >
Yes</label>
<input id="capture_type_no" name="answer_type" type="radio" data-theme="c" value="0" />
<label for="capture_type_no">
No</label>
<input id="capture_type_na" name="answer_type" type="radio" data-theme="c" value="2" />
<label for="capture_type_na">
Not Applicable</label>
</fieldset>
</div> }
<label for="textarea-a">
Comments</label>
<textarea name="textarea" id="textarea-a"> </textarea>
<fieldset class="ui-grid-a">
<div class="ui-block-a">
<button type="submit" name="Submit" data-theme="c">Submit</button></div>
<div class="ui-block-b">
<button type="submit" name="Cancel" data-theme="b" class="cancel">Cancel</button></div>
</fieldset>
</li>
In the for each code above, my model.Questionlist collection is not being updated. I also need to figure out how I can tie the radio button click (Yes,No or Not Applicable) to the AnswerID property of my model.Questionlist collection
It actually ended up being this line:
public List<QuestionModel> QuestionList = new List<QuestionModel>();
I needed the get/set to initialize
public List<QuestionModel> QuestionList { get; set; }
Please Can You Add Code after
[AllowAnonymous]
[HttpPost]
public ActionResult Index(ResultsModel model,FormCollection fc)
{
if (fc["Cancel"] != null)
{
return RedirectToAction("Index", "Home");
}
if (ModelState.IsValid)
{
This would be a good post if you finish your code.
MVC5 Bind a LIST for Radio Button with Razor
Create a class RadioList anywhere in the program
public class RadioList
{
public int Value { get; set; }
public string Text { get; set; }
public RadioList(int Value, string Text)
{
this.Value = Value;
this.Text = Text;
}
}
In your Controller
List<RadioList> Sexes = new List<RadioList>();
Sexes.Add(new RadioList(1, "Male"));
Sexes.Add(new RadioList(2, "Female"));
Sexes.Add(new RadioList(3, "Other"));
ViewBag.SexeListe = Sexes;
In the Razor view (replace model.Sexe by the value in your database/model field
#foreach (RadioList radioitem in ViewBag.SexeListe) {
#Html.RadioButtonFor(model => model.Sexe, #radioitem.Value) #Html.Label(#radioitem.Text)
}

Resources