How do I map checkboxes onto MVC model members? - c#

I have an MVC view
<%# Page Language="C#" MasterPageFile="PathToMaster" Inherits="System.Web.Mvc.ViewPage<ModelData>" %>
and I have a form with HTML markup for a set of checkboxes:
<label for="MyCheckbox">Your choice</label>
<input type="checkbox" id="Option1" class="checkbox" name="MyCheckbox" value="Option one" />
<label for="Option1">Option one</label><br />
<input type="checkbox" id="Option2" class="checkbox" name="MyCheckbox" value="Option two" />
<label for="Option2">Option two</label><br />
and I have a controller-action pair
class MyController : Controller {
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult RequestStuff( ModelData data )
{
}
}
and that action is invoked when the form is submitted.
How do I map the checkboxes onto members of ModelData (and what members I have to add to ModelData) so that when the form is submitted data stores information on which checkboxes are checked?

OK, this one will be for MVC3, but - save for syntax changes - should work in MVC2 too. The approach is essentially the same.
First of all, you should prepare an appropriate (view)model
public class MyViewModel
{
[DisplayName("Option 1")]
public bool Option1 { get; set; }
[DisplayName("Option 2")]
public bool Option2 { get; set; }
}
Then you pass this model to the view you're showing (controller):
public ActionResult EditMyForm()
{
var viewModel = new MyViewModel()
return View(viewModel);
}
with the form:
#model MyViewModel
#using( Html.BeginForm())
{
#Html.Label("Your choice")
#Html.LabelFor(model => model.Option1) // here the 'LabelFor' will show you the name you set with DisplayName attribute
#Html.CheckBoxFor(model => model.Option1)
#Html.LabelFor(model => model.Option2)
#Html.CheckBoxFor(model => model.Option2)
<p>
<input type="submit" value="Submit"/>
</p>
}
Now here the HTML helpers (all the CheckBoxFor, LabelFor, EditorFor etc) allow to bind the data to the model properties.
Now mind you, an EditorFor when the property is of type bool will give you the check-box in the view, too. :)
And then, when you submit to the controller, it will auto-bind the values:
[HttpPost]
public ActionResult EditMyForm(MyViewModel viewModel)
{
//And here the view model's items will be set to true/false, depending what you checked.
}

First you define SelectList for Options. This will be used just to render checkboxes
public IList<SelectListItem> OptionsSelectList { get; set; }
Than, you define model that will hold value of single chosen option after post
public class ChooseOptionViewModel
{
public int OptionIdentifier { get; set; } //name or id
public bool HasBeenChosen { get; set; } //this is mapped to checkbox
}
Then IList of those options in ModelData
public IList<ChooseOptionViewModel> Options { get; set; }
And finally, the view
#for (int i = 0; i < Model.OptionsSelectList.Count(); i++)
{
<tr>
<td class="hidden">
#Html.Hidden("Options[" + i + "].OptionIdentifier", Model.OptionsSelectList[i].Value)
</td>
<td>
#Model.OptionsSelectList[i].Text
</td>
<td>
#Html.CheckBox("Options[" + i + "].HasBeenChosen", Model.Options != null && Model.Options.Any(x => x.OptionIdentifier.ToString().Equals(Model.OptionsSelectList[i].Value) && x.HasBeenChosen))
</td>
</tr>
}
After post, you just inspect Options.Where(x => x.HasBeenChosen)
This is full-functional, and it will allow you to redisplay view when validation errors occur, etc. This seems a bit complicated, but I haven't come up with any better solution than this.

Related

Model binding not working with labelfor

Here is my model:
public string CustomerNumber { get; set; }
public string ShipMethod { get; set; }
public string ContactPerson { get; set; }
public string ShipToName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3{ get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
Here part of my view:
<table class="table table-condensed">
<thead>
<tr>
<td>Customer Number</td>
<td>Ship Method</td>
<td>Contact Person</td>
<td>Ship to Name</td>
<td>Address 1</td>
<td>Address 2</td>
<td>Address 3</td>
<td>City</td>
<td>State</td>
<td>Zip</td>
</tr>
</thead>
<tbody>
<tr>
<td>#Html.LabelFor(x => x.CustomerNumber, Model.CustomerNumber)</td>
<td>#Html.LabelFor(x => x.ShipMethod, Model.ShipMethod)</td>
<td>#Html.LabelFor(x => x.ContactPerson, Model.ContactPerson)</td>
<td>#Html.LabelFor(x => x.ShipToName, Model.ShipToName)</td>
<td>#Html.LabelFor(x => x.Address1, Model.Address1)</td>
<td>#Html.LabelFor(x => x.Address2, Model.Address2)</td>
<td>#Html.LabelFor(x => x.Address3, Model.Address3)</td>
<td>#Html.LabelFor(x => x.City, Model.City)</td>
<td>#Html.LabelFor(x => x.State, Model.State)</td>
<td>#Html.LabelFor(x => x.ZipCode, Model.ZipCode)</td>
</tr>
</tbody>
</table>
The data gets displayed in the view correctly, but when a post happens on my page, the data comes back null. I need the data in those LabelFors to be sent back to my controller, so I dont have to store it in a session. I thought MVC was supposed to bind to your model automagically with the labelfors. What am I doing wrong?
EDIT
I guess the reason I asked this question, is because I just moved from webforms to MVC, and I am pretty lost without the viewstate. I figured if the values in my model kept posting back to me from the view, I wouldn't have to store my model in a session object. On my page, I need to be able to persist my model during page cycles, so I can store my model data into some sql tables after the user clicks the save button. What are some options to persist your model in MVC?
It only binds input elements inside a form (because the browser posts these). Label's aren't POST'ed.
You can use HiddenFor.. since they are input elements:
#Html.HiddenFor(x => x.City)
..etc. You just can't use labels for sending back to the Controller.
LabelFor:
<label></label> <!-- Not POST'ed by the browser -->
HiddenFor:
<input type="hidden" /> <!-- POST'ed by the browser -->
MVC will work according to HTML standards which means it won't postback a label element.
Use a HiddenFor or TextboxFor with a readonly attribute.
But if you just display the values, why not putting them in a session before sending it to the page?
If you have like a wizard style form where you need to save changes inbetween steps before committing the data to a database for example your best bet would be to save the submitted values in a session which you read out again in the next step in the controller. Otherwise you can put hidden inputs on your form with the values but that is prone to manipulation by the user.
Like Simon and others have explained labels are not posted. A post request should be used when you want to change something. You don't need to store it in session if you're just viewing and editing your model.
In your view, you'll need a link to edit your model:
#Html.ActionLink("edit", "Edit", new {id = Model.CustomerNumber});
In your controller implement your Edit action method:
public ViewResult Edit(int customerNumber) {
var customer = _repository.Customers.FirstOrDefault(c => c.CustomerNumber == customerNumber);
return View(customer);
}
You'll need a view for the Edit action method and in it goes your form to post your update.
#using (Html.BeginForm()) {
<label>Contact Person:</label>
<input name="ContactPerson" value="#Model.ContactPerson" />
// the rest of your form
<input type="submit" value="Save" />
}
Implement the method to handle the post. Make sure to add the HttpPost attribute.
[HttpPost]
public ViewResult Edit(Customer customer) {
if (ModelState.IsValid) {
// Save your customer
return RedirectToAction("Index");
}
else {
return View(customer);
}
}
Hope this helps.

How to pass id and name of a #Html.CheckBox via form collection in MVC3

This is my html-
<td>
#{
IEnumerable<SelectListItem> Brands = ViewBag.GetBrands;
foreach (var item in Brands)
{
#Html.CheckBox(item.Text, false)
<label>#item.Text</label><br />
}
}
</td>
Im Posting this controller as JSON data (form collection). How can i get checkbox's text and value in form collection data in controller?
How can i get checkbox's text and value in form collection data in controller?
The correct approach is to use a view model instead of this IEnumerable<SelectListItem>. So basically your model could look like this:
public class BrandViewModel
{
public string Text { get; set; }
public bool Checked { get; set; }
}
and then add a property to your main view model (the one your view is strongly typed to) of type IList<BrandViewModel>:
public IList<BrandViewModel> Brands { get; set; }
and then it's pretty easy:
<td>
#for (var i = 0; i < Model.Brands.Count; i++)
{
#Html.CheckBoxFor(x => x.Brands[i].Checked)
#Html.LabelFor(x => x.Brands[i].Checked, Model.Brands[i].Text)
#Html.HiddenFor(x => x.Brands[i].Text)
}
</td>
and finally you can get rid of any weakly typed FormCollection from your controller action and simply take the view model:
[HttpPost]
public ActionResult SomeAction(IList<BrandViewModel> brands)
{
...
}
or if there are also other properties you need to pass your controller action may take the main view model:
[HttpPost]
public ActionResult SomeAction(MainViewModel model)
{
// the model.Brands collection will be automatically bound here
...
}
I managed to get ID by -
#Html.CheckBox(item.Text, false, new {item.Value})
First You have to perform post back to server.
#using (Html.BeginForm("actionname", "controller",
FormMethod.Post))
//your code
#{
IEnumerable<SelectListItem> Brands = ViewBag.GetBrands;
foreach (var item in Brands)
{
#Html.CheckBox(item.Text, false)
<label>#item.Text</label><br />
}
}
<input type="submit" class="k-button" value="Submit" id="btnSubmit" name="btnSubmit" />
}
Now in the controller you will get the values using form collection
[HttpPost]
public ActionResult actionName( FormCollection collection)
{
collection.keys["checkbox"].value ... your code
}

How to access dynamically created checkbox value in Controller in mvc3?

I have the view that contains the checkbox and Submit button as shown below.
#using (Html.BeginForm())
{
<fieldset>
<legend style="font-size: 100%; font-weight: normal">Delete</legend>
<p> Are you sure you want to delete?</p>
#foreach (string resource in resources)
{
if (resource != "")
{
<input type="checkbox" name="Resources" title="#resource" value="#resource" checked="checked"/>#resource
<br />
}
}
<br />
#Html.HiddenFor(m => m.AttendeeListString)
#Html.HiddenFor(m => m.ResourceListString)
<span class="desc-text">
<input type="submit" value="Yes" id="btnYes" />
</span>
<span class="desc-text">
<input type="submit" value="No" id="btnNo" />
</span>
</fieldset>
}
Below is the Controller code...
public ActionResult DeleteResource(RoomModel roomModel)
{
...
}
RoomModel contains some other data...
Now how can i access the checkbox value in controller?
Note : I have lot more information that need to be send to Controller when i clicked on submit button... Can anybody suggest some solution....
Answer :
I have added these two property to My model
public List<SelectListItem> Resources
{
get;
set;
}
public string[] **SelectedResource**
{
get;
set;
}
And My view check box i have updated as follows
#foreach (var item in Model.Resources)
{
<input type="checkbox" name="**SelectedResource**" title="#item.Text" value="#item.Value" checked="checked"/>#item.Text
<br /><br />
}
And in Controller ...
if (roomModel.SelectedResource != null)
{
foreach (string room in roomModel.**SelectedResource**)
{
resourceList.Add(room);
}
}
Note: The name of check box and Property in the model should be same. In my case it is SelectedResource
You have a few options. The easiest would be:
1) Parameter bind a view model with the Resources property. I recommend this way because it's the preferred MVC paradigm, and you can just add properties for any additional fields you need to capture (and can take advantage of validation easily by just adding attributes).
Define a new view model:
public class MyViewModel
{
public MyViewModel()
{
Resources = new List<string>();
}
public List<string> Resources { get; set; }
// add properties for any additional fields you want to display and capture
}
Create the action in your controller:
public ActionResult Submit(MyViewModel model)
{
if (ModelState.IsValid)
{
// model.Resources will contain selected values
}
return View();
}
2) Parameter bind a list of strings named resources directly in the action:
public ActionResult Submit(List<string> resources)
{
// resources will contain selected values
return View();
}
It's important to note that in the question, the view is creating checkboxes that will send the string value of all checked resources, not boolean values (as you might expect if you used the #Html.CheckBox helper) indicating if each item is checked or not. That's perfectly fine, I'm just pointing out why my answer differs.
In MVC action, have a parameter that corresponds to the name of the checkbox, something like:
bool resources
bool[] resources
use javascript or jquery to collect all the value and post to the controller
var valuesToSend='';
$('input:checked').each(function(){
valuesToSend+=$(this).val() + "$";//assuming you are passing number or replace with your logic.
});
and after submit call ajax function
$.ajax({
url:'yourController/Action',
data:valuesTosend,
dataType:'json',
success:function(data){//dosomething with returndata}
})
or else you can pass the model to controller. if you implemented Model -View-ViewModel pattern.
public class yourViewModel
{
public string Id { get; set; }
public bool Checked { get; set; }
}
Action methods
[HttpPost]
public ActionResult Index(IEnumerable<yourViewModel> items)
{
if(ModelState.IsValid)
{
//do with items. (model is passed to the action, when you submit)
}
}
I'm assuming that the resources variable is generated in the Controller or can be placed onto the ViewModel. If so, then this is how I would approach it:
Your view model would have a Resources dictionary added to it, and would look something like this:
public class RoomModel
{
public Dictionary<string,bool> Resources { get; set; }
// other values...
}
You populate the Resources Dictionary with the names of your resource items as the key (string) and set the "checked" value (bool) to a default state of false.
e.g. (in your [HttpGet] controller)
// assuming that `resource` is your original string list of resources
string [] resource = GetResources();
model.Resources = new Dictionary<string, bool>();
foreach(string resource in resources)
{
model.Resources.Add(resource, false);
}
To render in the view, do this:
#foreach (string key in Model.Resources.Keys)
{
<li>
#Html.CheckBoxFor(r => r.Resources[key])
#Html.LabelFor(r => r.Resources[key], key)
</li>
}
This will then enable the [HttpPost] controller to automatically populate the dictionary onto the Model when you post back:
public ActionResult DeleteResource(RoomModel roomModel)
{
// process checkbox values
foreach(var checkbox in roomModel.Resources)
{
// grab values
string resource = checkbox.Key;
bool isResourceChecked = checkbox.Value;
//process values...
if(isResourceChecked)
{
// delete the resource
}
// do other things...
}
}
I have added these two property to My model
public List<SelectListItem> Resources
{
get;
set;
}
public string[] **SelectedResource**
{
get;
set;
}
And My view check box i have updated as follows
#foreach (var item in Model.Resources)
{
<input type="checkbox" name="**SelectedResource**" title="#item.Text" value="#item.Value" checked="checked"/>#item.Text
<br /><br />
}
And in Controller ...
if (roomModel.SelectedResource != null)
{
foreach (string room in roomModel.**SelectedResource**)
{
resourceList.Add(room);
}
}
Note: The name of check box and Property in the model should be same. In my case it is SelectedResource

Custom ValidationAttribute not firing IsValid function call in view model

I have created my own custom ValidationAttribute:
public class UrlValidationAttribute : ValidationAttribute
{
public UrlValidationAttribute() {}
public override bool IsValid(object value)
{
if (value == null)
return true;
var text = value as string;
Uri uri;
return (!string.IsNullOrWhiteSpace(text) &&
Uri.TryCreate(text, UriKind.Absolute, out uri));
}
}
I am using that on one of my models and it works perfectly. However, now I am attempting to use it on a view model:
public class DeviceAttribute
{
public DeviceAttribute(int id, attributeDefinition, String url)
{
ID = id;
Url = url;
}
public int ID { get; set; }
[UrlValidation]
public String Url { get; set; }
}
The view model is used in the partial view like this:
#model List<ICMDB.Models.DeviceAttribute>
<table class="editor-table">
#foreach (var attribute in Model)
{
<tr>
#Html.HiddenFor(a => attribute.ID)
<td class="editor-label">
#Html.LabelFor(a => attribute.Url)
</td>
<td class="editor-field">
#Html.TextBoxFor(a => attribute.Url)
#Html.ValidationMessageFor(a => attribute.Url)
</td>
</tr>
}
</table>
For some unknown reason, while the constructor for UrlValidationAttribute fires, the IsValid function doesn't fire. Any ideas?
Edit: On further investigation, it seems this is happening because the DeviceAttribute view model is actually the view model for a partial. The full page is passed a different view model that contains the list of DeviceAttribute view models. So when my controller action is called, the full page view model is constructed and its values filled, but no DeviceAttribute view models are constructed, hence why no validation is run.
I would recommend you using editor templates instead of writing foreach loops. I suppose that your main view model looks something like this:
public class MyViewModel
{
public List<DeviceAttribute> Devices { get; set; }
...
}
Now in your main view:
#model MyViewModel
#using (Html.BeginForm())
{
<table class="editor-table">
#Html.EditorFor(x => x.Devices)
</table>
<input type="submit" value="OK" />
}
and in the corresponding editor template (~/Views/Shared/EditorTemplates/DeviceAttribute.cshtml):
#model DeviceAttribute
<tr>
#Html.HiddenFor(x => x.ID)
<td class="editor-label">
#Html.LabelFor(x => x.Url)
</td>
<td class="editor-field">
#Html.TextBoxFor(x => x.Url)
#Html.ValidationMessageFor(x => x.Url)
</td>
</tr>
And your POST action takes the view model back:
[HttpPost]
public ActionResult Index(MyViewModel model)
{
...
}
Now the default model binder will successfully bind all values in the view model and kick validation.
Here's a nice blog post about templates.

How can I get multiple selections from ASP.NET CheckBoxs

This seems like it should be prettty easy - but I just can't get it to work!
I have an enum in my model, which I want to display as a list of checkboxes. The user can select multiple checkboxes, and I want to save this in the database.
So the enum is like so (approx 20 elements unabridged):
public enum ReferrerType
{
[Description("No Data")]
NoData = 9999,
[Description("Father or Mother")]
Parents = 1,
[Description("Brother or Sister")]
Sibling = 2,
[Description("Other")]
Other = 10
}
Whereby the Description is what is shown on the UI, and the numeric value is what is to be saved in the database. The numbers have to remain as listed, as they go directly into a stats package.
I then defined a Referrer class:
public class Referrer
{
public virtual Guid Id { get; private set; }
public virtual ReferrerType{ get; set; }
}
I realise this might be an odd (anti)pattern. I developed it in haste, and am repenting at leisure. Any advice on improving this model would also be much appreciated!
My controller sets up the list:
private static IList<string> GenerateReferrerList()
{
var values = from ReferrerType e in Enum.GetValues(typeof(ReferrerType))
select new { Name = e.ToDescription() };
return values.Select(x => x.Name).ToList();
}
And I use it in my View like this:
<div class="radio-boolean form-field" id="Referrers">
<p class="form-field-attribute"> <span class="label">Referred By </span> </p>
<% for (var i = 0; i < ((IList<string>)ViewData["ReferrerList"]).Count; i++)
{ %>
<p class="form-field-value">
<%= Html.CheckBox(string.Format("Referrers[{0}].Type", i) ) %>
<label for="Referrers"> <%= ((IList<string>)ViewData["ReferrerList"])[i]%></label>
</p>
</div>
And it doesn't work! I guess I'm missing something obvious, but I can't work out what. There are no errors - just an empty database table where referrers should be...
As always, any help much appreciated!
Let's take a moment and see what do we need here. We need to show a form which will contain multiple checkboxes (one for each value of the enum) and an associated label (this label should come from the Description attribute use on the enum). When this form is submitted we want to fetch all the values that the use has checked.
So as always once we have clear definition of what we are trying to do we introduce our view model:
public class MyViewModel
{
public bool IsChecked { get; set; }
public ReferrerType ReferrerType { get; set; }
public string Text { get; set; }
}
Then we write a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = Enum.GetValues(typeof(ReferrerType)).Cast<ReferrerType>().Select(x => new MyViewModel
{
ReferrerType = x,
Text = x.ToDescription() // I guess that's an extension method you wrote
});
return View(model);
}
[HttpPost]
public ActionResult Index(IEnumerable<MyViewModel> model)
{
...
}
}
And finally a strongly typed view corresponding to the Index action of our controller (~/Views/Home/Index.aspx):
<% using (Html.BeginForm()) { %>
#Html.EditorForModel()
<input type="submit" value="OK" />
<% } %>
and the last part is the corresponding editor template (~/Views/Home/EditorTemplates/MyViewModel.ascx):
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.MyViewModel>" %>
<%= Html.CheckBoxFor(x => x.IsChecked) %>
<%= Html.HiddenFor(x => x.ReferrerType) %>
<label><%: Model.Text %></label>
Now when this form is submitted inside the POST index action you would get a list of all enums with a corresponding boolean value indicating whether the user checked it or not.
OT: Don't perform excess actions:
return (from e in Enum.GetValues(typeof(ReferrerType))
select e.ToDescription()).ToList();
or just
return Enum.GetValues(typeof(ReferrerType)).Select(e => e.ToDescription()).ToList();

Categories

Resources