How to tie a data model to multiple groups of radio buttons? - asp.net

I need to present to the user a list of yes/no type questions. Here are my models:
class QuestionModel {
public String ID {get; set;}
public String Title {get; set; }
public bool Value {get; set; }
}
class QuestionsModel {
List<QuestionModel> Questions {get; set}
}
In my Razor code, I might be able to do something like:
#foreach(var item in Model.Questions) {
#Html.Label(item.Title);
#Html.RadioButton(item.ID, "Yes");
#Html.RadioButton(item.ID, "No");
}
However, I don't see how the Value field in QuestionModel will get populated based on what the user has selected. Please help. Regards.

Firstly you need to use a for loop or an EditorTemplate for generating a collection otherwise the controls will not be named correctly and cannot be bound on post back
#model QuestionsModel
#(using Html.BeginForm())
{
for (int i = 0; i < Model.Questions.Count; i++)
{
string yes = i + "Yes";
string no = i + "No";
#Html.HiddenFor(m => m.Questions[i].ID)
#Html.DisplayFor(m => m.Questions[i].Title) // assuming you don't want to edit this
#Html.RadioButtonFor(m => m.Questions[i].Value, true, new { id = #yes })
<label for="#yes">Yes</label>
#Html.RadioButtonFor(m => m.Questions[i].Value, false, new { id = #no })
<label for="#no">No</label>
}
<input type="submit" value="Save" />
}

Related

Mapping list of checked InputCheckbox elements to list of string on the form model

I have a list of strings that I'd like to list in a form. Users should be able to check the boxes for the strings they want and I'd like to bind the list values back to the form model.
Let's take a look at the code I've figured out so far:
//mypage.razor
//...
<EditForm Model="MyModel">
#foreach(var opt in AvailableOptions)
{
<label for="option-#opt">#opt</label>
<InputCheckbox id="option-#opt" name="option-#opt" type="checkbox" #bind="#(/* bind to what?!? */)"/>
}
</EditForm>
#code
{
private MyPageModel MyModel = new ();
private List<string> AvailableOptions = new List<string>
{
"Apple",
"Banana",
"Cherry"
}
public class MyPageModel
{
public List<string> SelectedValues { get; set; } = new();
}
}
So this is where I started at. Of course, while I can show the value of the label as part of the loop, it's not clear how I'd bind the boolean value of the Checked property to the form model, especially not when I'm wanting to save the opt string value to the form model's list (for checked values) and not a collection of boolean values.
Looking at an answer to a similar question, the thought is that I'd create a "carrier" class with the name and a boolean like:
public class MyCheckedOption
{
public string Name {get; set;}
public bool IsChecked {get; set;}
}
Now, when I bind in the InputCheckbox, I can now set AvailableOptions to a list of MyPageModel and do #bind="opt.IsChecked", but this now binds to MyPageModel and doesn't bind back to my form model.
Now, in my OnValidSubmit, I could harvest the values of these and populate my form model, but that doesn't seem ideal.
Rather, is there some way to map the boolean of the checked properties (as populated by something in my code behind) to a list of string (wherein the value I want to use is another property of the "carrier" class I'm looping through a list of) that I can store directly on the form model?
You can use Linq's Select method to convert your list to something else-- in the following case, a list of MyCheckableOption objects. Later, when you want to do something with the list, you can do the same in reverse to get back to a List<string>
#foreach (var opt in AvailableOptions)
{
<label>#opt.Name </label>
<input type="checkbox" #bind="opt.IsChecked" />
<br/>
}
<hr />
#foreach (var item in AvailableOptions.Where(option => option.IsChecked))
{
<div>#item.Name has been selected. </div>
}
#code
{
private List<MyCheckableOption> AvailableOptions = new List<string>
{
"Apple",
"Banana",
"Cherry"
}
.Select(option => new MyCheckableOption {Name = option }).ToList();
public class MyCheckableOption
{
public string Name { get; set; }
public bool IsChecked { get; set; }
}
}

Incorrect values being posted from View after dynamically removing rows

I have the option of dynamically adding and removing rows in my form. Before submitting the form if I remove the last row it works fine but if I remove any other row not in a sequence then add a row and then submit the form it posts the wrong value in my model (i.e. it changes the data of the new row to the data of previous row)
I am using hidden fields but that did not help. The main view renders a partial view which contains the data for the rows. I have tried to use EditorFor but that didn't help either.
Main View:
#using (Html.BeginForm("Submit", "TimeEntry", FormMethod.Post, new {
#class= "form-container" }))
{
<div id="times">
#{Html.RenderPartial("TimeTable", Model);}
</div>
<input name="Submit" class="form-control btn btn-primary" type="submit"
id="submit" value="Submit" />
}
View Model:
public class TimeFilter
{
public List<TimeItemWeekly> TimeItemWeekly { get; set; }
}
public class TimeItemWeekly
{
public string SelectedJob { get; set; }
public List<SelectListItem> Job { get; set; }
public string SelectedServiceItem { get; set; }
public List<SelectListItem> ServiceItem { get; set; }
}
Partial View:
#model NWwebappCS.Models.TimeFilter
#if (Model.TimeItemWeekly != null)
{
for (int i = 0; i < Model.TimeItemWeekly.Count(); i++)
{
<div class="row-container #(Model.TimeItemWeekly[i].HasError ?
<div class="row">
<input type="hidden" name="TimeItemWeekly.Index" value="#i" />
<div class="customer-details">
#Html.DropDownListFor(x => x.TimeItemWeekly[i].SelectedJob,
Model.TimeItemWeekly[i].Job, new { #class = "jobs select
form-control" })
</div>
<div class="service-details">
#if (Model.TimeItemWeekly[i].ServiceItem != null)
{
#Html.DropDownListFor(x =>
x.TimeItemWeekly[i].SelectedServiceItem,
Model.TimeItemWeekly[i].ServiceItem, new { #class =
"service-items select" })
}
else
{
<select name="TimeItemWeekly[#i].SelectedServiceItem"
class="service-items select">
<option></option>
</select>
}
</div>
</div>
</div>
}
}
Controller:
[System.Web.Http.HttpPost]
public ActionResult Submit(Models.TimeFilter Model, string submit)
{
switch (submit)
{
case "Submit":
string errorMessage = ValidateTime(Model);
FillLists(Model);
if (errorMessage == "")
{
DataTable timeRows = GetWeekData(Model);
DeleteTime(Model, timeRows);
SaveTime(Model, timeRows);
if (Model.TimeItemWeekly != null)
{
Model.TimeItemWeekly =
Model.TimeItemWeekly.OrderBy(x => x.Job.Where(y
=>
y.Selected).First().Text).ThenBy(x => x.ServiceItem.Where(y
=> y.Selected).First().Text).ToList();
}
GetUserInfo(Model);
HasPrivilege(Model);
TempData["SuccessMessage"] = "Changes Saved!";
return View("~/Views/TimeEntry/Index.cshtml",
Model);
}
else
{
TempData["ErrorMessage"] = errorMessage;
return View("~/Views/TimeEntry/Index.cshtml",
Model);
}
default:
return View("~/Views/TimeEntry/Index.cshtml", Model);
}
}
}
This happens because the indices get out of order and the mvc model binder can't bind the list properly because the name values which contain the index position are no longer sequential.
One way to solve this is to create an ajax call that returns your partial view view with an updated data set that doesn't contain the row you removed, and replacing the old partial HTML with a new one that has correct indices.
It would look something like this:
JS event for the remove button:
$(document).on("click", ".red-box", function () {
var id = 1; // this would be the id of the thing you are removing, if needed
$.get('/path/to/remove', { id: id }, function(data){
$("#times").html(data);
}
});
Action
public ActionResult Remove(int id) {
// Get data based on id and make any dB updates you need to make
var model = GetData(); // Get new NWwebappCS.Models.TimeFilter without removed row
return PartialView("TimeTable", model);
}
Something along those lines....

Radio Button For multiple bools

Say I have the following properties in my model that I want to be mutually exclusive:
public bool PrintWeek1 {get; set;}
public bool PrintWeek2 {get; set;}
public bool PrintWeek3 {get; set;}
Is it possible to render these as a set of radio buttons or do I need to change them to an enum?
If I use #Html.RadioButtonFor it renders name as the name of the property so they aren't grouped correctly.
Here comes a quick solution, let you have following properties in Model -
public bool PrintWeek1 { get; set; }
public bool PrintWeek2 { get; set; }
public bool PrintWeek3 { get; set; }
public string SelectedValue { get; set; }
Then your HTML should be like this -
#Html.RadioButtonFor(Model => Model.PrintWeek1, "PrintWeek1", new { #Name = "SelectedValue" })
#Html.RadioButtonFor(Model => Model.PrintWeek2, "PrintWeek2", new { #Name = "SelectedValue" })
#Html.RadioButtonFor(Model => Model.PrintWeek3, "PrintWeek3", new { #Name = "SelectedValue" })
Then when you submit the form, you will get the selected value in SelectedValue property.
EDIT
To Address #StephenMuecke point, created the below solution -
Create a enum -
public enum PrintWeekType
{
PrintWeek1, PrintWeek2, PrintWeek3
}
Then have a model property (instead of individual properties, have single emum property) -
public PrintWeekType SelectedValue { get; set; }
HTML should be like below -
#Html.RadioButtonFor(m => m.SelectedValue, PrintWeekType.PrintWeek1)
#Html.RadioButtonFor(m => m.SelectedValue, PrintWeekType.PrintWeek2)
#Html.RadioButtonFor(m => m.SelectedValue, PrintWeekType.PrintWeek3)
Using above sample, one can pre-select a radiobutton, at the same time we can post the selected value in SelectedValue property.
Ok I abandoned the bools and just ended up using a list - this seemed to be the quickest and easiest way to do it.
Where I initialize my model:
public PrintViewModel()
{
this.PrintTypes = new List<string>() { "Print Week 1", "Print Week 2", "Print Week 3" };
}
public List<string> PrintTypes { get; set; }
public string SelectedPrintType { get; set; }
In my view (I wanted the first option selected by default):
#for(int i = 0; i < Model.PrintTypes.Count; i++)
{
<div class="row">
<div class="col-md-2">
#(i == 0 ? Html.RadioButtonFor(x => x.SelectedPrintType, Model.PrintTypes[i], new {#checked = "checked"}) : #Html.RadioButtonFor(x => x.SelectedPrintType, Model.PrintTypes[i]))
<label for="#Model.PrintTypes[i]">#Model.PrintTypes[i]</label>
</div>
</div>
}

How to check If input with id is not empty. ASP.NET MVC

I want to check if Input with id is not empty.
Here is my view
<div class="form-group form-group-inline">
#Html.TextBoxFor(model => model.DocName, new { id = "DocName1", name ="DocName1"})
</div>
<div class="form-group form-group-inline">
#Html.TextBoxFor(model => model.DocName, new { id = "DocName2", name ="DocName2"})
</div>
I've checked it before with if (!(string.IsNullOrEmpty( model.DocName )))
but there is two DocName's in a view and both must be inserted in same table if !empty
I want to store it in Db like this:
id Name
1 DocName1.Value
2 DocName2.Value
Controller is Empty...
but i want something like this:
var DocumentDb = new Document();
if (!(string.IsNullOrEmpty(model.DocName1)))
db.Documents.Add(DocumentDb);
db.SaveChanges();
{
DocumentDb.Name = model.DocName1;
}
if (!(string.IsNullOrEmpty(model.DocName2)))
{
DocumentDb.Name = model.DocName2;
db.Documents.Add(DocumentDb);
db.SaveChanges();
}
And my Model...
public class Document
{
[Key]
public int DocumentID { get; set; }
public string DocName { get; set; }
}
Sorry, but I think you have to change your View Model, instead of using this (I supposed):
public class MyModel {
public String DocName { get; set; }
}
you have to use this:
public class MyModel {
[Required]
public String DocName1 { get; set; }
[Required]
public String DocName2 { get; set; }
}
In the view:
<div class="form-group form-group-inline">
#Html.TextBoxFor(model => model.DocName1, new { id = "DocName1", name ="DocName1"})
</div>
<div class="form-group form-group-inline">
#Html.TextBoxFor(model => model.DocName2, new { id = "DocName2", name ="DocName2"})
</div>
After, in your action, to add "two different document" you have to do this:
if(!String.IsNullOrEmpty(model.DocName1)) {
var doc1 = new Document() { DocName = model.DocName1 };
db.Documents.Add(doc1);
}
if(!String.IsNullOrEmpty(model.DocName2)) {
var doc2 = new Document() { DocName = model.DocName2 };
db.Documents.Add(doc2);
}
// Save invoked only if at least one field is set.
if(!String.IsNullOrEmpty(model.DocName1) || !String.IsNullOrEmpty(model.DocName2))
db.SaveChanges();
You made a mistake while instantiating objects:
when you do this:
var DocumentDb = new Document();
technically you create a new object pointer.
when you do, after first "SaveChanges", DocumentDb.DocName = model.DocName2,
entity framework detect that you are "Updating" your existing object (because the pointer is the same).
Hope this can help.
The Model.DocName is sent to the controller as a List. See Scott Hanselman's explanation here
FWIW – you don’t need the bracket notation if you’re submitting simple
types to the server. That is, if your request contains
key=foo&key=bar&key=baz, we’ll correctly bind that to an
IEnumerable<T>, IList<T>, ICollection<T>, T[], Collection<T>, or
List<T>. In the first sentence in this paragraph, "simple type" means
a type for which
TypeDescriptor.GetConverter(typeof(T)).CanConvertFrom(typeof(string))
returns true. This makes a handful of cases simpler.
Set your controller to something like this:
public ActionResult MyController(List<string> docName, int documentID) {
foreach(string doc in docName) {
// do whatever you like
}
}

Pass model value from View to controller is not working

My model has two properties, one of them is an object of another class
public class Association : Entity
{
public Association()
{
this.User = new User();
}
public User User
{
get;
set;
}
public Role Role
{
get;
set;
}
};
and my view is strongly typed to this model
#model MuddyBoots.Greenlight.Association
.
.
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div>
#Html.TextBoxFor(model => model.User.FirstName,new { id = "first-name" })
<span class="red-asterisk">#Html.ValidationMessageFor(model => model.User.FirstName)</span>
</div>
<div>
#Html.HiddenFor(model => model.Role, new { id="hiddenRole"})
<ul id="user-roles">
<li><input type="radio" name="user-role" id="role-admin" value="01" checked="checked" /> Read only</li>
<li><input type="radio" name="user-role" id="role-member" value="02" /> Restricted</li>
<li><input type="radio" name="user-role" id="role-read" value="03"/> Standard</li>
<li><input type="radio" name="user-role" id="role-subscriber" value="04" /> Administrator</li>
</ul>
</div>
}
my controller function is written like that:
[HttpPost]
public ActionResult AddUser(Association association)
{
string firstName = association.User.FirstName;
var role = association.Role;
IRepository<Association> associationRepository = new IRepository<Association>(db);
if (ModelState.IsValid)
{
siteRepository.Update(site);
return RedirectToAction("Index");
}
return View(association);
}
My problem is: when I post my view, my association object is null, it has no values.
to be more precise, when I try to debug these 2 lines:
string firstName = association.User.FirstName;
var role = association.Role;
their values are null, but if I comment the first line, the role variable has a value. So am sensing that the problem is related to the User property, but I do not know how to solve it.
You seem to have used some hidden field for the Role type:
#Html.HiddenFor(model => model.Role, new { id = "hiddenRole" })
But the Role property is a complex type, you cannot serialize it as a hidden field. You will have to use hidden fields for each of the properties you want to be sent:
#Html.HiddenFor(model => model.Role.Property1)
#Html.HiddenFor(model => model.Role.Property2)
#Html.HiddenFor(model => model.Role.Property...)
Also you seem to be using some radio buttons inside your form which are named user-role but you cannot have a property on your Role class with this name (you cannot have dash in a property name), so I guess you will have to use the proper name here if you want the value of those radio buttons to be bound to some property of the Role class on your Association model.
For example let's suppose that your Role class looks like this:
public class Role
{
public string Value { get; set; }
}
Now your view could look like this:
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div>
#Html.TextBoxFor(model => model.User.FirstName, new { id = "first-name" })
<span class="red-asterisk">
#Html.ValidationMessageFor(model => model.User.FirstName)
</span>
</div>
<div>
<ul id="user-roles">
<li>
#Html.RadioButtonFor(model => model.Role.Value, "01", new { id = "role-admin" })
Read only
</li>
<li>
#Html.RadioButtonFor(model => model.Role.Value, "02", new { id = "role-member" })
Restricted
</li>
<li>
#Html.RadioButtonFor(model => model.Role.Value, "03", new { id = "role-read" })
Standard
</li>
<li>
#Html.RadioButtonFor(model => model.Role.Value, "04", new { id = "role-subscriber" })
Administrator
</li>
</ul>
</div>
<button type="submit">OK</button>
}
I don't think the ModelBinder will bind child objects. You could create a custom ModelBinder to to bind your Association class or just create a ViewModel class that flattens your current Model into a single class. So your AssociationViewModel model class might look like this:
public class AssociationViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string RoleName { get; set; }
}
public ActionResult AddUser(AssociationViewModel associationViewModel)
{
if (ModelState.IsValid)
{
var association = new Association
{
User.FirstName = associationViewModel.FirstName,
Role = new Role { Name = associationViewModel.RoleName }
};
IRepository<Association> associationRepository = new IRepository<Association>(db);
....
}
}

Resources