MVC Binding to checkbox - asp.net-mvc

I have found so many questions about this, but none of them go over or seem to go over my scenario. I have a model:
public class CheckBoxModel
{
public int Id{ get; set; }
public bool IsSelected { get; set; }
}
In then try and bind my IsSelected bool to a checkbox like this:
<%= Html.CheckBox("IsSelectedCheck",Model.IsSelected)%>
I have lots of items on a page, they all have a checkbox next to them, all i am trying to do is pass back to the controller all the id's of the items and which ones have been selected.
At the moment, the value of IsSelected is always false. Should Html.CheckBox set the value of Model.IsSelected each time the user toggles the checkbox.
Thanks

Try like this:
<%= Html.CheckBoxFor(x => x.IsSelected) %>
Also if you want to pass along the id don't forget to do so:
<%= Html.HiddenFor(x => x.Id) %>
And if you had a collection of those:
public class MyViewModel
{
public CheckBoxModel[] CheckBoxes { get; set; }
}
you could:
<% for (var i = 0; i < Model.CheckBoxes.Length; i++) { %>
<div>
<%= Html.HiddenFor(x => x.CheckBoxes[i].Id) %>
<%= Html.CheckBoxFor(x => x.CheckBoxes[i].IsSelected) %>
</div>
<% } %>
which will successfully bind to:
[HttpPost]
public ActionResult MyAction(MyViewModel model)
{
// model.CheckBoxes will contain everything you need here
...
}

An alternative to Darin's fantastic answer
I definitely recommend following Darin's approach for returning classes which will be most of the time. This alternative is a 'quick' and dirty hack if all you need is the checked Ids:
<% foreach (var cb in Model.CheckBoxes) { %>
<div>
<input type="checkbox"
value="<%= cb.Id %>"
<%= cb.IsSelected ? "checked=\"checked\"" : "" %>
name="ids" />
</div>
<% } %>
Will bind to the int[] ids parameter in the following action:
[HttpPost]
public ActionResult MyAction(int[] ids)
{
// ids contains only those ids that were selected
...
}
The benefit is cleaner html as there is no hidden input.
The cost is writing more code in the view.
In MVC 4.0 (Razor 2.0) you can use the following syntax in your view:
<input type="checkbox" value="#cb.Id" checked="#cb.IsSelected" name="ids" />

Related

How do I post two instances of the same property from a view to an action?

How can I send my data from form, two fields are the same: Station name, but they have different values. How send they via post method to controller. Asp.net mvc2
here what i try:
<% using (Html.BeginForm("ViewRes", "Shedule"))
{%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Поиск по расписанию:</legend>
<ul>
<li>Из<%= Html.EditorFor(model => model.StationName) %></li>
<li>В<%= Html.EditorFor(model1 => model1.StationName) %></li>
<li>Дата отправления</li>
</ul>
<p>
<input type="submit" value="OK" />
</p>
</fieldset>
<% } %>
and such controller:
[HttpPost]
public ActionResult ViewRes(string a1, string b1)
{
DateTime dtm = Convert.ToDateTime("30.11.2011 0:00:00");
var res = (from d in db.RouteDetail
from m in db.RouteDetail
lalala
where (d.Station == a1
&&
m.Station == b1)
lalalal
}).ToList();
return View(res);
}
The way I would approach this is to refactor my model to encompass both of your existing inputs separately. That way each can be bound accordingly in the action.
public class RailwayRoute
{
public string StartStation { get; set; }
public string EndStation { get; set; }
}
View
<% using (Html.BeginForm("ViewRes", "Shedule"))
{%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Поиск по расписанию:</legend>
<ul>
<li>Из<%= Html.EditorFor(model => model.StartStation) %></li>
<li>В<%= Html.EditorFor(model => model.EndStation) %></li>
<li>Дата отправления</li>
</ul>
<p>
<input type="submit" value="OK" />
</p>
</fieldset>
<% } %>
Controller/Action
[HttpPost]
public ActionResult ViewRes(string startStation, string endStation)
{
...
}
Expanding on tvanfosson's answer which pretty much sums up, I would try and decouple your data model from your view data. This is often done by using the MVVMC aproach where VM stands for view model. In your case you appear to be passing directly the data model to the view which is sometimes not the recommended approach.
So changing tvanfosson's RailwayRoute to a viewModel object I would ensure the controller action maps the data appropiately. Something like
[HttpPost]
public ActionResult ViewRes(RailwayRouteViewModel viewModel)
{
DateTime dtm = Convert.ToDateTime("30.11.2011 0:00:00");
var res = (from d in db.RouteDetail
from m in db.RouteDetail
lalala
where (d.Station == viewModel.StartStation
&&
m.Station == viewModel.EndStation)
lalalal
select new RailywayRouteViewModel()
{
StartStation = d.Station,
EndStation = m.Station
}
}).ToList();
return View(res);
}

How ASP.NET MVC: How can I bind a property of type List<T>?

Let's say I've the bellow model
public class UserInformation
{
public List<UserInRole> RolesForUser { get; set; }
//Other properties omitted...
}
public class UserInRole
{
public string RoleName { get; set; }
public bool InRole { get; set; }
}
On my page I have something like
<%using(Html.BeginForm()){%>
.../...
<%for(int i =0; i<Model.InRoles.Cout; i++%>
<p><%: Html.CheckBox(Model.Roles[i].RoleName, Model.Roles[i].InRole)%></p>
<%}%>
The idea is to be able to check/uncheck the checkbox so that when the form is posted to the action, the action acts appropriately by adding/removing the user from each role.
The problem is when form is posted to the action method, the Roles property (which is a list UserInRole object) doesn't reflect the change made by the user. ModelBinder works properly on the all other properties but 'Roles property'
I wonder how I can do that. I suspect that the name/id given to the checkbox is not appropriate. But, I'm just stack. Maybe I should do it differently.
Thanks for helping
You should see Phil Haack's post on model binding to a list.
Essentially what you need to is simply submit a bunch of form fields each having the same name.
<%# Page Inherits="ViewPage<UserInformation>" %>
<% for (int i = 0; i < 3; i++) { %>
<%: Html.EditorFor(m => m.RolesForUser[i].RoleName) %>
<%: Html.EditorFor(m => m.RolesForUser[i].InRole) %>
<% } %>
I think the problem is how your submitting your form data. For model binding to work it needs the key name with its associated value. The below code based on your code should bind correctly:
<%using(Html.BeginForm()){%>
.../...
<%for(int i =0; i<Model.RolesForUser.Count; i++%>
<p>
<%: Html.Hidden("UserInformation.RolesForUser[" + i + "].RoleName", Model.RolesForUser[i].RoleName) %>
<%: Html.CheckBox("UserInformation.RolesForUser[" + i + "].InRole", Model.RolesForUser[i].InRole) %>
<%: Model.RolesForUser[i].RoleName %>
</p>
<%}%>

Asp.net MVC2 ModelBindingContext.ModelName empty

I'm not even quite sure where to start explaining this problem. I've been working on this for about the past 10 hours without a clue as to what the root cause is. If any additional details are needed, I'd be happy to provide. I'm just guessing at what is relevant at this point.
I have an MVC2 site with routes set up by by Steve Hodgkiss' wonderful RestfulRouting package, using the default route setup with nested controllers (e.g. /customer/{custid}/location/{locid} and such).
In this, I have one particular model that is giving me issues. For some reason, when the create page post's the data back to my server, the ModelName property in the ModelBindingContext object passed to the DefaultModelBinder (well, my custom class inherited from DefaultModelBinder, to handle grabbing objects from a repository). This happens only for this one model. And I can't spot the differences at all.
The broken model
public class RemedialItem : Entity
{
public virtual int Id { get; set; }
....
A working model:
public class Customer : Entity
{
public virtual int Id { get; set; }
....
Entity is just an empty class used as a marker for Reflection use.
The broken controller method in RemedialItemController.cs
[HttpGet]
public ActionResult New(int? locationId, int? applianceId)
{
var model = ViewModelFactory.Create<CreateRemedialItemViewModel>();
model.Categories = (from c in repository.Query<RemedialItemCategory>()
orderby c.Name
select c).ToList();
model.RemedialItem = new RemedialItem();
return View(model);
}
A working controller method in CustomerController.cs
[HttpGet]
public ActionResult New()
{
var viewModel = ViewModelFactory.Create<SingleCustomerViewModel>();
viewModel.Customer = new Customer();
return View(viewModel);
}
ViewModelFactory is an injected class that handles setting up some basic properties common to all view models (mainly is the user logged in and user details right now)
A broken viewmodel:
public class CreateRemedialItemViewModel : ViewModelBase
{
public RemedialItem RemedialItem { get; set; }
public IList<Location> Locations { get; set; }
public IList<Appliance> Appliances { get; set; }
public IList<RemedialItemCategory> Categories { get; set; }
}
A working ViewModel:
public class SingleCustomerViewModel : ViewModelBase
{
public Customer Customer { get; set; }
}
ViewModelBase contains a handful of properties populated by the ViewModelFactory.
The broken form in thew New view for RemedialItem:
<% using(Html.BeginForm("Create","RemedialItem",FormMethod.Post))
{%>
<%: Html.AntiForgeryToken() %>
<fieldset>
<legend>General</legend>
<div>
<%: Html.LabelFor(m=>m.RemedialItem.Category) %>
<%:Html.DropDownListFor(m=>m.RemedialItem.Category.Id, new SelectList(Model.Categories,"Id","Name")) %>
</div>
<div>
<%: Html.LabelFor(m=>m.RemedialItem.Item) %>
<%: Html.TextAreaFor(m=>m.RemedialItem.Item) %>
</div>
<div>
<%: Html.LabelFor(m=>m.RemedialItem.Note) %>
<%: Html.TextAreaFor(m=>m.RemedialItem.Note) %>
</div>
<input type="submit" value="Create Item" />
</fieldset>
<%}%>
A working New view:
<% using (Html.BeginForm("Create","Customer",FormMethod.Post)) {%>
<%: Html.ValidationSummary(true) %>
<%:Html.AntiForgeryToken() %>
<fieldset>
<legend>Fields</legend>
<p>
<%: Html.LabelFor(m=>m.Customer.Name) %>
<%: Html.TextBoxFor(m=>m.Customer.Name) %>
</p>
<p>
<%: Html.LabelFor(m=>m.Customer.Street) %>
<%: Html.TextBoxFor(m=>m.Customer.Street) %>
</p>
[...tl;dr...]
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
Both produce similar field names:
Broken:
<label for="RemedialItem_Item">Item</label>
<textarea cols="20" id="RemedialItem_Item" name="RemedialItem.Item" rows="2">
</textarea>
Working:
<label for="Customer_Name">Name</label>
<input id="Customer_Name" name="Customer.Name" type="text" value="" />
I apologize for the overly long code dump, in short:
The working set of stuff, when posted back on the create form, has the ModelName set to Customer. The broken stuff is an empty string
Is there something I'm missing? Has anyone encountered something like this before?
I found the issue. In the ViewModel the property that held the instance of RemedialItem to display was called RemedialItem. In the action it posted to, the parameter that took the RemedialItem instance was called item, and that broke everything.
In short, when using ViewModels, make sure the parameter name that takes an object from the ViewModel is the same as the property name in the viewmodel.
There went my day.

Model binding form to a List using dropdown. binding failing 1 way for dropdown

I have an action called Sessies. In this action i am creating 'Sessies' objects from a form. if they don't exist i add them in the DB, if there are already Sessies objects connected to the 'Reeksen' object, i load the 'Sessies' into the form so that they can be edited. so i have a create and edit in 1 and the same form.
Also, a 'Reeksen' has a predefined number of 'Sessies' which can not be changed. so i let the user make all 'Sessies' in one time (cos the amount of sessies will be from 1 to 10)
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<List<MVC2_NASTEST.Models.FlatSessie>>" %>
...
<h2>
Sessies</h2>
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<% for (int i = 0; i < Model.Count; i++) { %>
<%= Html.HiddenFor(model => model[i].Ses_ID)%>
<%= Html.HiddenFor(model => model[i].Ses_Rks_ID)%>
<div class="editor-label">
<%= Html.LabelFor(model => model[i].Ses_Nummer)%>
</div>
<div class="editor-field">
<%= Html.HiddenFor(model => model[i].Ses_Nummer)%>
<%= Html.Label(Model[i].Ses_Nummer.ToString())%>
</div>
....
<div class="editor-label">
<%= Html.LabelFor(model => model[i].Ses_LG_ID)%>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model[i].Ses_LG_ID, MVC2_NASTEST.MvcApplication.lesgeverList(), "Selecteer een lesgever...")%>
<%= Html.ValidationMessageFor(model => model[i].Ses_LG_ID)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model[i].Ses_Lpl_ID)%>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model[i].Ses_Lpl_ID, (ViewData["lesplist"] as List<List<SelectListItem>>)[i], "Selecteer een lesplaats...")%>
<%= Html.ValidationMessageFor(model => model[i].Ses_Lpl_ID)%>
</div>
<% } %>
<p>
<input type="submit" value="Create" />
</p>
in my aspx i use a for loop which goes over the List (a FlatSessie is a Sessie flattened strings.)
namespace MVC2_NASTEST.Models {
public partial class FlatSessie {
public int Ses_ID { get; set; }
public int Ses_Nummer { get; set; }
public string Ses_Datum { get; set; }
public string Ses_Beginuur { get; set; }
public string Ses_Einduur { get; set; }
public int Ses_Lpl_ID { get; set; }
public int Ses_Rks_ID { get; set; }
public int Ses_LG_ID { get; set; }
}
}
so, in my code it goes like this:
int antses = m.Mod_AantalSessies.Value;
List<List<SelectListItem>> lpllst = new List<List<SelectListItem>>(antses);
List<FlatSessie> sl = new List<FlatSessie>(antses);
Reeksen rks = _db.Reeksens.First(r => r.Rks_ID == id)
...
List<Sessies> sesl = rks.Sessies.ToList();
for (int i = 0; i < antses; i++) {
sl.Add(Mapper.Map<Sessies, FlatSessie>(sesl[i]));
lpllst.Add(MvcApplication.lesplaatsList(schooljaarparam, sesl[i].Ses_Lpl_ID));
}
...
ViewData["lesplist"] = lpllst;
ViewData["lglist"] = MvcApplication.lesgeverList();
return View(sl);
and the lesgeverlist() method
public static List<SelectListItem> lesgeverList() {
NASDataContext _db = new NASDataContext();
var lesg = (from l in _db.Lesgevers
where l.LG_Naam != "leeg"
orderby l.LG_Naam
select l).ToSelectList(m => m.LG_Naam + " " + m.LG_Vnaam, m => m.LG_ID.ToString(), m => m.LG_ID < -1);
return lesg.ToList();
}
now the problem:
this all works brilliantly. the List goes to the ASPX, i get the form as much times as there are items in the List, and postback works also, the parsing goes and everything. so all is good except for 1 point: the dropdowns.
usually in MVC i don't set any selected value for a SelectList or for a List because they dont need it, in the Edit page, MVC sets those selected items itself on binding.
now however, with the form in the Foreach loop, all fields get filled besides the dropdown boxes, these do not receive their 'initial value'.
however when i set an item in the List as selected, it does get selected in the form. (as seen from the ViewData["lesplist"]) but when i send a normal List with no selected value, the model binder does not propagate it's given value for that field to the selectedvalue of the dropdown.
however, when i do a form submit, and i return the view (because of validation failed) the dropdowns DO keep their value.
Is this fixable, or is this just a flaw in MVC2?
DropDownListFor not binding on Edit View with repeating items (List<T>)
there is the answer :)

ASP.NET MVC 2 UI Templates displaying data without markup. How to fix?

Using EditorFor( model lambda, "viewTemplateName"), my output is completely not as expected. This doesn't produce any errors, but it is rendering output without markup. What am I doing wrong?
The Output:
HarryTomRichard
The Expected Output (I need to figure out how to render the List [] indexes on id too but not to that problem yet):
<table>
<tr><td><span><input type="Text" id="Name[0]" value="Harry" /></span></td></tr>
<tr><td><span><input type="Text" id="Name[1]" value="Tom" /></span></td></tr>
<tr><td><span><input type="Text" id="Name[2]" value="Richard" /></span></td></tr>
</table>
My Classes:
namespace Marcs.Models {
public class Student { public string Name { get; set; } }
public class Classroom { public List<Student> Students { get; set; }
}
My Controller:
public ActionResult Index() {
var myStudents = new List<Student>();
myStudents.Add(new Student { Name = "Harry" });
myStudents.Add(new Student { Name = "Tom" });
myStudents.Add(new Student { Name = "Richard" });
var myClass = new Classroom {Students = myStudents};
return View(myClass);
}
My Index View:
Inherits="System.Web.Mvc.ViewPage<Marcs.Models.Classroom>" %>
<% using (Html.BeginForm()) { %>
<%= Html.EditorFor(m => m.Students, "Classroom") %>
<input type="submit" value="Save" />
<% } %>
My Classroom Template (notice the m => item so I can use the item, not the model):
Inherits="System.Web.Mvc.ViewUserControl<List<Marcs.Models.Student>>" %>
<table>
<% foreach (Marcs.Models.Student item in Model)
{ %><tr><td><%= Html.EditorFor(m => item, "Student")%></td></tr><%
} %>
</table>
My Student Template:
Inherits="System.Web.Mvc.ViewUserControl<Marcs.Models.Student>"
%><span><%= Html.Encode( Html.EditorFor( m => m.Name)) %></span>
jfar has the answer, and I will mark it appropriately when added. The solution was simply to ensure the files were located in Views->ControllerName->EditorTemplates and Views->ControllerName->DisplayTemplates. These can also be located in the Shared folder too.
I like this post. Now I need to learn how to use the MVC 2 template Html helpers that reference collections. It's in MVC 2 RC.

Resources