I know there are many custom implementation of CheckBoxListFor helper method to fill up the missing feature in the MVC framework. But I am not ready to use them just yet. I am interested in creating a checkbox list using MVC 4 or 5 provided features only. So, I created this model class:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace MvcApplication1.Models
{
public class FruitViewModel
{
public int[] SelectedFruits { get; set; }
public IList Fruits
{
get
{
return new List{
new Fruit{Id=1, Name="Apple", Selected = false},
new Fruit{Id=2, Name="Banana", Selected = false},
new Fruit{Id=3, Name="Cherry", Selected = false},
new Fruit{Id=4, Name="Durian", Selected = false},
new Fruit{Id=5, Name="Elderweiss Grape", Selected = false}
};
}
}
}
public class Fruit
{
public int Id { get; set; }
public string Name { get; set; }
public bool Selected { get; set; }
}
}
And here is the controller class:
using MvcApplication1.Models;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class FruitController : Controller
{
public ActionResult Index()
{
FruitViewModel model = new FruitViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(FruitViewModel model)
{
if (ModelState.IsValid)
{
}
return View(model);
}
}
}
And here is the razor view:
#model MvcApplication1.Models.FruitViewModel
#{
ViewBag.Title = "Index";
}
<h2>Select your favorite fruits</h2>
#using (Html.BeginForm("Index", "Fruit"))
{
<p>Using Html helper method:</p>
for (int i = 0; i < Model.Fruits.Count; i++)
{
#Html.CheckBoxFor(m => m.Fruits[i].Selected) #Model.Fruits[i].Name<br />
}
<p>Plain html without html helper</p>
for (int i = 0; i < Model.Fruits.Count; i++)
{
<input type="checkbox" name="SelectedFruits" value="#Model.Fruits[i].Id" checked="#Model.Fruits[i].Selected" />
#Model.Fruits[i].Name<br />
}
<input type="submit" name="submit" value="Submit" />
}
Here are the problems I am having:
The plain HTML version will populate the SelectedFruits collection with my selected fruits' Ids properly, as you can see from the screen shot. But when the page refreshes after post back, the selected checkboxes status is reset to not checked.
The version using the Html helper CheckBoxFor will not populate my SelectedFruits collection with my selected fruits' IDs, although it does seem to maintain the check box status as checked after post back completes.
So, in either case, there is a big problem.
What is the correct way to set up a checkbox list such that I can get the SelectedFruits collection populated correctly and the status of the checkboxes maintained after form post completes (important when I add other stuff on the page and if validation fails).
I would suggest that you can use the following solution,
Controller Change [Only the Get Method to set the data for the view to display]
public ActionResult Index()
{
FruitViewModel model = new FruitViewModel();
model.Fruits = new List<Fruit>
{
new Fruit{Id=1, Name="Apple", Selected = false},
new Fruit{Id=2, Name="Banana", Selected = false},
new Fruit{Id=3, Name="Cherry", Selected = false},
new Fruit{Id=4, Name="Durian", Selected = false},
new Fruit{Id=5, Name="Elderweiss Grape", Selected = false}
};
return View(model);
}
FruitModel Change, we are setting the property to be populated dynamically based on the fruits that the user has selected.
public int[] SelectedFruits
{
get
{
return Fruits != null && Fruits.Count > 0
? Fruits.Where(f => f.Selected == true).Select(f => f.Id).ToArray()
: new int[0];
}
set { }
}
View Page Change [Index.cshtml]
for (int i = 0; i < Model.Fruits.Count; i++)
{
#Html.CheckBoxFor(m => m.Fruits[i].Selected) #Model.Fruits[i].Name<br />
#Html.HiddenFor(m => m.Fruits[i].Id)
#Html.HiddenFor(m => m.Fruits[i].Name)
}
The problem that I found in your code was that you have used the checkbox name property as "SelectedFruits" in the html for the checkbox that you have manually rendered. Whereas the markup rendered by the Html Helper is having the name of "Fruits[0].Selected" etc...
Hence, the selected fruits was not properly modelbound.
Kindly verify the currently generated markup and post your feedback in case of any queries.
Your problem is that the Fruits property in your viewmodel always creates a new array where the Selected property for each fruit is always false. You can fix it by just initializing the array once in your viewmodel instead:
public IList<Fruit> Fruits
{
get
{
if (_fruits == null)
_fruits = new List<Fruit> {
new Fruit{Id=1, Name="Apple", Selected = false},
new Fruit{Id=2, Name="Banana", Selected = false},
new Fruit{Id=3, Name="Cherry", Selected = false},
new Fruit{Id=4, Name="Durian", Selected = false},
new Fruit{Id=5, Name="Elderweiss Grape", Selected = false}
}
return _fruits;
};
}
private List<Fruit> _fruits;
Once that's fixed, you'll have to update your controller to fix the Selected properties:
[HttpPost]
public ActionResult Index(FruitViewModel model)
{
foreach (int fruitId in model.SelectedFruits)
model.Fruits[fruitId].Selected = true;
return View(model);
}
Related
I have a razor page which contains a for loop which iterates over a list of User objects.
Each repetion of the loop contains a dropdownlist:-
<button type="submit" asp-page-handler="SetUserStatuses">Update Statuses</button>
#for (int i = 0; i < Model.UserList.Count; i++)
{
#Model.UserList[i].UserAccountStatusId
#Html.DropDownList("UserAccountStatusId", new SelectList(Enum.GetValues(typeof(UserAccountStatus)).Cast<UserAccountStatus>().Select
(v => new SelectListItem
{
Text = v.GetDescription(),
Value = (Convert.ToInt32(v)).ToString()
}), "Value", "Text", Model.UserList[i].UserAccountStatusId))
#Html.Hidden("UserId", Model.UserList[i].UserId)
The pagehandler is just:-
public void OnPostSetUserStatuses(int[] UserId, int[] UserAccountStatusId)
{
for (var i = 0; i < UserId.Length; i++)
{
userService.SetUserStatus(UserId[i], UserAccountStatusId[i]);
}
var userList = userService.GetUsers();
UserList = userList;
return Page();
}
The model is defined as such:-
public List<User> UserList { get; set; }
[BindProperty]
public int[] UserAccountStatusId { get; set; }
[BindProperty]
public int[] UserId { get; set; }
So when I post the submit, the page handler is fired, and the db is updated and the new values are returned to the model within UserList.
Additionally, the line which says:-
#Model.UserList[i].UserAccountStatusId
is shown with the correct value.
However, the dropdownlist is not refreshed with the correct selected value. I think the problem is that I have multiple dropdowns and there seems to be some binding issue going on which I cannot for the life of me fathom out.
Can someone help please.
try to use DropDownListFor
#Html.DropDownListFor(model=> model.UserAccountStatusId[i],
...., #Model.UserAccountStatusId[i])
````
I am using MVC5, Razor, Entity Framework, C#. I am trying to pass a value of a dorpdown list using a link.
my model is
public class TestVM
{
public string TheID { get; set; }
}
I am loading an enum into a IEnumerable<SelectListItem>.
My enum is
public enum DiscountENUM
{
SaleCustomer,
SaleCustomerCategory,
SaleProduct,
SaleProductCategory,
SaleCustomerAndProduct,
SaleCustomerAndProductCategory,
SaleCustomerCategoryAndProductCategory,
PurchaseVendor,
PurchaseVendorAndProduct,
PurchaseVendorAndProductCategory,
PurchaseProduct,
PurchaseProductCategory,
Unknown
}
I am using the index method of the home controller
public ActionResult Index()
{
ViewBag.ListOfDiscounts = SelectListDiscountENUM();
TestVM d = new TestVM();
return View(d);
}
Where I load the ListOfDiscounts using:
private IEnumerable<SelectListItem> SelectListDiscountENUM()
{
List<SelectListItem> selectList = new List<SelectListItem>();
var listOfEnumValues = Enum.GetValues(typeof(DiscountENUM));
if (listOfEnumValues != null)
if (listOfEnumValues.Length > 0)
{
foreach (var item in listOfEnumValues)
{
SelectListItem sVM = new SelectListItem();
sVM.Value = item.ToString();
sVM.Text = Enum.GetName(typeof(DiscountENUM), item).ToString();
selectList.Add(sVM);
}
}
return selectList.OrderBy(x => x.Text).AsEnumerable();
}
My create method which is called from the view is
public ActionResult Create(TestVM d, string TheID)
{
return View();
}
My Index view is
#model ModelsClassLibrary.Models.DiscountNS.TestVM
<div>#Html.ActionLink("Create New", "Create", new { TheID = Model.TheID})</div>
<div>
#Html.DropDownListFor(x => x.TheID, #ViewBag.ListOfDiscounts as IEnumerable<SelectListItem>, "--- Select Discount Type ---", new { #class = "form-control" })
</div>
The problem is in the following line in the View
<div>#Html.ActionLink("Create New", "Create", new { TheID = Model.TheID })</div>
I have tried adding a model with the name of the field as "TheID"... no luck. Also, added a string field in the parameter, no luck. I looked at the FormControl object, and there was nothing in it either! I suspect something has to be added at the Route level in the helper, but I don't know what.
Model.TheID is always null. Even when I select an item in the DropDownListFor.
Does anyone have an idea how I can capture the select value of the DropDownListFor and send it into the Html.ActionLink TheID?
I am bit to new asp.net mvc and using aps.net mvc 5. I have create the below dropdown using html helpers in aps.net mvc. When i submit(post back) the form i want to set the selected index to zero. Here i am using a optionLabel "--select--". I want to set the selected value to that one ("--select--") after post back. How to achieve this. Please help. Thank you.
#Html.DropDownListFor(model => model.TestCategory, new SelectList(#ViewBag.TestCategories, "value", "text"), "-- Select --", new { #class = "form-control input-sm"})
Controller Code
[HttpGet]
public ActionResult Index()
{
var model = new LaboratoryViewModel {
medicaltestlist = new List<MedicalTest>()
};
PopTestCategory();
PopEmptyDropdown();
return View(model);
}
[HttpPost]
public ActionResult Index(LaboratoryViewModel labvm)
{
var test = PopMedicalTests().Where(x => x.TestSerial == Convert.ToInt32(labvm.TestCode)).FirstOrDefault();
if (labvm.medicaltestlist == null)
labvm.medicaltestlist = new List<MedicalTest>();
if(!labvm.medicaltestlist.Any(x=> x.TestSerial == test.TestSerial))
labvm.medicaltestlist.Add(test);
labvm.TestCategory = "";
PopTestCategory();
return View(labvm);
}
public void PopTestCategory()
{
var categorylist = new List<DropDownItem>
{
new DropDownItem{value="Medical",text="Medical"},
new DropDownItem{value="Animal",text="Animal"},
new DropDownItem{value="Food",text="Food"},
new DropDownItem{value="Water",text="Water"}
};
ViewBag.TestCategories = categorylist;
}
public class DropDownItem
{
public int id { get; set; }
public string value { get; set; }
public string text { get; set; }
}
You return the view in you post method so if you selected (say) Animal then that value will be selected when you return the view because the html helpers use the values from ModelState, not the model property. Setting labvm.TestCategory = ""; has no effect. The correct approach is to follow the PRG pattern and redirect to the GET method, however you can make this work by calling ModelState.Clear(); before setting resetting the value of TestCategory although this will clear all ModelState properties and errors and may have other side effects.
Side note: You DropDownItem class seems unnecessary. MVC already has a SelectListItem class designed to work with dropdownlists, and in any case you can replace all the code in your PopEmptyDropdown() method with
ViewBag.TestCategories = new SelectList(new List<string>() { "Medical", "Animal", "Food", "Water" });
and in the view
#Html.DropDownListFor(m => m.TestCategory, (SelectList)#ViewBag.TestCategories, "-- Select --", new { #class = "form-control input-sm"})
If you set the "value" attribute of the top item in the drop down list to something and then pass back a model containing that for the bound property it should work?
This question already has answers here:
NullReferenceException after POST
(2 answers)
Closed 9 years ago.
I've got a form that has a dropDownlist using the Model to fill the list, the view is rendered. The issue is that when i press the submit button, a null pointer exception for Model is thrown. I want to receive the value selected in the Post Action.
Here is my code:
Model:
public class BillViewModel
{
public List<SelectListItem> ClientList { get; set; }
public int SelectedClient { get; set; }
}
Controller:
public ActionResult Index()
{
var billRepo = new BillRepo();
var bill = new BillViewModel {ListProducts = billRepo.GetAllProducts()};
bill.ClientList = new List<SelectListItem>();
List<Client> allClientList = billRepo.GetAllClients();
foreach (Client client in allClientList)
{
var item = new SelectListItem() { Value = client.ClientId.ToString(), Text = client.Name };
bill.ClientList.Add(item);
}
ViewBag.ClientSelect = new SelectList(billRepo.GetAllClients(), "value", "text", bill.SelectedClient);
bill.SelectedClient = 1;
return View(bill);
}
[HttpPost]
public ActionResult Index(BillViewModel billViewModel)
{
return View();
}
View: The Model
#using (Html.BeginForm())
{
#Html.DropDownListFor(item => item.SelectedClient, Model.ClientList, "Select Client")
<input type="submit" value="Aceptar"/>
}
In your POST action you are returning the same Index view as in your GET action. But you are not passing any model to this view. That's why you are getting a NRE. Your view must render a dropdown and you need to populate its values, the same way you did in your GET action:
[HttpPost]
public ActionResult Index(BillViewModel billViewModel)
{
bill.ClientList = billRepo
.GetAllClients()
.ToList()
.Select(x => new SelectListItem
{
Value = client.ClientId.ToString(),
Text = client.Name
})
.ToList();
return View(billViewModel);
}
Notice how the view model is passed to the view and how the ClientList property (to which your dropdown is bound) is filed with values.
In my VIew ive got 3 checkboxes, i want to validate them that way :
if one checkbox is checked other 2 is disabled, if checked checkbox is unchecked, other 2 checkboes is enabled again.
Controller
public ActionResult SolarPart()
{
var model = new SolarParentViewModel();
var list = new List<URLTimeLimitViewModel>();
list.Add(new URLTimeLimitViewModel { Name = "14 dage", IsChecked = false, Id = 1 });
list.Add(new URLTimeLimitViewModel { Name = "1 Måned", IsChecked = false, Id = 2 });
list.Add(new URLTimeLimitViewModel { Name = "2 Måneder", IsChecked = false, Id = 3 });
model.TimeLimit = list;
return View(model);
}
View
#for (var i = 0; i < Model.TimeLimit.Count; i++)
{
<div class="editor-label">
#Html.LabelFor(c=>Model.TimeLimit[i].Name, Model.TimeLimit[i].Name)
#Html.HiddenFor(c=>Model.TimeLimit[i].Id)
#Html.CheckBoxFor(c=>Model.TimeLimit[i].IsChecked)
</div>
}
If the values are mutually exclusive you might consider using radio buttons instead of checkboxes. They seem more adapted to your scenario. If for some very weird reason you still want to use checkboxes which are mutually exclusives you could use javascript and subscribe to the change event of each of them and based on the value toggle the other 2 checkboxes. And for validating this model on the server you could write a custom validation attribute which could either be applied to the TimeLimit property on your view model:
public class MaximumOneTimeLimitCanBeCheckedAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var list = value as IEnumerable<URLTimeLimitViewModel>;
if (list == null)
{
return true;
}
return list.Where(x => x.IsChecked).Count() < 2;
}
}
and then:
public class SolarParentViewModel
{
[MaximumOneTimeLimitCanBeChecked]
public IList<URLTimeLimitViewModel> TimeLimit { get; set; }
}