Multiple checkboxes in razor (using foreach) - c#

I have a problem and I can't find solution.
I'm using Razor and it is my VieModel class.
public class GroupToExport
{
public GroupToExport()
{
ToExport = false;
}
[DisplayName("Export")]
public bool ToExport { get; set; }
public Group Group { get; set; }
}
public class GroupsToExport
{
public GroupsToExport()
{
//refill list
}
public List<GroupToExport> ExportingGroups { get; set; }
}
View:
#using (Html.BeginForm("Export", "ElmahGroup", FormMethod.Post, new { id = "toExportForm" }))
{
//some divs
<input type="submit" id="js-export-submit" value="Export" />
#foreach (var item in Model.ExportingGroups)
{
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(modelItem => item.ToExport)
</td>
</tr>
}
//some divs
}
Controller:
public ActionResult Export(GroupsToExport model)
{
var groupsToExport = model.ExportingGroups.Where(x => x.ToExport).Select(x => x);
throw new System.NotImplementedException();
}
After submit "ToExport", in Controller, every group always has value 'false'. Even if all groups are checked.
Can somebody help me? What I'm doing wrong?

You cannot use a foreach loop to generate controls for a collection. The html you're generating for each checkbox (and for the associated hidden input) is <input type="checkbox" name="item.ToExport" .../>. Your model does not contain a property which is named item.
Use a for loop
#for(int i = 0; i < Model.ExportingGroups.Count; i++)
{
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(m => m.ExportingGroups[i].ToExport)
</td>
</tr>
}
Now your HTML will be
<input name="ExportingGroups[0].ToExport" .../>
<input name="ExportingGroups[1].ToExport" .../>
etc. which will correctly bind to your model
Edit
Alternatively you can use a custom EditorTemplate for typeof GroupToExport. Create a partial view /Views/Shared/EditorTemplates/GroupToExport.cshtml
#model yourAssembly.GroupToExport
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(m => m.ToExport)
</td>
</tr>
And then in the main view
#Html.EditorFor(m => m.ExportingGroups)
The EditorFor() method will generate the correct html for each item in your collection based on the template.

You are using Incorrect syntax to Map the values back when they are posted, since the checked value of a checkbox is initialised to false by default, that is the reason why it is always false,use sysntax
#for(int i = 0; i < Model.ExportingGroups.Count(); i++)
{
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(modelItem => Model.ExportingGroups[i].ToExport)
</td>
</tr>
}
//some divs
This should map back all values you are looking for.

I found this works much better: Leave the foreach loop as is (do not user a counter)
#foreach (var item in Model.GroupToExport)
{
Then use this Razor format to display a checkbox
#Html.CheckBox("Active", #item.ToExport)
Simple to use and does not make you change the typical foreach loop.

Related

MVC Form submit not transferring the updated model back to action method for List<class>

My VIEW (success.cshtml) is as below
#model IList<AzSample.Models.Employeelist>
#using (Html.BeginForm("Save","Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<h2>Employees Details</h2>
<div>
<table>
<tr>
<th>Id</th>
</tr>
#foreach (var emp in Model)
{
<tr>
<td>#emp.ID</td>
</tr>
}
</table>
<button type="submit" value="save" onclick="#Url.Action("Save", "Home")">Save</button>
</div>
}
My Home Controller is as below
public class HomeController : Controller
{
public ActionResult Import(HttpPostedFileBase excelfile)
{
//Code to get data from Excel sheet
List<Employeelist> obj = new List<Employeelist>();
for( int row =2; row <=range.Rows.Count; row++)
{
Employeelist emp = new Employeelist();
emp.ID =((Excel.Range)range.Cells[row,1]).Text;
obj.Add(emp);
}
return View("success", obj);
}
[HttpPost]
public ActionResult Save(List<Employeelist> empLIst)
{
// Code fro storing the data got from VIEW.
}
}
My Model is as below
public class Employeelist
{
public string ID { get; set; }
}
I am calling import action method from some other page, and reading the data from Excel sheet and showing it on success.cshtml. Till here it works fine.
When ever i click on Save button, the debug point is coming back to Save action method, but the data (i.e. List<Employeelist>, basically all Employeelist.ID's ) is null ?
What is missing ?
The data for the empList is never submitted with the form. You should render <input> fields to submit your data.
#for(int i = 0; i < Model.Count(); ++i) {
#Html.HiddenFor(m => m[i].Id)
// send other properties
}
It is important that you bind to indexed properties so the MVC modelbinder can bind this as a collection. #Html.HiddenFor(m => m.Id) will not work.
See also Model Binding to a List MVC 4
--Edit--
As Stephen has pointed out, the modelbinder will try to bind this using the index, so your c# model must implement IList<T>.
Another pitfall are non-sequential indices.
You should use this > ASP.NET MVC TempData ?
ASP.NET MVC TempData dictionary is used to share data between controller actions. The value of TempData persists until it is read or until the current user’s session times out. Persisting data in TempData is useful in scenarios such as redirection, when values are needed beyond a single request.
The code would be something like this:
Import Method:
public ActionResult Import(HttpPostedFileBase excelfile)
{
//Code to get data from Excel sheet
for( int row =2; row <=range.Rows.Count; row++)
{
Employeelist emp = new Employeelist();
emp.ID =((Excel.Range)range.Cells[row,1]).Text;
obj.Add(emp);
}
TempData["doc"] = obj;
return View("success", obj);
}
View:
#model IEnumerable<AzSample.Models.Employeelist>
#using (Html.BeginForm("Save","Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<h2>Employees Details</h2>
<div>
<table>
<tr>
<th>Id</th>
</tr>
#{var list = (List<AzSample.Models.Employeelist>)TempData["doc"];}
#{TempData["doc"] = list;}
#foreach (var emp in list)
{
<tr>
<td>#emp.ID</td>
</tr>
}
</table>
<button type="submit" value="save" onclick="#Url.Action("Save", "Home")">Save</button>
</div>
}
Save Method:
[HttpPost]
public ActionResult Save()
{
if (TempData["doc"] != null)
{
// Your code will be here
}
}

ASP.NET MVC - How do I access a specific property from a ViewModel?

I am learning how to apply ViewModels to ASP.NET MVC. I'm unsure how to be able to access "specific" properties of the ViewModel from within the View. I've got a ViewModel containing a whole bunch of properties like this:
public class ProductViewModel
{
public IEnumerable<Product> Products { get; set; }
public IEnumerable<ProductCategory> ProductCategories { get; set; }
public IEnumerable<ProductDescription> ProductDescriptions { get; set; }
}
Now, previously when I had a "listing" type page I could simply do this in the View (as can be seen, I'm accessing two different properties here, Products and ProductDescriptions):
#for (int i = 0; i < Model.Products.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(m => Model.Products.ElementAt(i).Name)
</td>
<td>
#Html.DisplayFor(m => Model.Products.ElementAt(i).ProductID)
</td>
<td>
#Html.DisplayFor(m => Model.ProductDescriptions.ElementAt(i).Description)
</td>
}
However I'm now totally stuck on implementing a "details" like page where I only need to access specific records For example:
#Html.DisplayFor(m => m.Products.ProductID)
Does not compile because ""Model.Products" does not contain a definition for "ProductID"". However, the Products model does have a ProductID or else the "listing" page code would not compile.
How do I fix this?
Erik Funkenbusch answered your problem, but an easy way to iterate a Collection is like this:
#foreach (var product in Model.Products)
{
<tr>
<td>
#Html.DisplayFor(m => product.ProductID)
</td>
</tr>
}

How binding random count column?

How binding this (dynamic) table to IList<IList<string>> or another type
html:
#using (Html.BeginForm())
{
<table>
<thead>
<tr><th>column1</th><th>column2</th></tr>
</thead>
<tbody>
#for (int i = 0; i < 10; i++)
{
<tr><td><input type="text" value="#i" name="column1_#(i)"/></td><td><input type="text" value="#(Guid.NewGuid())" name="column2_#(i)"/></td></tr>
}
</tbody>
</table>
<input type="submit" value ="send"/>
}
I need get columns and rows
Update:
maybe I can take String[][]
My first thought was to use a Dictionary<string, string>, but that's not indexable, so you'd have to write a custom model binder. Not that hard, but still. Then I thought about using a List<KeyValuePair<string, string>>, but KeyValuePairs have private setters, so, again, you'd need a custom binder.
So I think the best way is:
Create a custom type
public class MyItems
{
public string Key { get; set; }
public string Value { get; set; }
}
Now, add a list of this type as a property to your view model
public List<MyItems> MyItems { get; set; }
And, after populating the list and strongly typing your view, of course, you can render your table using the built in html helpers to ensure model binding won't have any hiccups
#for (int i = 0; i < Model.MyItems.Count( ); i++ )
{
<tr>
<td>#Html.TextBoxFor( m => m.MyItems[i].Key )</td>
<td>#Html.TextBoxFor( m => m.MyItems[i].Value)</td>
</tr>
}
Then catch the model in the controller and access you data
[HttpPost]
public ActionResult Index(Model viewModel)
{
foreach (var item in viewModel.MyItems)
{
string columnOneValue = viewModel.MyItems[0].Key;
string columnTwoValue = viewModel.MyItems[0].Value;
}

asp net MVC3 submit List of object is null when post back

I use asp net MVC 3 one of my project.I use partial view for my coding. I want to list all customers in a list and submit their information as a list. When I try to submit my list in post back, it sends my list is null. You can find my code as in the below:
My controller method is:
[HttpPost]
public ActionResult ConfirmUsers(ICollection<Career.DomainModel.UserApprovalDto> collection)
{
string bas = "";
//if (collection != null)
if (ModelState.IsValid)
{
bas = "bas";
}
return RedirectToAction("Index");
}
My partial view is:
#model List<Career.DomainModel.UserApprovalDto>
#using (Html.BeginForm("ConfirmUsers", "ManageUsers", new { area = "" }, FormMethod.Post))
{
<table>
<tr>
<th>
Name
</th>
<th>
Is Reported
</th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].FirstName)
</td>
<td>
#Html.CheckBox("IsReported", Model[i].IsReported.HasValue ? Model[i].IsReported.Value : false)
#*#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);*# #* #if (Model[i].IsReported != null)
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}
else
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}*#
</td>
<td>
</td>
</tr>
}
</table>
<div>
<input name="submitUsers" type="submit" value="Save" />
</div>
}
Thanks in advance.
Kerem
I would use Editor template to handle this. Have your View Model like this to represent the CheckBox item.
public class ReportedUserViewModel
{
public string FirstName { set;get;}
public int Id { set;get;}
public bool IsSelected { set;get;}
}
Now in yout main view model, add a property which is a collection of the above class
public class ConfirmUserViewModel
{
public List<ReportedUserViewModel> ReportedUsers{ get; set; }
//Other Properties also here
public ConfirmUserViewModel()
{
ReportedUsers=new List<ReportedUserViewModel>();
}
}
Now in your GET Action, you will fill the values of the ViewModel and sent it to the view.
public ActionResult ConfirmUser()
{
var vm = new ConfirmUserViewModel();
//The below code is hardcoded for demo. you mat replace with DB data.
vm.ReportedUsers.Add(new ReportedUserViewModel { Name = "Test1" , Id=1});
vm.ReportedUsers.Add(new ReportedUserViewModel { Name = "Test2", Id=2 });
return View(vm);
}
Now Let's create an EditorTemplate. Go to Views/YourControllerName and Crete a Folder called EditorTemplate and Create a new View there with the same name as of the Property Name(ReportedUsers.cshtml)
Add this code to the newly created editor template.
#model ReportedUserViewModel
<p>
<b>#Model.FirstName </b> :
#Html.CheckBoxFor(x => x.IsSelected) <br />
#Html.HiddenFor(x=>x.Id)
</p>
Now in your Main View, Call your Editor template using the EditorFor Html Helper method.
#model ConfirmUserViewModel
#using (Html.BeginForm())
{
<div>
#Html.EditorFor(m=>m.ReportedUsers)
</div>
<input type="submit" value="Submit" />
}
Now when You Post the Form, Your Model will have the ReportedUsers Collection where the Selected Check boxes will be having a True value for the IsSelected Property.
[HttpPost]
public ActionResult AddAlert(ConfirmUserViewModel model)
{
if(ModelState.IsValid)
{
//Check for model.ReportedUsers collection and Each items
// IsSelected property value.
//Save and Redirect(PRG pattern)
}
return View(model);
}
With the code you wrote, MVC model binder mechanism does not know how to map those inputs into List of object.
Do this little trick instead:
#Html.CheckBox("[" + i + "]." + "IsReported", Model[i].IsReported.Value);
This will result the name of input field as [0].IsReported for the first item in the list, [1].IsReported for next item.
That should work.
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
I'm a little late to the party, but it is easy enough to just refer to the list if you embed it in the model--
#Html.CheckBoxFor(modelItem => Model.List[i].Selected)
That will post back for each item in the iterator.
#Html.CheckBox("[" + i + "]." + "IsReported", Model[i].IsReported.Value);
worked perfectly for me.
Make sure your post method contains parameter for the list.
e.g public ActionResult Index(ConfigViewModel model, List configurationList)
Here in my case I have one view model (model) which contains list object.
If you specify view model in your post method as it is then you will get null value for the list (here model object is null). But if you add specific list parameter (configurationList) in the action then you can get all of the list values in the controller.
i run into same problem last week.
I realize that checkbox has three value(true/false/null) when i get it from database because i let the checkbox value nullable when i desingned database. i redesinged db and the problem was solved.
you didnt post models so i dont realy sure if this is the case. just look at the model, if it's writing Nullable above your Ischeck property, go to database and redesign. remember isCheck, isSelected properties have to have just two values(true/false).

How to get a list/array from a view back to the controller

My ViewResult Controller:
public ViewResult Controller(int id)
{
List<Data> dataList = dataAccess.getDataById(id);
Results[] resultArray = new Results[dataList.Count];
ViewBag.results= resultArray;
return View(dataList);
}
My View:
#model IEnumerable<Solution.Models.Data>
#{
Solution.Models.Results[] res= ViewBag.results;
}
#using (Html.BeginForm()) {
<table>
#{
int i = 0;
foreach (var item in Model) {
<tr>
<td>
Snippet: #Html.DisplayFor(modelItem => item.text)
</td>
</tr>
<tr>
<td>
Translation: #Html.EditorFor(modelItem => res[i].text)
</td>
</tr>
i++;
}
}
</table>
<p>
<input class="CRUD-buttons" type="submit" value="Send" />
</p>
}
My Controller ActionResult:
[HttpPost]
public ActionResult Controller(/*List<Data> dataList, */Results[] results)
{
ResultText = results[0].text; //NullReferenceException
}
dataList and results are empty. I read a couple of posts on stackoverflow, but could not find a solution.
I took already a look on the following blog (link) but its MVC 2 code. :(
There are multiple ways to do this. What gets you here is that in order for you to receive the results parameter, the name for the generated edit controls should be results[0] for the first, results[1] for the second, etc with no gaps in the indexes (this is because of how DefaultModelBinder expects to find the fields named when the form is posted).
So one immediate (although not very good) solution would be to specify the name correctly:
#Html.TextBox(string.Format("results[{0}]", i), res[i].text)
A much better solution would be to put the results into your model (better yet, in a ViewModel created specifically for this view). For example, first of all you create a class that encapsulates one piece of data and the corresponding result:
class ItemViewModel
{
Solution.Models.Data TheData { get; set; }
Solution.Models.Results TheResults { get; set; }
}
You then make your view have a collection of these items as its model:
#model IEnumerable<ItemViewModel>
and then output the edit control with
Translation: #Html.EditorFor(modelItem => modelItem.TheResults)
Finally, you modify the postback action to accept an array of ItemViewModel:
[HttpPost]
public ActionResult Controller(ItemViewModel[] results)
General piece of advice: Try to avoid using EditorFor with objects that are not part of your view's model (for example, things you have passed through ViewBag or ViewData). If you do this, MVC will punish you.

Categories