allow to select only one radiobutton - asp.net-mvc

I have a survey with 6 questions. Each question will be rated between (1-5). I want to capture the value selected for each question. Only one value can be selected on each question
The following is allowing me to select more than one option for each question, I should restrict to select only one for each question. All the radio buttons should be grouped as one, and only one button can be selected. I should be able to capture the selected value for each when submitted.
public class SurveyViewModel
{
public GroupViewModel GroupA { get; set; }
public GroupViewModel GroupB { get; set; }
public GroupViewModel GroupC { get; set; }
public SurveyViewModel()
{
GroupA = new GroupViewModel();
GroupA.GroupName = "A";
GroupB = new GroupViewModel();
GroupB.GroupName = "B";
GroupC = new GroupViewModel();
GroupC.GroupName = "C";
}
}
public class GroupViewModel
{
public string GroupName { get; set; }
public bool radio1 { get; set; }
public bool radio2 { get; set; }
public bool radio3 { get; set; }
public bool radio4 { get; set; }
public bool radio5 { get; set; }
}
Editor Template:
<div>
<h4>GroupViewModel</h4>
<hr />
<dl class="dl-horizontal">
<dt>#Html.RadioButtonFor(model => model.radio1, true, new { id = Model.GroupName + "1" })</dt>
<dd>#Html.DisplayNameFor(model => model.radio1)</dd>
<dt>#Html.RadioButtonFor(model => model.radio2, true, new { id = Model.GroupName + "2" })</dt>
<dd>#Html.DisplayNameFor(model => model.radio2)</dd>
<dt>#Html.RadioButtonFor(model => model.radio3, true, new { id = Model.GroupName + "3" })</dt>
<dd>#Html.DisplayNameFor(model => model.radio3)</dd>
<dt>#Html.RadioButtonFor(model => model.radio4, true, new { id = Model.GroupName + "4" })</dt>
<dd>#Html.DisplayNameFor(model => model.radio4)</dd>
<dt>#Html.RadioButtonFor(model => model.radio5, true, new { id = Model.GroupName + "5" })</dt>
<dd>#Html.DisplayNameFor(model => model.radio5)</dd>
</dl>
</div>
and the survey View as follows:
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.GroupA)
#Html.EditorFor(m => m.GroupB)
#Html.EditorFor(m => m.GroupC)
<input type="submit" value="Continue" />
}
I have added a group name as posted below, but I could not able to capture the selected value in the http post
#Html.RadioButtonFor(model => model.radio1, true, new { id = Model.GroupName + "1", #Name = Model.GroupName })

You have to override the name attribute:
#Html.RadioButtonFor(model => model.radio1, true, new { id = Model.GroupName + "1", #Name = "Group" })
You have to do this for every radiobutton that you want to have only one selection.
When you use Html.RadioButtonFor it expected that you use the same property to bind all radio buttons like:
#Html.RadioButtonFor(model => model.radio, "answer1")
#Html.RadioButtonFor(model => model.radio, "answer2")
#Html.RadioButtonFor(model => model.radio, "answer3")
Or using an Enumeration in place of "answer1", "answer2" and "answer3"

Each radio button you generating if for a different property and has a different name (they are not grouped) so all radio buttons within a 'Question' can be selected, and worse, none of then can be unselected once selected.
In addition your properties within SurveyViewModel are all the same type, so you should be using a collection property, not generating individual properties for each item.
Start by creating a view model which represents what you want to display/edit in the view.
public class QuestionVM
{
public string Name { get; set; }
[Required(ErrorMesage = "Please select a rating")]
public int? Rating { get; set; }
}
and in the controller GET method
List<QuestionVM> model = new List<QuestionVM>()
{
new QuestionVM() { Name = "A" },
new QuestionVM() { Name = "B" },
new QuestionVM() { Name = "C", Rating = 3 } // use this if you want to pre-select a radio button
};
return View(model);
Next, create an EditorTemplate for QuestionVM. The templates needs to be named /Views/Shared/EditorTemplates/QuestionVM.cshtml
#model QuestionVM
<div class="question">
#Html.HiddenFor(m => m.Name) // so this value posts back
<h2>#Model.Name</h2>
#for(int i = 1; i < 6; i++)
{
<label>
#Html.RadioButtonFor(m => m.Rating, i, new { id = "" })// remove id attribute to prevent invalid html
<span>#i</span>
</label>
}
#Html.ValidationMessageFor(m => m.Rating)
</div>
and finally in the main view
#model IEnumerable<QuestionVM>
#using (Html.BeginForm())
{
#Html.EditorFor(m => m)
<input type="submit" value="Save" />
}
which will post back to
public ActionResult Edit(IEnumerable<QuestionVM model) // adjust method name to suit

Related

Model binding with complex type

I have made a test controller and view to test complex binding, but I can't seem to make it work.
Here is my ViewModel:
public class TestViewModel
{
public SubTest MainTest { get; set; }
public List<SubTest> SubTestList { get; set; }
}
public class SubTest
{
public string Name { get; set; }
public int Id { get; set; }
}
Here is my View:
#model TestViewModel
#{
using (Html.BeginForm())
{
<h2>Main</h2>
<p>
#Html.DisplayTextFor(m => m.MainTest.Id)
=>
#Html.DisplayTextFor(m => m.MainTest.Name)
</p>
<h2>Subs</h2>
foreach (var sub in Model.SubTestList)
{
<p>
#Html.DisplayTextFor(m => sub.Id)
=>
#Html.DisplayTextFor(m => sub.Name)
</p>
}
<button type="submit">Submit</button>
}
}
And here is my controller:
public ActionResult Test()
{
TestViewModel tvm = new TestViewModel();
tvm.MainTest = new SubTest() { Id = 0, Name = "Main Test" };
tvm.SubTestList = new List<SubTest>()
{
new SubTest() { Id = 1, Name = "Sub Test 1" } ,
new SubTest() { Id = 2, Name = "Sub Test 2" } ,
new SubTest() { Id = 3, Name = "Sub Test 3" } ,
new SubTest() { Id = 4, Name = "Sub Test 4" } ,
};
return View(tvm);
}
[HttpPost]
public ActionResult Test(TestViewModel tvm)
{
return View(tvm);
}
When I load the page, everything displays correctly, but if I set a breakpoint in the POST method, I see that the parameter values are both null.
What am I doing wrong ?
Firstly DisplayTextFor() does not generate form controls (input, textarea, select) therefore there is nothing for the form to post back.
Secondly, if you did want to edit the values of your model (say using a textbox), then you would need to use a for loop (or custom EditorTemplate for typeof SubTest) not a foreach loop for your collection property, for example
for (int i = 0; i < Model.SubTestList.Count; i++)
{
#Html.TextBoxFor(m => m.SubTestList[i].Id)
#Html.TextBoxFor(m => m.SubTestList[i].Name)
}
Or using an EditorTemplate (the name of the template must match your model type
In /View/Shared/EditorTemplates/SubTest.cshtml
#model yourAssembly.SubTest
#Html.TextBoxFor(m => m.Id)
#Html.TextBoxFor(m => m.Name)
and in the main view
#model TestViewModel
....
#Html.EditorFor(m => m.SubTestList)
The EditorFor() method accepts IEnumerable<T> and is smart enough to rendered the html from the template for each item in the collection.

Passing Model data from View to Controller

I am trying to pass the Model data from a View (and PartialView within the View) back to the Controller upon HttpPost. (Adapted from Pass SelectedValue of DropDownList in Html.BeginForm() in ASP.NEt MVC 3)
Why? I want to show a list of assets each with a DropDownList and number of options. Upon submission of form to read the selected items from DropDownList.
My 2 (simplified) models:
public class Booking
{
public int BookingID { get; set; }
public int StoreID { get; set; }
...
public IEnumerable<AssetShort> Assets { get; set; }
}
and
public class AssetShort
{
public int AssetID { get; set; }
....
public int SelectedAction { get; set; }
public IEnumerable<SelectListItem> ActionList { get; set; }
}
In my Booking Controller > Create I build the List:
public ActionResult Booking(int id)
{
// get myBag which contains a List<Asset>
// booking corresponds to 'id'
var myAssets = new List<AssetShort>();
foreach (var a in myBag.Assets)
{
var b = new AssetShort();
b.AssetID = a.ID;
b.SelectedAction = 0;
b.ActionList = new[]
{
new SelectListItem { Selected = true, Value = "0", Text = "Select..."},
new SelectListItem { Selected = false, Value = "1", Text = "Add"},
new SelectListItem { Selected = false, Value = "2", Text = "Remove"},
new SelectListItem { Selected = false, Value = "3", Text = "Relocate"},
new SelectListItem { Selected = false, Value = "4", Text = "Upgrade"},
new SelectListItem { Selected = false, Value = "5", Text = "Downgrade"}
};
myAssets.Add(b);
};
var model = new BookingRequirementsViewModel
{
BookingID = booking.ID,
StoreID = booking.StoreID,
Assets = myAssets.ToList(),
};
return View(model);
My View:
#model uatlab.ViewModels.BookingRequirementsViewModel
#{
ViewBag.Title = "Booking step 2";
}
<h4>Your booking ref. #Model.BookingID</h4>
#using (Html.BeginForm("Booking2", "Booking", FormMethod.Post))
{
<fieldset>
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.StoreID)
#Html.Partial("_Assets", Model.StoreAssets)
<input type="submit" value="Cancel" class="btn btn-default" />
<input type="submit" value="Next" class="btn btn-default" />
</fieldset>
}
The Partial View includes
#foreach (var item in Model)
{
<tr>
<td>#item.Name</td>
<td>#item.Number</td>
<td>#Html.DropDownListFor(modelItem=>item.SelectedAction, item.ActionList)</td>
</tr>
}
So, all this works fine in the browser and I can select dropdowns for each asset listed but when I submit the only value posted back is the StoreID as it is in a "HiddenFor".
The booking2 controller has the model for a parameter:
public ActionResult Booking2(BookingRequirementsViewModel model)
{
//loop through model.Assets and display SelectedActions
}
Let me make it clear what the problems is - in Booking2 controller the Model is null when viewed in Debug mode and I get error "Object reference not set to an instance of an object."
Any ideas please how to pass back the Model to controller from view?
Regards
Craig
You need to create an EditorTemplate for AssetShort. I also suggest moving ActionList to the BookingRequirementsViewModel so your not regenerating a new SelectList for each AssetShort
The models you have posted aren't making sense. Your controller has var model = new BookingRequirementsViewModel { ..., Assets = myAssets.ToList() }; but in the view you refer to #Html.Partial("_Assets", Model.StoreAssets)? Are these 2 different properties. I will assume that StoreAssets is IEnumerable<AssetShort>
/Views/Shared/EditorTemplates/AssetShort.cshtml
#model AssetShort
<tr>
<td>#Html.DispayFor(m => m.Name)</td>
....
<td>
#Html.DropDownListFor(m => m.SelectedAction, (IEnumerable<SelectListItem>)ViewData["actionList"], "--Please select--")
#Html.ValidationMessageFor(m => m.SelectedAction)
</td>
</tr>
In the main view
#model uatlab.ViewModels.BookingRequirementsViewModel
....
#using (Html.BeginForm()) // Not sure why you post to a method with a different name
{
....
#Html.HiddenFor(m => m.StoreID)
#Html.EditorFor(m => m.StoreAssets, new { actionList = Model.ActionList })
....
}
In the controller
public ActionResult Booking(int id)
{
....
var model = new BookingRequirementsViewModel
{
BookingID = booking.ID,
StoreID = booking.StoreID,
Assets = myBag.Assets.Select(a => new AssetShort()
{
AssetID = a.ID,
SelectedAction = a.SelectedAction, // assign this if you want a selected option, otherwise the "--Please select--" option will be selected
....
})
};
ConfigureViewModel(model); // Assign select list
return View(model);
}
And a separate method to generate the SelectList because it needs to be called in the GET method and again in the POST method if you return the view. Note use the overload of DropDownListFor() to generate the option label (null value) as above, and there is no point setting the Selected property (the value of SelectedAction determines what is selected, not this)
private ConfigureViewModel(BookingRequirementsViewModel model)
{
model.ActionList = new[]
{
new SelectListItem { Value = "1", Text = "Add"},
....
new SelectListItem { Value = "5", Text = "Downgrade"}
};
}
and the POST
public ActionResult Booking(BookingRequirementsViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model); // Re-assign select list
return View(model);
}
// save and redirect
}
I recommend also making SelectedAction nullable with the [Required] attribute so you get client and server side validation
public class AssetShort
{
public int AssetID { get; set; }
....
[Required]
public int? SelectedAction { get; set; }
}

How do I use DataAnnotations on Radio Types?

I'm trying to make a form but I have 2 radio buttons which is a required choice if the user wants to complete the registration, but if he/she doesn't select anything, the [Required] DataAnnotation is not working here for some reason.
[Required]
public int UserType { get; set; }
How do I make the form return false with data annotations?
EDIT:
I've changed the model to
[Required(ErrorMessage="Please select an option")]
public int UserType { get; set; }
My view is:
#using (Html.BeginForm("Registrationform", "Members", FormMethod.Post, new { id = "wizardform" }))
{
#Html.ValidationMessageFor(m => m.UserType)
#Html.RadioButtonFor(m => m.UserType, "1", new { #id = "vip_radio"})
#Html.RadioButtonFor(m => m.UserType, "2", new { #id = "normal_radio"})
<input type="submit" value="submit" />
}
when I press submit, the form returns with no errors (it submitted the form) even when I haven't clicked on any of the radio buttons.
I have used like this
Metadata
[Required(ErrorMessage="Please select usertype")]
[Display(Name = "User type")]
public string Usertype{ get; set; }
In view
#Html.RadioButtonFor(model => model.Usertype, "admin", new { id = "admin" })
#Html.Label("admin", "admin", new { style = "padding-right:20px" })
#Html.RadioButtonFor(model => model.Usertype, "employee", new { id = "employee"})
#Html.Label("employee", "employee", new { style = "padding-right:20px" })
works fine for me!
Worked fine for me with below code
$("#MyCheckBox").click(function(){
if($(this).is(':checked')){
$("#vip_radio").rules("add","required")
} else {
$("#normal_radio").rules("remove","required")
}
});

#html razor radio buttons mvc5

here's my viewmodel
public class UserResponseModel
{
public string QuestionId { get; set;}
public string QuestionText { get; set; }
public bool IsChecked { get; set; }
}
so, for checkboxes this works beautifully
for (var i = 0; i < Model.UserResponses.Count; i++)
{
<div class="row">
<div class="col-md-4">
<div class="form-group">
#Html.HiddenFor(x => x.UserResponses[i].QuestionId)
#Html.CheckBoxFor(x => x.UserResponses[i].IsChecked)
but for radio buttons this does not
for (var i = 0; i < Model.UserResponses.Count; i++)
{
<div class="row">
<div class="col-md-4">
<div class="form-group">
#Html.HiddenFor(x => x.UserResponses[i].QuestionId)
#Html.RadioButtonFor(x => x.UserResponses[i].IsChecked, new { Name = "grp1" })
#Html.DisplayTextFor(x => x.UserResponses[i].QuestionText)
when i submit the form then IsChecked is always false - why - what am i missing - as I mentioned for checkboxes it works just fine. I did look at this question here but i'm not sure why radiobuttons require extra properties in the view model to hold the correct answer when checkboxes work transparently.
Edit: my question model is now as so
public class QuestionModel
{
public string WhichQuestion { get; set; }
public int PointsObtained { get; set; }
public bool CorrectAnswer { get; set; }
private List<UserResponseModel> _userResponse;
public List<UserResponseModel> UserResponses
{
get { return _userResponse ?? (_userResponse = new List<UserResponseModel>()); }
set { _userResponse = value; }
}
}
notice i have just added public bool CorrectAnswer { get; set; }
and in my view here's the code
for (var i = 0; i < Model.UserResponses.Count; i++)
{
<div class="row">
<div class="col-md-4">
<div class="form-group">
#Html.HiddenFor(x => x.UserResponses[i].QuestionId)
#Html.HiddenFor(x => x.CorrectAnswer)
#Html.HiddenFor(x => x.UserResponses[i].IsChecked)
#Html.RadioButtonFor(x => x.UserResponses[i].IsChecked, Model.CorrectAnswer, new { Name = "grp1" })
EDIT 2:
#Html.HiddenFor(x => x.UserResponses[i].QuestionId)
#Html.HiddenFor(x => x.SelectedAnswerId)
#Html.HiddenFor(x => x.UserResponses[i].IsChecked)
#Html.RadioButtonFor(x => x.UserResponses[i].IsChecked, Model.SelectedAnswerId, new { Name = "grp1" })
EDIT3:
public class QuestionModel
{
public string WhichQuestion { get; set; }
public int PointsObtained { get; set; }
private List<UserResponseModel> _userResponse;
public List<UserResponseModel> UserResponses
{
get { return _userResponse ?? (_userResponse = new List<UserResponseModel>()); }
set { _userResponse = value; }
}
}
public class UserResponseModel
{
public string QuestionId { get; set;}
public string QuestionText { get; set; }
public string SelectedQuestionId { get; set; }
}
finally, my view
for (var i = 0; i < Model.UserResponses.Count; i++)
{
<div class="row">
<div class="col-md-4">
<div class="form-group">
#Html.HiddenFor(x => x.UserResponses[i].QuestionId)
#Html.RadioButtonFor(x => x.UserResponses[i].SelectedQuestionId, Model.UserResponses[i].QuestionId, new { Name = "grp1" })
#*#Html.CheckBoxFor(x => x.UserResponses[i].IsChecked)*#
#Html.DisplayTextFor(x => x.UserResponses[i].QuestionText)
EDIT4:so finally I'm getting some where
this works!!!
#Html.RadioButtonFor(x => x.UserResponses[i].SelectedQuestionId, Model.UserResponses[i].QuestionId)
I can now see the selectedquestionid populated in my httppost method but if I do this
#Html.RadioButtonFor(x => x.UserResponses[i].SelectedQuestionId, Model.UserResponses[i].QuestionId, new {Name="grp"})
then though i am able to select only one radiobutton the selectedquestionid is null on httppost - wierd
Radio buttons are not checkboxes. Radio buttons are grouped. This means that you should have a single property on your view model to hold the selected radio button value (as shown in the answer you linked to). With check boxes you could have multiple checkboxes selected, that's thy you are binding them to a collection of boolean values in your view model. Radio buttons on the other hand have only one possible selected value because they are mutually exclusive. That's why they should all be bound to a single property on your view model that will hold the selected radio button value.
So in your example of questions and answers, you have a question view model that will contain the question id and text as well as a list of possible answers. Each answer will be represented by an id and text. Your question view model will also have an answer property that will hold the selected answer radio button from the user. It is this property that you are going to bind all your radio button to. But with a different value.

SelectList does not select items when generated inside View

I am having problems to preselect the items when creating SelectList inside View. Here is the Razor code:
#{
ViewBag.Title = "RoleGroupMappings";
}
<h2>Map roles to groups:</h2>
<table>
#foreach (var role in Model.Roles)
{
<tr>
<td>
<p>#role.RoleName: </p>
</td>
<td>
#using (Html.BeginForm("MapGroupsToRole", "Role", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.Hidden("RoleId", role.RoleId)
#Html.DropDownList("Groups", new SelectList(role.Groups, "GroupId", "GroupName", role.Groups.Where(g => g.Mapped).Select(g => g.GroupId)), new { #class = "directory-groups", multiple = "multiple", style = "display: none;" })
<input type="button" value="Clear All" class="btn btn-danger clear-selection" />
<input type="submit" value="Save Changes" class="btn btn-success save-mappings" />
}
</td>
</tr>
}
</table>
Does anybody know what is the problem here?
I think you want to be using a MultiSelectList instead of the normal SelectList. Try this:
#Html.DropDownList("Groups", new MultiSelectList(role.Groups, "GroupId", "GroupName", role.Groups.Where(g => g.Mapped).Select(g => g.GroupId)), new { #class = "directory-groups", multiple = "multiple", style = "display: none;" })
Update
Being as this is working for me here, I'll show you how I've setup this test project. Hopefully that will help you to narrow down what the problem is. Test model setup:
public class Role
{
public int RoleId { get; set; }
public string RoleName { get; set; }
public List<Group> Groups { get; set; }
}
public class Group
{
public Guid GroupId { get; set; }
public string GroupName { get; set; }
public bool Mapped { get; set; }
}
The view model:
public class TestViewModel
{
public List<Role> Roles { get; set; }
}
public ActionResult Index()
{
var model = new TestViewModel
{
Roles = new List<Role>
{
new Role
{
RoleId = 1,
RoleName = "Test",
Groups = new List<Group>
{
new Group
{
GroupId = new Guid("12345678-1234-1234-1234-123412341234"),
GroupName = "Group 1",
Mapped = false
},
new Group
{
GroupId = new Guid("12345678-5678-6789-1234-123412341234"),
GroupName = "Group 2",
Mapped = true
},
new Group
{
GroupId = new Guid("12345678-0000-6789-1234-123412341234"),
GroupName = "Group 3",
Mapped = true
}
}
}
}
};
return View(model);
}
My view is the same as yours, except with using MultiSelect and I also removed #class = "directory-groups" and the display: none; style. Both group 2 and group 3 have their Mapped property set to true. This is the result from loading the view:
I found a problem in my controller. I was setting the ViewBag.Groups to some values, and obviously it was interfering with my DropDownList which had the same name. Sorry for that, I was staring whole day and I could not find the error, so I become little desperate. I should have looked at my controller, too.

Resources