Getting values from CheckBoxes with composite model - c#

I'm trying to obtain list of Id values for which user checked a checkbox. Here's the model:
using System.Collections.Generic;
namespace TestWebApplication3.Models
{
public class TestViewModel
{
public IEnumerable<InnerViewModel> ModelData { get; set; }
public class InnerViewModel
{
public int Id { get; set; }
public bool Checked { get; set; }
}
}
}
Controller:
using System.Web.Mvc;
using TestWebApplication3.Models;
namespace TestWebApplication3.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var test = new TestViewModel();
test.ModelData = new[]
{
new TestViewModel.InnerViewModel {Id = 10},
new TestViewModel.InnerViewModel {Id = 20},
new TestViewModel.InnerViewModel {Id = 30},
new TestViewModel.InnerViewModel {Id = 40}
};
return View(test);
}
[HttpPost]
public string TestAction(TestViewModel model)
{
string s = "";
foreach (TestViewModel.InnerViewModel innerViewModel in model.ModelData)
{
if (innerViewModel.Checked)
s += innerViewModel.Id + " ";
}
return s;
}
}
}
And the View:
#model TestWebApplication3.Models.TestViewModel
#using (Html.BeginForm("TestAction", "Home"))
{
<ol>
#foreach (var testData in Model.ModelData)
{
<li>
#Html.HiddenFor(m => testData.Id)
#Html.CheckBoxFor(m => testData.Checked)
</li>
}
</ol>
<input type="submit"/>
}
So I'm displaying a list of InnerViewModel objects (created in Index action) as checkboxes. When user submits the form, I'd like to somehow obtain the list of Id values which are "checked" in TestAction method. But the returning model is always null.
In the application I'm making there are many more properties to the model, therefore it's important that the list of InnerViewModel objects is nested in the TestViewModel. I also don't want to use third party solution like MvcCheckBoxList, as it seems to me to be an overkill for such a simple task.
Can anyone explain to me what is missing for this to work?

I slightly changed your code to make it working -
ViewModel -
public class TestViewModel
{
public List<InnerViewModel> ModelData { get; set; }
public class InnerViewModel
{
public int Id { get; set; }
public bool Checked { get; set; }
}
}
Controller -
public ActionResult Index()
{
var test = new TestViewModel();
test.ModelData = new List<TestViewModel.InnerViewModel>()
{
new TestViewModel.InnerViewModel {Id = 10},
new TestViewModel.InnerViewModel {Id = 20},
new TestViewModel.InnerViewModel {Id = 30},
new TestViewModel.InnerViewModel {Id = 40}
};
return View(test);
}
public string TestAction(TestViewModel model)
{
string s = "";
foreach (TestViewModel.InnerViewModel innerViewModel in model.ModelData)
{
if (innerViewModel.Checked)
s += innerViewModel.Id + " ";
}
return s;
}
View -
#model MVC.Controllers.TestViewModel
#using (Html.BeginForm("TestAction", "Home"))
{
<ol>
#for (int i = 0; i < Model.ModelData.Count() ; i++ )
{
<li>
#Html.HiddenFor(m => m.ModelData[i].Id)
#Html.CheckBoxFor(m => m.ModelData[i].Checked)
</li>
}
</ol>
<input type="submit" />
}

You need to understand how the model binder works. Simple once you understand that.
MVC Binding to checkbox

Complex object require indexing in order for the model binder to pick them up.
Change it to this so the model binder will pick them up:
#for (int i = 0; i < Model.ModelData.Count; i++)
{
<li>
#Html.HiddenFor(m => Model.ModelData[i].Id)
#Html.CheckBoxFor(m => Model.ModelData[i].Checked)
</li>
}
This is a good article explaining some of the gotchas in model binding.
http://msdn.microsoft.com/en-us/magazine/hh781022.aspx

One thought would be to use a CheckBoxFor control. It saves you a whole lot of trouble in the end in finding what is checked and what isn't. I built a RadioButtonListFor one before and it wasn't very difficult. In fact, here is a link to use on it.
Create MVC3 CheckBoxFor from List and getting the list back (With updated values) on Post

Related

ASPNET CustomValidation on List of Editor Templates, add input-validation-error css class on nested fields

How do I get my editor template field to apply .input-validation-error css class for custom validation on a collection field property?
Normally I'd add a decorator directly on the view model property but in this case, the validation attribute is on the parent view model for the collection.
I tried to apply the answer similar to what was given here:
https://stackoverflow.com/a/8573879/181473
Here is an example with custom validation attribute on the collection property:
public class ParentViewModel
{
public ParentViewModel()
{
Quantities = new List<Quantity>();
}
[Required]
public string Title { get; set; }
[CustomValidation(typeof(ParentViewModel), "ValidateQuantities")]
public List<Quantity> Quantities { get; set; }
public static ValidationResult ValidateQuantities(List<Quantity> quantities , ValidationContext context)
{
ValidationResult result = null;
ParentViewModel vm = (ParentViewModel)context.ObjectInstance;
if (quantities.Sum(m => m.Amount) != 21)
{
var memberNames = new List<string>();
for (int i = 0; i < quantities.Count; i++)
{
string memberName = context.MemberName + "[" + i.ToString() + "]." + nameof(Quantity.Amount);
memberNames.Add(memberName);
}
// Not working, adding member name to result does not work:
result = new ValidationResult("Amounts must add up to 21.", memberNames);
}
return result;
}
}
public class Quantity
{
public int? Id { get; set; }
[Required]
public int? Amount { get; set; }
}
Here is my Controller:
public class FormController : Controller
{
[HttpGet]
public ActionResult Index()
{
var viewModel = new ParentViewModel();
viewModel.Quantities = new List<Quantity>()
{
new Quantity(){ Id = 1000 },
new Quantity(){ Id = 1001 },
new Quantity(){ Id = 1002 },
};
return View(viewModel);
}
[HttpPost]
public ActionResult Index(ParentViewModel viewModel)
{
ModelState.Clear();
TryValidateModel(viewModel);
for (int i = 0; i < viewModel.Quantities.Count; i++)
{
string prefix = nameof(viewModel.Quantities) + "[" + i.ToString() + "]";
TryValidateModel(viewModel.Quantities[i], prefix);
}
return View(viewModel);
}
}
This is the Form razor template:
#* Views/Form/Index.cshtml *#
#model Mvc5App.Controllers.ParentViewModel
<style>
.input-validation-error {
background: red;
}
</style>
<br>
#using (Html.BeginForm("Index", "Form", FormMethod.Post))
{
#Html.ValidationSummary();
#Html.LabelFor(m => m.Title)
#Html.TextBoxFor(m => m.Title)
<br>
for (int i = 0; i < Model.Quantities.Count; i++)
{
#Html.EditorFor(m => m.Quantities[i], "Form/EditorTemplates/Quantity");
<br>
}
<button type="submit" name="SubmitAction" value="SubmitAction" class="btn btn-primary">Submit</button>
}
And this is the editor template:
#* Views/Form/EditorTemplates/Quantity.cshtml *#
#model Mvc5App.Controllers.Quantity
#Html.HiddenFor(m => m.Id)
#Html.LabelFor(m => m.Amount)
#Html.TextBoxFor(m => m.Amount)
When I call TryValidateModel(viewModel); in the POST route the "ParentViewModel.Title" field gets the class applied that changes the background color to red.
But all the "ParentViewModel.Quantities" Amount fields don't get the .input-validation-error css class which I guess means ModelState isn't aware that these fields have an issue.
How can I get all the amount fields to turn red if that custom validation result fails validation?

Asp.net razor textbox array for list items

I can't find or figure out how to take a list of items (cupcakes) and display them in razor with a quantity field.
What is happening is I am not able to get the values for each cupcake quantity in the list. Can you do textbox arrays in Razor?
VIEW
<div class="form-group">
<label>Cupcakes</label>
#foreach (var cupcake in Model.CupcakeList)
{
#Html.TextBox("CupcakeQuantities", cupcake.Id) #cupcake.Name <br/>
}
</div>
MODEL
public List<Cupcake> CupcakeList { get; set; }
public List<int> CupcakeQuantities { get; set; }
CONTROLLER
public ActionResult Create()
{
var model = new PartyBookingModel()
{
CupcakeList = db.Cupcakes.ToList(),
CupcakeQuantities = new List<int>()
};
return View(model);
}
CUPCAKE (ENTITY)
public class Cupcake
{
public int Id { get; set; }
public string Name { get; set; }
public decimal PerDozen { get; set; }
}
You have to use an index, rather than foreach for it to work.
#for (int i = 0; i < Model.CupcakeList.Count; i++)
{
#Html.TextBoxFor(x=>Model.CupcakeQuantities[i]) #Model.CupcakeList[i].Name <br/>
}
This will create sequentially named+number entries that will be recombined back into the model on post back.
I realise this may seem like "why doesn't foreach work?", but with foreach there is not enough reflected information available to TextBoxFor (as it is just a single object), whereas the array index is extracted by reflection from the Model.CupcakeQuantities[i] expression.
The receiving controller method should take the same as the model passed to the view:
e.g.
[HttpPost]
public ActionResult(PartyBookingModel model)
Try it this way:
view:
#for (int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(x=>Model[i].Id) #Model[i].Name
#Html.TextBoxFor(x => Model[i].Quantity) <br/>
}
model:
public class CupcakeViewModel
{
public int Id {get;set;}
public string Name {get;set;}
public int Quantity {get;set;}
}
controller:
public ActionResult Create()
{
var model = db.Cupcakes.Select(c => new CupcakeViewModel {
Id = c.Id,
Name = c.Name,
Quantity = 0
})
.ToList();
return View(model);
}
[HttpPost]
public ActionResult Create(CupcakeViewModel[] cakes)
{
//Save choosen cakes
}

Problems model binding nested list in ASP.NET MVC

I am trying to bind a view to a model which contains a list in a list. Naturally I would prefer to use out of the box model binding. Having spent some time on it yesterday I found a workaround which is really a hack and I would like to correct this. The basic structure of my models are as follows:
public class MyMPIModel
{
public List<ScoreInfo> ScoreInfo { get; set; }
}
public class ScoreInfo
{
public int ScorePrefId { get; set; }
public List<Category> Categories { get; set; }
}
public class Category
{
public int Id;
public string Name;
public bool Checked;
}
The view InterestCategories.cshtml contains the following form:
#using (Html.BeginForm())
{
for (var i = 0; i < Model.ScoreInfo.Count; i++)
{
#Html.EditorFor(x => x.ScoreInfo[i])
}
}
The editor template ScoreInfo.cshtml:
#Html.HiddenFor(x => x.ScorePrefId)
<div class="preferences-block">
#for (var i = 0; i < Model.Categories.Count; i++)
{
#Html.EditorFor(x => x.Categories[i])
}
</div>
Finally the editor template Category.cshtml:
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Name)
<label>
#Html.CheckBoxFor(x => x.Checked, new { #class = "check"})
<span>#Model.Name</span>
</label>
Inspecting the form using firebug I can see that all the hidden fields have been populated. Also when I submit the form, Fiddler shows the correct data. Here is a sample:
ScoreInfo[0].Categories[1].Id 2
ScoreInfo[0].Categories[1].Name Managing Money
ScoreInfo[0].Categories[1].Checked false
However, when I post to the controller, set a breakpoint and inspect the model, the list of ScoreInfo objects have been populated but the lists of Category objects inside the ScoreInfo object have not.
I have the following POST action in my controller:
[HttpPost]
public ActionResult InterestCategories(MyMPIModel model, FormCollection form)
{
...
// model would not bind forcing me to populate via form collection
for (var i = 0; i < model.ScoreInfo.Count; i++)
{
...
for (var j = 0; j < scoreInfo.Categories.Count; j++)
{
var category = scoreInfo.Categories[j];
var prefix = "ScoreInfo[" + i + "].Categories[" + j + "]";
category.Name = form[prefix + ".Name"];
var sId = form[prefix + ".Id"];
if (sId != null) category.Id = Int32.Parse(sId);
var sChecked = form[prefix + ".Checked"];
if (sChecked != null) category.Checked = sChecked.Contains("true");
}
}
}
You have to use Properties instead of Fields in your Category class:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}

Binding a DropDownList in the view to a column in table using ASP.NET MVC4

I'm brand new to ASP.NET MVC, and I would appreciate any help with my question. I already did plenty of research (not enough apparently) on this topic. I need to bind a dropdownlist to a specific column in a table and then render it in the view. I already have the query to retrieve the table in the controller:
public ActionResult SelectAccountEmail()
{
var queryAccountEmail = (from AccountEmail in db.UserBases select AccountEmail)
var selectItems = new SelectList(queryAccountEmail);
return View(selectItems);
}
I get lost when it come to binding the query to a dropdownlist in the view.
#model RecordUploaderMVC4.Models.UserBase
#{
ViewBag.Title = "SelectAccountEmail";
}
<h2>SelectAccountEmail</h2>
#Html.LabelFor(model => model.AccountEmail);
#Html.DropDownList(Model.AccountEmail);
#Html.ValidationMessageFor(model => model.AccountEmail);
<input /type="submit" value="Submit">
I get this error when I run it:
Server Error in '/' Application.
--------------------------------------------------------------------------------
The model item passed into the dictionary is of type 'System.Web.Mvc.SelectList', but this dictionary requires a model item of type 'RecordUploaderMVC4.Models.UserBase'.
Any help will be appreciated.
Thanks in advance.
Few things wrong. Firstly, change your model to add the following properties (Looking at your view, it's RecordUploaderMVC4.Models.UserBase):
public class UserBase
{
public string AccountEmail { get; set; }
public SelectList Emails { get; set; }
//rest of your model
}
Then, build your model in your controller properly:
public ActionResult SelectAccountEmail()
{
UserBase model = new UserBase();
var queryAccountEmail = (from AccountEmail in db.UserBases select AccountEmail)
model.Emails = new SelectList(queryAccountEmail);
return View(model);
}
Then in your view you can do:
#Html.LabelFor(model => model.AccountEmail)
#Html.DropDownListFor(model => model.AccountEmail, Model.Emails)
#Html.ValidationMessageFor(model => model.AccountEmail)
Step 1:
First Create a model Like this to hold your ListofAccountEmail
public class AccountEmailViewModel
{
public int AccountEmailId { get; set; }
public string AccountEmailDescription { get; set; }
}
Step 2: Create your model class
public class UserBaseViewModel
{
public IEnumerable<SelectListItem> AccountEmail { get; set; }
public string AccountEmail { get; set; }
}
Step 3 :
In Controller
[HttppGet]
public ActionResult SelectAccountEmail()
{
var EmailAccounts = (from AccountEmail in db.UserBases select AccountEmail)
UserBase userbaseViewModel = new UserBase
{
AccountEmail = EmailAccounts.Select(x => new SelectListItem
{
Text = x.AccountEmailDescription,
Value = Convert.ToString(x.AccountEmailId)
}).ToList()
};
return View(userbaseViewModel);
}
Step 4 : In View
#model RecordUploaderMVC4.Models.UserBase
#{
ViewBag.Title = "SelectAccountEmail";
}
<h2>SelectAccountEmail</h2>
#Html.ValidationSummary()
<h2>SelectAccountEmail</h2>
#Html.LabelFor(model => model.AccountEmail )
#Html.DropDownListFor(x => x.AccountEmailId, Model.AccountEmail, "Please Select", "")
</div>
<input /type="submit" value="Submit">

Asp.Net MVC with Drop Down List, and SelectListItem Assistance

I am trying to build a Dropdownlist, but battling with the Html.DropDownList rendering.
I have a class:
public class AccountTransactionView
{
public IEnumerable<SelectListItem> Accounts { get; set; }
public int SelectedAccountId { get; set; }
}
That is basically my view model for now. The list of Accounts, and a property for returning the selected item.
In my controller, I get the data ready like this:
public ActionResult AccountTransaction(AccountTransactionView model)
{
List<AccountDto> accounts = Services.AccountServices.GetAccounts(false);
AccountTransactionView v = new AccountTransactionView
{
Accounts = (from a in accounts
select new SelectListItem
{
Text = a.Description,
Value = a.AccountId.ToString(),
Selected = false
}),
};
return View(model);
}
Now the problem:
I am then trying to build the Drop down in my view:
<%=Html.DropDownList("SelectedAccountId", Model.Accounts) %>
I am getting the following error:
The ViewData item that has the key 'SelectedAccountId' is of type 'System.Int32' but must be of type 'IEnumerable'.
Why would it want me to return the whole list of items? I just want the selected value. How should I be doing this?
You have a view model to which your view is strongly typed => use strongly typed helpers:
<%= Html.DropDownListFor(
x => x.SelectedAccountId,
new SelectList(Model.Accounts, "Value", "Text")
) %>
Also notice that I use a SelectList for the second argument.
And in your controller action you were returning the view model passed as argument and not the one you constructed inside the action which had the Accounts property correctly setup so this could be problematic. I've cleaned it a bit:
public ActionResult AccountTransaction()
{
var accounts = Services.AccountServices.GetAccounts(false);
var viewModel = new AccountTransactionView
{
Accounts = accounts.Select(a => new SelectListItem
{
Text = a.Description,
Value = a.AccountId.ToString()
})
};
return View(viewModel);
}
Step-1: Your Model class
public class RechargeMobileViewModel
{
public string CustomerFullName { get; set; }
public string TelecomSubscriber { get; set; }
public int TotalAmount { get; set; }
public string MobileNumber { get; set; }
public int Month { get; set; }
public List<SelectListItem> getAllDaysList { get; set; }
// Define the list which you have to show in Drop down List
public List<SelectListItem> getAllWeekDaysList()
{
List<SelectListItem> myList = new List<SelectListItem>();
var data = new[]{
new SelectListItem{ Value="1",Text="Monday"},
new SelectListItem{ Value="2",Text="Tuesday"},
new SelectListItem{ Value="3",Text="Wednesday"},
new SelectListItem{ Value="4",Text="Thrusday"},
new SelectListItem{ Value="5",Text="Friday"},
new SelectListItem{ Value="6",Text="Saturday"},
new SelectListItem{ Value="7",Text="Sunday"},
};
myList = data.ToList();
return myList;
}
}
Step-2: Call this method to fill Drop down in your controller Action
namespace MvcVariousApplication.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
RechargeMobileViewModel objModel = new RechargeMobileViewModel();
objModel.getAllDaysList = objModel.getAllWeekDaysList();
return View(objModel);
}
}
}
Step-3: Fill your Drop-Down List of View as follows
#model MvcVariousApplication.Models.RechargeMobileViewModel
#{
ViewBag.Title = "Contact";
}
#Html.LabelFor(model=> model.CustomerFullName)
#Html.TextBoxFor(model => model.CustomerFullName)
#Html.LabelFor(model => model.MobileNumber)
#Html.TextBoxFor(model => model.MobileNumber)
#Html.LabelFor(model => model.TelecomSubscriber)
#Html.TextBoxFor(model => model.TelecomSubscriber)
#Html.LabelFor(model => model.TotalAmount)
#Html.TextBoxFor(model => model.TotalAmount)
#Html.LabelFor(model => model.Month)
#Html.DropDownListFor(model => model.Month, new SelectList(Model.getAllDaysList, "Value", "Text"), "-Select Day-")

Categories