ASP.NET MVC - CheckBox List ID, Name, and Value - asp.net-mvc

I'm working on an ASP.NET MVC app. In this app, I'm am working to create a checkbox list on top of a custom object. The custom object looks like this:
public class ListItem<TId, TLabel, TIsChecked>
{
private TId id;
private TLabel label;
private TIsChecked isChecked;
public ListItem(TId id, TLabel label, TIsChecked isChecked)
{
this.id = id;
this.label = label;
this.isChecked = isChecked;
}
public TId Id
{
get { return id; }
}
public TLabel Label
{
get { return label; }
}
public TIsChecked IsChecked
{
get { return isChecked; }
}
}
I have a model that looks like this:
public class MyModel
{
public IEnumerable<ListItem<Guid, string, bool>> Options { get; set; }
}
I then have a controller, with an action called Profile, that looks like this:
[HttpGet]
public ActionResult Profile()
{
var model = new MyModel();
return View(model);
}
[HttpPost]
public ActionResult Profile(MyModel model)
{
return View(model);
}
I am rendering my checkbox options in my Razor view like this:
#foreach (var option in Model.Options)
{
<div class="form-group">
<div class="checkbox">
<label>
<input name="Options" value="#option.Id" type="checkbox" #((option.IsChecked == true) ? "checked" : string.Empty)> #option.Label
</label>
</div>
</div>
}
The options render fine. But, when I save the form, it looks like only the first selected checkbox ID is returned. I'm not sure how to a) properly render my HTML for a checkbox list and b) get the values to properly post back to the server. I've seen other examples in blog posts, but, my view will have significantly more HTML. For that reason, I'm trying to figure out "how" checkbox values are rendered on the client-side and posted back to the server in ASP.NET MVC.

First you have to do change in your model to use List<T> instead of IEnumerable<T>:
public List<ListItem<Guid, string, bool>> Options { get; set; }
Secondly, you do not have setters for your properties, so data would not be posted back, add setters:
public TId Id
{
get { return id; }
set { id = value;}
}
public TLabel Label
{
get { return label; }
set { label = value;}
}
public TIsChecked IsChecked
{
get { return isChecked; }
set { isChecked = value;}
}
and at last you simply need to use strongly typed html helper and use for loop so that indexing can bind data for post:
#for(int i=0; i< Model.Options.Count; i++)
{
<div class="form-group">
<div class="checkbox">
<label>
#Html.CheckBoxFor(model => model.Options[i].IsChecked) #Model.Options[i].Label
</label>
#Html.HiddenFor(model=> model.Options[i].Id)
#Html.HiddenFor(model=> model.Options[i].Label)
</div>
</div>
}
This will create check-boxes with correct names so that they can be binded on post.
You might want to have a look on How List binding works

Related

ASP.NET MVC - Checkbox list not returning all comma separated values

I have the following checkbox list:
<input type="checkbox" name="Categories" value="1" />
<input type="checkbox" name="Categories" value="2" />
My model is as follows:
public class MyModel
{
public string Categories { get; set; }
}
My controller:
public ActionResult Index(MyModel model)
{
if (ModelState.IsValid)
{
// Save data to database, and redirect to Success page.
return RedirectToAction("Success");
}
}
Selecting both checkboxes saves only one value?
You can't get comma separated value directly to server, I suggest to change class as below
public class MyModel
{
public List<string> Categories { get; set; }
}
You get list of values which checkbox selected.
If you want comma separated value then on client side need when submit form create function and need to save on hidden variable.
May this help you
Thanks
The main problem is string property stores single string, you should use collection property to bind with checkboxes e.g. List<string> as #Ajay mentioned before.
Therefore, you should use this setup:
Model
public class MyModel
{
public MyModel()
{
SelectedCategories = new List<string>();
// put categories here
Categories = new List<SelectListItem>() { ... };
}
// other properties
public List<string> SelectedCategories { get; set; }
public List<SelectListItem> Categories { get; set; }
}
View
#foreach (var item in Model.Categories)
{
<input type="checkbox" name="SelectedCategories" value="#item.Value" ... />
}
Controller
[HttpPost]
public ActionResult Index(MyModel model)
{
if (ModelState.IsValid)
{
// create comma-separated values
var selectedCategories = string.Join(",", model.SelectedCategories);
// Save data to database, and redirect to Success page.
return RedirectToAction("Success");
}
}
If you want to use CheckBoxFor helper, use SelectListItem which has Selected property with bool type, because CheckBoxFor binds for bool property:
Model
public class MyModel
{
public MyModel()
{
// put categories here
Categories = new List<SelectListItem>() { ... };
}
// other properties
public List<SelectListItem> Categories { get; set; }
}
View
#for (var i = 0; i < Model.Categories.Count; i++)
{
#Html.CheckBoxFor(model => model.Categories[i].Selected)
#Html.HiddenFor(model => model.Categories[i].Text)
#Html.HiddenFor(model => model.Categories[i].Value)
}
Controller
[HttpPost]
public ActionResult Index(MyModel model)
{
if (ModelState.IsValid)
{
string selectedCategories = string.Join(",",
model.Categories.Where(x => x.Selected == true)
.Select(x => x.Text).ToList());
// Save data to database, and redirect to Success page.
return RedirectToAction("Success");
}
}
Note:
There is a custom helper named CheckBoxListFor which should be considered to create checkbox list from List<T> property.
An example of the checkbox list implementation can be seen here.
Related problems:
Get comma-separated string from CheckboxList HTML Helper
Get Multiple Selected checkboxes in MVC

Null object from view in ASP.NET MVC Form

I'm building a simple inventory application where the user create an order by selecting the items he wants to be delivered, fill a form with the name of the recipient and then the order get processed.
First of all I have an OrdineScarico class that stores a collection of InventoryItems to be processed (like a cart), a DeliverDetails class that stores the recipient name
public class OrdineScarico
{
private List<SingoloOrdine> ordineCollection = new List<SingoloOrdine>();
// collection methods
}
public class SingoloOrdine
{
public InventoryItem InventoryItem { get; set; }
public int Qty { get; set; }
}
public class DeliverDetails
{
[Required(ErrorMessage = "Inserire il nome del ricevente")]
public string Nome { get; set; }
}
and then a ConfermaScaricoViewModel class -in a different namespace- for wrapping them up
public class ConfermaScaricoViewModel
{
public OrdineScarico OrdineScarico { get; set; }
public DeliverDetails DeliverDetails { get; set; }
}
I have these action methods in the ScaricoController
public ViewResult Conferma()
{
return View(
new ConfermaScaricoViewModel
{
OrdineScarico = GetScarico(),
DeliverDetails = new DeliverDetails()
});
}
[HttpPost]
public ViewResult Conferma(ConfermaScaricoViewModel viewModel)
{
if (ModelState.IsValid)
{
repositoryProcessor.ScaricaItem(viewModel.OrdineScarico, viewModel.DeliverDetails);
viewModel.OrdineScarico.Clear();
return View("Confermato");
}
else
{
return View(
new ConfermaScaricoViewModel
{
OrdineScarico = GetScarico(),
DeliverDetails = new DeliverDetails()
});
}
}
where GetScarico() reads the OrdineScarico instance from the active session
private OrdineScarico GetScarico()
{
OrdineScarico scarico = (OrdineScarico)Session["Scarico"];
if (scarico == null)
{
scarico = new OrdineScarico();
Session["Scarico"] = scarico;
}
return scarico;
}
This is the view code:
#model GestioneMagazzino.WebUI.Models.ConfermaScaricoViewModel
#{
ViewBag.Title = "Conferma";
}
<h2>Conferma scarico</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary()
<div class="form-group col-md-12">
<div class="row">
<label class="text-left">Ricevente:</label>
</div>
<div class="row">
#Html.TextBoxFor(model => model.DeliverDetails.Nome, new { #class="col-md-7" })
</div>
</div>
<div>
<input class="btn btn-primary" type="submit" value="Conferma" />
</div>
}
The problem is that when the POST action method is called, I get a null value for the OrdineScarico argument, and the ModelState is always false. I also tried adding an hidden field
#Html.HiddenFor(model => model.OrdineScarico)
but the OrdineScarico argument is always null when the POST method is called, while it's not when the controller renders the view.
Thanks,
Davide.
you must use #Html.HiddenFor(model => model.OrdineScarico)
also may be DeliverDetails has other field that you must set value for it.
you can use break point on line :
if (ModelState.IsValid)
in method post of Conferma.
run code and when code arrived to break point,
move cursor to the ModelState and wait to see data in it.
on values check all entities and find which property has error and you can see the message of error.
edit: cause you have not send any data for OrdineScarico. so it will be null in post action.
the OrdineScarico has a list member so
you should add this.
<input hidden name="OrdineScarico.ordineCollection[]" value="#value">

Problems to send data back to the controller. Parameter is always null MVC

I have problems to send back data from view to controller. I'm quite new to MVC and I can't figure it out what is the problem.
This is the view:
#model IEnumerable<OnlineCarStore.Models.CategoriesVM>
<div class="container">
<div class="row">
#using (Html.BeginForm("SubCategory", "Product"))
{
<div class="list-group col-sm-3" style="width:280px;">
#{var selected = string.Empty;
if (#HttpContext.Current.Session["selectedCar"] == null)
{
selected = string.Empty;
}
else
{
selected = #HttpContext.Current.Session["selectedCar"].ToString();
}
foreach (var c in Model)
{
<a href="#Url.Action("SubCategory", "Product", new { selected = selected, id = #c.ID, category = #c.CategoryName })" id="link" class="list-group-item">
<span> #c.CategoryName</span>
</a>
for (int i = 0; i < c.Childrens.Count; i++)
{
#Html.HiddenFor(x => c.Childrens[i].Item)
#Html.HiddenFor(x => c.Childrens[i].Children)
}
}
}
This is the view model for the data that I need:
public class CategoriesVM
{
public int ID { get; set; }
public int AtpID { get; set; }
public string CategoryName { get; set; }
public List<Helpers.TreeItem<Categories>> Childrens { get; set; }
}
This is in the controller:
public ActionResult SubCategory(IEnumerable<OnlineCarStore.Models.CategoriesVM> Model)
{
. In the Model parameter I need all the data that the CategoriesVM contains, but the Model parameter is always null.
Can you please advise what I'm doing wrong? What I'm missing?
Thanks!
You cannot (and should not) pass all the properties of your Category object via a link click! It works only when your object is lean-flat with very few properties. When your object is complex, this is not ideal!
You should simply pass the unique Id (category id) to the next action method and in which you use this id to query the needed data and use that.
I also noticed that you are setting the same id value to the link in the loop. It will produce multiple anchor tag with same Id value. That is invalid HTML. So let's remove that.
<a href="#Url.Action("SubCategory", "Product", new { id = #c.ID })"
class="list-group-item">
<span> #c.CategoryName</span>
</a>
Now in your SubCategory action method
public ActionResult SubCategory(int id)
{
//using the id, get the data needed (may be the category & subcategories)
// to do : Return something
}

Better way of creating repeating HTML section

I've asked this once before but without any code to look at, here I have an implementation and I'm wondering if there is a better way to accomplish this.
I want a repeating html section like so:
<div>
<input id=id1 name=id1 type=text/>
</div>
<div>
<input id=id2 name=id2 type=text/>
</div
etc
This could contain any number of input boxes which map to the List of 'something' classes I have in the model, I presently do this with a View
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Somethings.Count; i++)
{
Model.Index = i;
#Html.Action("Index", "HtmlSection", Model);
}
// other stuff
}
and a partial view
#{
int index = Model.Index;
}
#Html.TextBoxFor(x => x.Somethings[index].TheProperty)
Where the model looks like this
public class HtmlSectionModel
{
public List<Something> Somethings { get; set; }
public int Index { get; set; }
}
Finally the action looks like this
public ActionResult Index(HtmlSectionModel model)
{
// do stuff
}
To me this works but isn't ideal
The partial view can now only be used within this context, it uses the top level model rather than just the 'Something' class
I have to pass an index in the model in order to get unique name's for binding, if I didn't do this then textbox would have the same name/id
This seems to me to be a common pattern so others must have solved it in other ways?
I guess what I'm after here is the MVC equivalent of Asp.Net UserControls/Webcontrols (which seem to be child actions/partial views), but, combined with model binding which seems to require unique names
What I wanted can be accomplished with editor templates
Controller
public class UsesEditorController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View(new SomeModel());
}
[HttpPost]
public ActionResult Index(SomeModel model)
{
return View(model);
}
}
Model
public class Blob
{
public string Name { get; set; }
public string Address { get; set; }
public Blob()
{
Name = string.Empty;
Address = string.Empty;
}
}
public class SomeModel
{
public List<Blob> Blobs { get; set; }
public SomeModel()
{
int count = 5;
this.Blobs = new List<Blob>(count);
for (int i = 0; i < count; i++)
{
this.Blobs.Add(new Blob());
}
}
}
View
#model MyProject.Areas.EditorTemplates.Models.SomeModel
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Blobs.Count; i++)
{
#Html.EditorFor(m => m.Blobs[i], "CustomEditorForBlob");
}
<input type="submit" value="Send data back" />
}
And Editor, which can be anywhere in the view folder as I'm referring to it directly
#model MyProject.Areas.EditorTemplates.Models.Blob
#Html.TextBoxFor(m => m.Name)
#Html.TextBoxFor(m => m.Address)
This renders with ids like:
<input class="k-textbox" id="Blobs_1__Name" name="Blobs[1].Name" ...
So this gives me
List item
a repeating structure, just like UserControls in Asp.Net
The editor template only refers to the Blob class, it has no knowledge of the SomeModel class
Binding works (tested it)
It looks to me like what you are trying to accomplish is unique IDs for your inputs, and you certainly don't need a partial to do this. You can output your text box inside your for loop like the following:
#Html.TextBoxFor(x => x.Somethings[i].TheProperty)
This will generate a unique id something like id="Somethings_1_TheProperty". If you don't like that id, you can certainly make your own with something like this:
#Html.TextBoxFor(x => x.Somethings[i].TheProperty, new {id="id" + (i+1)})

Retrieving values from partial view during post method

I have a view which contains a dropdown list and on dropdownlist item being selected I load a partial view. And when the form is submitted I want to be able to get both the values from main view and partial view during form submit.
Here is the main view
#model AdminPortal.Areas.Hardware.Models.CreateModule
#{
ViewBag.Title = "Create Module";
Layout = "~/Views/shared/_BootstrapLayout.basic.cshtml";
}
#Html.ValidationSummary(true)
<fieldset class="form-horizontal">
<legend>Add a Module <small>Create</small></legend>
#using (Html.BeginForm("CreateModule", "Module", new{id="AddModuleForm"}))
{
#Html.ValidationSummary(true)
<div class ="controls">
<div class="input-block-level">#Html.TextBoxFor(model => model.ModuleId, new {#placeholder = "ModuleID"})</div>
<br/>
<div class ="input-block-level" id="selectedModuleTypeName">#Html.DropDownListFor(model => model.SelectedModuleTypeName, Model.TypeNames,"Select Moduletype", new{id = "ModuleList"})</div>
<br/>
<div id="partialDiv"></div>
</div>
<div class="form-actions" id="buttons">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
</fieldset>
<div>
#Html.ActionLink("Back to List", "ModuleList")
</div>
<script>
$("#buttons").hide();
$("#ModuleList").on("change", function() {
var modId = $(this).val();
$.get('#Url.Action("GetModulePropertyName", "Module")', { moduleTypeValue: modId }, function(result) {
$("#partialDiv").html(result);
});
//uncomment following section to check if the partial view is working properly
/*.done(function() { alert("done"); })
.fail(function() { alert("fail"); })
.always(function() { alert("completed"); });*/
});
$("#buttons").show();
</script>
and here is the partial view
#model IEnumerable<string>
#foreach(var names in Model)
{
<div class="input-block-level">#Html.TextBoxFor(m=>names, new{Value="", placeholder=names})</div>
<br/>
}
Here is my model
public class CreateModule
{
//Empty form to handle form serialization
public CreateModule()
{
}
[Required]
public string ModuleId { get; set; }
[DataType(DataType.DateTime)]
public DateTime DateEntered { get; set; }
[Required]
public string SelectedModuleTypeName { get; set; }
public IEnumerable<SelectListItem> TypeNames { get; set; }
public List<Property> Properties { get; set; }
}
public class Property
{
public string Name { get; set; }
public string Value { get; set; }
}
Here is the method that script in main view forwards to
[HttpGet]
public ActionResult GetModulePropertyName(string moduleTypeValue)
{
var moduleKindId = _repository.GetModuleKindId(moduleTypeValue);
var modulePropertyNames = _repository.GetModuleKindPropertyNames(moduleTypeValue);
return PartialView("GetModulePropertyName",modulePropertyNames);
}
and finally here is httppost method for the main view
[HttpPost]
public ActionResult CreateModule(CreateModule moduleV)
{
var module = new Module
{
ModuleTypeId = Convert.ToInt64(moduleV.SelectedModuleTypeName),
ModuleId = moduleV.ModuleId,
DateEntered = moduleV.DateEntered,
};
if (ModelState.IsValid)
{
_repository.AddModule(module);
Success("Module added successfully!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Something went wrong!");
return RedirectToAction("CreateModule", "Module", new { area = "Hardware" });
}
Current situation:
When the form is posted, the properties value of the model that is being passed via partial view is null. I get other values, like typename, Module ID.
What I'd want:
I also want to get the value of properties that is being passed via partial view.
You don't have any input field for the Properties property anywhere in your form. So it will always be null. That's normal.
Here's how you could proceed. Start by setting the correct navigational property so that the helper generates correct names of the corresponding input fields.
Also make sure that you are passing an IEnumerable<Property> model to the partial if you want to be able to get them back correctly:
[HttpGet]
public ActionResult GetModulePropertyName(string moduleTypeValue)
{
var moduleKindId = _repository.GetModuleKindId(moduleTypeValue);
IList<Property> model = ...
return PartialView("GetModulePropertyName", model.ToList());
}
and in your partial view use an editor template:
#model IList<Property>
#{
// This indicates the current navigational context to the helpers
ViewData.TemplateInfo.HtmlFieldPrefix = "Properties";
}
#Html.EditorForModel()
and the last step is to define a custom editor template for the Property class: ~/Views/Shared/EditorTemplates/Property.cshtml (note that the name and location of the template is important)
#model Property
<div class="input-block-level">
#Html.HiddenFor(m => m.Name)
#Html.TextBoxFor(m => m.Value, new { placeholder = Model.Name })
</div>
<br />
Try using the
List<Property>
as a model in your partial view and pass the CreateModule.Properties as model from your View
The problem is model binder can not figure out there
#Html.TextBoxFor(m=>names, new{Value="", placeholder=names})
belongs to as the "names" is not a property on your model class. If you need to bind to the CreateModule.Properties you need to change the partial view to emit textboxes with aproprate names, like this one:
#model IEnumerable<string>
#
{
int i=0;
}
#foreach(var names in Model)
{
<div class="input-block-level">#Html.TextBox("Properties[" + i + "].Value")</div>
<br/>
}

Resources