What about if I have a navigation property? I have a collection of another model in the model I'm trying to edit. I want to display a list of checkboxes for each object in the collection property. So far, this is what I tried...
#{
foreach (var category in ViewBag.Categories)
{
if (Model.Categories.Contains(category))
{
<input type="checkbox" name="selected-categories" value="category.CategoryId" checked="checked" />#category.Name
}
else
{
<input type="checkbox" name="selected-categories" value="#category.CategoryId" />#category.Name
}
}
}
But it fails with an EntityCommandExecutionException. In my if statement, how can I access the model.Categories like I do in something like #Html.EditorFor(model => model.Id)???
Using a strongly-typed View is the way I'd go. Create a ViewModel that contains your Model and use this ViewModel for the strongly-typed View.
Domain Model and ViewModel (simplified)
public class YourModel
{
string Category { get ; set ; }
}
public class YourViewModel
{
public List<string> PossibleCategories { get ; set ; }
public YourModel YourData { get ; set ; }
}
Then the View:
#model YourViewModel
#{
foreach (string CurrCategory in Model.PossibleCategories)
{
if (Model.YourData.Category == CurrCategory)
{
#Html.CheckBox(CurrCategory, new { #checked = "checked" })
#Html.Encode(CurrCategory) <br />
}
else
{
#Html.CheckBox(CurrCategory, false)
#Html.Encode(CurrCategory) <br />
}
}
}
Related
I want to bind a CheckBoxList on Razor page with Viewdata.I have the following code on my Controller Index:
public async Task<IActionResult> Index()
{
var testSections = new List<ReassignmentSectionLookup>();
testSections = await _employeeService.GetTestSections();
ViewData["testSections"] = new SelectList(testSections, "ReassignmentSectionLookupID", "Section");
return View();
}
I have the following on my razor page:
<div class="form-group row">
<div class="col-md-12">
Select Test: <br />
#{
var select = ViewData["testSections"] as SelectList;
if (select != null && select.ToList().Count > 0)
{
foreach (var item in select.ToList())
{
<input type="checkbox" name="selectedItems" value="#item.Value" #(Html.Raw(item.Selected ? "checked=\"checked\"" : "")) /> #item.Text <br />
}
}
}
</div>
When I hover over select, I can see 12 items in the select:
when I hover over select.ToList().Count I get an error saying "system.NullReferenceException". I am not sure what am I doing wrong. I can see the data in the select. below is the screen shot of the error:
If I try doing select.Items.ToList() then I am getting this error:
When using this line:
<input type="checkbox" name="selectedItems" value="#item.Value" #(Html.Raw(item.Selected ? "checked=\"checked\"" : "")) /> #item.Text <br />
I am getting this error:
As discussed in the comment, I would say SelectList is not suitable as it is used to render the items for the select/drop-down list with either tag helper (asp-items) or HtmlHelper (#Html.DropdownList()).
Create a model class for the data set.
public class ReassignmentSectionLookupOption
{
public string Text { get; set; }
public int Value { get; set; }
public bool Selected { get; set; }
}
Instead of returning the data as SelectList in ViewData, return as List<ReassignmentSectionLookupOption> type.
public async Task<IActionResult> Index()
{
var testSections = await _employeeService.GetTestSections();
ViewData["testSections"] = testSections
.Select(x => new ReassignmentSectionLookupOption { Value = x.ReassignmentSectionLookupID, Text = x.Section })
.ToList();
return View();
}
In View, cast the ViewData as List<ReassignmentSectionLookupOption>. And you can use .Any() to check whether the list is not empty.
#{
var testSections = ViewData["testSections"] as List<ReassignmentSectionLookupOption>;
if (testSections != null && testSections.Any())
{
foreach (var item in testSections)
{
<input type="checkbox" name="selectedItems" value="#item.Value" #(Html.Raw(item.Selected ? "checked=\"checked\"" : "")) /> #item.Text <br />
}
}
}
I'm quite new to MVC4 and my past experience with asp.net has been working with Webforms, so I think I need some careful hand holding. Thank you in advance!
I'm creating a simple web application that is similar to choosing lotto numbers. At the moment the user chooses whichever numbers they want (no limit, starting with "1") and posts it to the next page where I continue to do something with the numbers the user has chosen. The number of numbers - to choose from is dynamic and represented by checkboxes.
The problem is that when the view is submitted, I get the error page "Collection is read-only."
Model - LottoNumbers.cs
bool[] numbers is to store which numbers the user has checked.
namespace Lotto.Models
{
public class LottoNumbers
{
public string name { get; set; }
public bool[] numbers { get; set; }
public LottoNumbers() {
numbers = new bool[49];
}
public LottoNumbers(int limit)
{
numbers = new bool[limit];
}
}
}
in the HomeController.cs ...
I declared LottoNumbers ln, and
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Lotto.Models;
namespace Lotto.Controllers
{
public class HomeController : Controller
{
public LottoNumbers ln;
public ActionResult Index()
{
return View();
}
public ActionResult SetNumbers()
{
ln = new LottoNumbers(30);
// 30 checkboxes will be generated on the view
return View(ln);
}
[HttpPost]
public ViewResult SetNumbers(LottoNumbers l)
{
// get checkbox values
return View();
}
}
}
View - SetNumbers.cshtml
#model Lotto.Models.LottoNumbers
#{
ViewBag.Title = "SetNumbers";
}
<h2>SetNumbers</h2>
#using (Html.BeginForm()) {
<div>
#Html.LabelFor(m => m.name)
#Html.EditorFor(m => m.name)
</div>
<div id="numberlist">
#for (int i = 0; i < Model.numbers.Length; i++)
{
<div class="item-number" style="">
#Html.EditorFor(m => m.numbers[i])
<label class="lbl-number" for="numbers[#i]">#(i+1)</label>
</div>
}
</div>
<div style="clear:both;">
<input type="reset" value="Reset everything" />
<input type="submit" value="Submit" />
</div>
}
Change your array to a list.
public string Name { get; set; }
public List<bool> Numbers { get; set; }
public LottoNumbers()
{
Numbers = new List<bool>();
for (var i = 0; i < 40; i++)
{
Numbers.Add(false);
}
}
public LottoNumbers(int limit)
{
Numbers = new List<bool>();
for (var i = 0; i < limit; i ++)
{
Numbers.Add(false);
}
}
then your view would be
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(m => m.Name)
#Html.EditorFor(m => m.Name)
</div>
<div id="numberlist">
#for (var i = 0; i < Model.Numbers.Count; i++)
{
<div class="item-number" style="">
#Html.CheckBox("Numbers[" + i.ToString() + "]")
<label class="lbl-number" for="numbers[#(i + 1)]">#(i + 1)</label>
</div>
}
</div>
<div style="clear:both;">
<input type="reset" value="Reset everything" />
<input type="submit" value="Submit" />
</div>
}
Null Reference Exception
You are most likely getting the null reference exception after submitting because you are not passing a model back to the view. You set up your page to expect a LottoNumbers model.
[HttpPost]
public ViewResult SetNumbers(LottoNumbers l)
{
// get checkbox values
return View(); //Nothing being passed to the view
}
so after post when your code gets to here
#foreach (var numberBool in Model.Numbers)
it blows up as the Model is null. You can fix this in several ways depending on how you want your app to run. If you want the user to see a blank list of numbers after submit simply do this and add some sort of success message on post back
[HttpPost]
public ViewResult SetNumbers(LottoNumbers l)
{
//process stuff
ln = new LottoNumbers(30);
return View(ln);
}
you can also simply redirect the user back to the index page. Either way the error is caused because you are not passing in a model with numbers back to the view.
If you look at the stack trace of your "Collection is read only" error, you can see that it's happening during Model Binding, which is when it tried to recreate your LottoNumbers object from the data passed back from the form post.
You're getting the error because you're initialising your object with an array size using you're constructor that takes a parameter, but when it posts back the data, it doesn't know what size the array needs to be when it recreates the object.
To solve this, it would be better to use a List instead, and have the size as a property of your LottoNumbers object.
Using a List means you don't have to pre-initialise the array with a size. And having the size as a property means you can embed the size in a hidden input variable, so that it can be passed back to the controller, and it should correctly do the model binding.
I develop an application who manages formations of employees, I use MVC4 Asp.net with Razor.
In my model I have tow class (who are table in my database) formation and formateur (trainers).
In my application i can create a “formation” and I want to add a list of “formative”(trainers) but I don’t know what I must do.
I think the best solution it’s a list of checkbox, I succeeded to display my list of checkbox with a foreach but I have no idea how I get the result of selected checkbox to pass into my controller.
I saw many tutorials where use “CheckBoxList” and I tried to use too, but I use a ViewBag to populate it and they don't explain how to use it with a viewbag.
Now I test a Dual listBox with tow buttons (Add and Remove) but this doesn't work.
So, somebody can help me to find, and explain how I must do, the good or the best solution ?
I'm sorry for my english, I'm a french girl.
One of my solutions look like this :
My controller :
public ActionResult Create()
{
ViewBag.formateurListe = (from unFormateur in db.salarie
where unFormateur.sFormateur == true
select unFormateur).AsEnumerable()
.Select(m => new SelectListItem
{
Text = m.sNom.ToString() + " " + m.sPrenom.ToString(),
Value = m.sId.ToString()
}).ToList();
return View();
}
[HttpPost]
public ActionResult Create(formation formation, IEnumerable<SelectList> formateurList)
{
if (ModelState.IsValid)
{
db.formation.Add(formation);
foreach (var unSal in formateurList)
{
formateur f = new formateur();
f.ftIdFormation = formation.fId;
f.ftIdSalarie = (int)unSal.SelectedValue;
db.formateur.Add(f);
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(formation);
}
In my view :
#model MvcAppGestionRH.Models.formation
#using (Html.BeginForm("Create", "Formation", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
#Html.Label("Libelle")
#Html.EditorFor(model => model.fLibelle)
#Html.ValidationMessageFor(model => model.fLibelle)
<label id="fFormateur">Formateur</label>
#Html.ListBox("formateurListe", ViewData["formateurListe"] as SelectListItem[], new {Multiple = "multiple"})
<input type="button" value="+" name="add" />
<select name="select" size="7" >
</select>
<input type="submit" value="Créer" />
}
With a script :
$(function () {
$("#Add").click(function () {
$("select").add($('fFormateurListe').selected);
});
});
Checkboxes can be tricky the first time - I googled that a long time, too.
My solution is a view model which looks like this:
It is intended for questions, where the crator can speciy items via checkboxes (e.g. a questions might have the answer "GOOD" and "BAD".
public class QuestionModel
{
public int QuestionID { get; set; }
public string QuestionText { get; set; }
/// <summary>
/// Gets or sets the selected items. Purely a helper List to display check boxes for the user
/// </summary>
/// <value>
/// The selected items.
/// </value>
[Display(Name = "Items", ResourceType = typeof(Domain.Resources.Question))]
public IEnumerable<SelectListItem> SelectedItems { get; set; }
/// <summary>
/// Gets or sets the selected ids. Populated by the user, when he checks / unchecks items. Later translated into QuestionItems
/// </summary>
/// <value>
/// The selected ids.
/// </value>
public int[] SelectedIds { get; set; }
}
This is populated like this in the QuestionController:
private async Task GetSelectedItems(QuestionModel sm, Item selectedItems)
{
var alreadySelected = new List<Scale>();
if (selectedScale != null)
{
alreadySelected.Add(selectedScale);
}
var itemList = (await this.uoW.ItemRepository.Get()).OrderBy(i => i.Name);
sm.SelectedItems = itemList.Select(x => new SelectListItem
{
Value = x.ScaleID.ToString(),
Text = x.NameOfScale.GetText(),
Selected = (from a in alreadySelected where a.ItemID == x.ItemID select x).Any()
});
}
What does this do? It gets a list of all avialable items in the database and populates the model with it. Furthermore, you can pass in a list of items, which are already selected - so you can edit an existing question and siplay all already checked Items.
And n the view I have used a DropDownList:
<div class="form-group">
#Html.LabelFor(model => model.SelectedItems, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.DropDownListFor(x => x.SelectedIds, Model.SelectedItems, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SelectedItems, "", new { #class = "text-danger" })
</div>
</div>
</div>
If you want checkboxes, that looks like this(different controller, so dont be confused)
for (int i = 0; i < Model.SelectedItems.Count(); i++)
{
var currentElem = Model.SelectedItems[i];
//if this item is selected by the user, e.g. because he is editing the item, the item will be pre-selected
var selected = currentElem.Selected ? "checked=\"selected\"" : string.Empty;
// column for the questions. expected layout: list of all questions
<div class="col-md-6">
<div class="checkbox" id="SelectedIds">
<label>
<input type="checkbox" value="#currentElem.Value" #selected name="SelectedIds">
#Html.Encode(currentElem.Text)
</label>
</div>
</div>
}
and finally the create() method itself:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "QuestionText,SelectedItems, SelectedIds")] QuestionModel question)
{
if (ModelState.IsValid)
{
// I need only one Item, but if you want ore more change this line
if (question.SelectedIds.Count() == 1)
{
// better use Automapper here, this is unnecessary work
var newQuestion = new Question { QuestionText = question.QuestionText};
var ItemID = question.SelectedIds.First();
newQuestion.QuestionScale = await this.uoW.ItemRepository.GetRaw().Where(i => i.ItemID == ItemD).FirstAsync();
this.uoW.QuestionRepository.Insert(newQuestion);
await this.uoW.Save();
return this.RedirectToAction("Index");
}
else
{
this.logger.Warn("User {0} tried to insert more than one Itemin question {1}", User.Identity.Name, question.QuestionID);
ModelState.AddModelError(string.Empty, xyz.Areas.QuestionManagement.Resources.QuestionRes.ErrorTooManyScales);
}
}
else
{
// the SelectedItems are empty in the model - so if you have to redisplay it, repopulate it.
await this.GetSelectedItems(question, null);
}
return this.View(question);
}
Have you tried using a viewmodel to pass your two model in the view?
For example :
ViewModel
public class CreateFormationViewModel
{
public Formation formation{ get; set; }
public List<Formative> trainers {get;set;}
}
and then use this viewmodel in your view
An easy way to use this view model :
In your controller
public ActionResult CreateFormation()
{
//Get your data (formation and trainer)
CreateFormationViewModel createFormationVM = new CreateFormationViewModel();
createFormationVM.formation = YourFormationModel;
createFormationVM.trainers = YourtrainersModelasList;
//bind data to the view
return View(createFormationVM);
}
And in your View, you have :
#model [yournamespace].CreateFormationViewModel
My model class:
public class StatusList
{
public int StatusID {get;set;}
[UIHint("ByteCheckbox")]
public byte Active {get;set;}
}
In /Views/Shared/EditorTemplates I created a file called ByteCheckbox.cshtml
The editortemplate ByteCheckbox contains (My 3rd attempt):
#model byte
#if (Model == 1)
{
#Html.CheckBox("", true)
}
else
{
#Html.CheckBox("", false)
}
Doing this nicely renders a checkbox. When I change the checkbox status and try to save the changes the model validation complains that the value is 'false' (or 'true') instead of the expected 0 or 1.
How to modify the editortemplate to allow for the value to be translated?
Have you tried this?
<div class="editor-label">
#Html.LabelFor(model => model.Active)
</div>
<div class="editor-field">
#Html.CheckBoxFor(model => model.Active != 0)
#Html.ValidationMessageFor(model => model.Active)
</div>
You can do this in your model:
public class StatusList
{
public int StatusID {get;set;}
public byte Active {get;set;}
[NotMapped]
public bool ActiveBool
{
get { return Active > 0; }
set { Active = value ? 1 : 0; }
}
}
Don't use Html.CheckBox; instead use Html.EditorFor. You'll need to define a file called ByteCheckbox.cshtml in Views/Shared/EditorTemplates for this to work as well.
You can also use a custom model binder.
Here's a sample for a decimal, but you can do it for the byte type.
public class DecimalModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
dynamic valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == null) {
return base.BindModel(controllerContext, bindingContext);
}
return ((string)valueProviderResult.AttemptedValue).TryCDec();
}
}
try this one
#model bool
<div class="editor-for">
#Html.CheckBox("", Model, new{#class="tickbox-single-line"})
<div>
Try this:
#Html.CheckBox("Model", "Model")
How can i hide the attribute:
[Display(Name = "dspName")]
alternatively the variable name from my variable in the (razor) view?
My problem is that I have defined a custom template for booleans that views the boolean like:
varname/displayName: 'box'
If I create the view with:
#Html.EditorForModel(Model)
the Result is:
varname/displayName
varname/displayName: 'box'
Result in Browser:
edit: my BooleanTemplate
#model System.Boolean?
#{
string name = string.Empty;
if (!string.IsNullOrWhiteSpace(ViewData.ModelMetadata.DisplayName))
{
name =ViewData.ModelMetadata.DisplayName;
}
else
{
name = ViewData.ModelMetadata.PropertyName;
}
}
#name:
#Html.CheckBox("", Model.HasValue ? Model : Model.Value)
The additional label you are seeing is baked into the default editor template for the Object class. So you have two possibilities:
Use #Html.EditorFor(x => x.SomeBoolProperty) and so on for each property instead of #Html.EditorForModel()
Modify the default editor template of the object class (EditorTemplates/Object.cshtml) to remove the label (notice the part I have put in comments):
#if (ViewData.TemplateInfo.TemplateDepth > 1)
{
#ViewData.ModelMetadata.SimpleDisplayText
}
else
{
foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
#Html.Editor(prop.PropertyName)
}
else
{
#*if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString()))
{
<div class="editor-label">#Html.Label(prop.PropertyName)</div>
}*#
<div class="editor-field">
#Html.Editor(prop.PropertyName)
#Html.ValidationMessage(prop.PropertyName, "*")
</div>
}
}
}