I have a list, then i want to pass it to view. I use model binding method.
Here is my code:
Model:
public class Group
{
public string Name { get; set; }
public string GroupID { get; set; }
}
Controller:
public ActionResult Index()
{
return View(ListGroup);
}
[HttpPost]
public ActionResult Index(List<Group> listModel)
{
#ViewBag.Success = "Update Suceess";
return View(listModel);
}
[HttpPost]
public ActionResult Search(Group modelSearch)
{
List<Group> listResult = listGroup.Where(m=>m.GroupID == modelSearch.GroupID).ToList();
if (modelSearch.GroupID == null)
return View("Index", listGroup);
return View("Index", listResult);
}
In view i want to display list by group of GroupID
#using (Html.BeginForm("Index", "DisplayTable", FormMethod.Post))
{
<table>
<tr>
<td>Name</td>
<td>GroupID</td>
</tr>
#foreach(var items in Model.GroupBy(m => m.GroupID).Select(g => g.ToList())){
<tr><td colspan="2">#items.ElementAt(0).GroupID</td></tr>
for (int i = 0; i < items.Count; i++) {
<tr>
<td>#Html.TextBoxFor(m => items[i].Name)</td>
<td>#Html.TextBoxFor(m => items[i].GroupID)</td>
</tr>
}
}
</table>
<input type="submit" value="SAVE" />
}
My problem is: If I use the code above, the view display data in group correctly, but when i click button SAVE, It didn't send any data to controller, ActionResult Index(List<Group> listModel) received a null list.
MORE EXPLANATION:
I have list like this:
NAME | GROUPID
Pen; A
Ink; B
Pecil; A
Book ; C
Ruler; B
NoteBook ; C
I want to display it in view like this:
GroupID: A - Pen: A | Pencil: A
GroupID: B - Ruler: B | Ink: B
GroupID: C - Book: C | NoteBook: C
If i use this code in view:
<td>#Html.TextBoxFor(m => items[i].Name)</td>
<td>#Html.TextBoxFor(m => items[i].GroupID)</td>
The result in view is correct, but this cannot send data back to controller. I found that the name of each field Name GroupID didnt match with the name of textfield in html.
I use group by in view and i dont know what is the best way in this case: Group by in controller or in View. and What is the structure of list if I group by in controller.
From your edit, I see so you have a list of these objects and you must group them by id, so many objects like pen and pencil have the same id 'A', if we group them together, they belong to group with ID 'A'.
Your best practice is to have the correct business logic, and model from controller to view. Like in this answer, actually have the List'group' in your model, if you want to separate business and UI, then create a view model, which holds your 'group' business object. The ui part will hold a list of child groups. Then serialize from viewmodel to business model.
For how to populate the correct model from only having a list of child items. See the controller.
First, you can check the source of the page to see if razor is building the correct html that you expect.
Your problem is due to how list binding works. At the end your page source must appear as below:
Notice the hidden for fields, they bind your original objects and their id's. Each input on the page must have a unique name or ID.
<tr>
<td colspan="2">
0 - name - 0
<input name="[0].GroupID" type="hidden" value="0" />
<input name="[0].Name" type="hidden" value="0 - name" />
</td>
</tr>
<tr>
<td><input class="text-box single-line" name="[0].ListGroup[0].Name" type="text" value="0 - name0 - subname" /></td>
<td><input name="[0].ListGroup[0].GroupID" type="text" value="00" /></td>
</tr>
<tr>
<td><input class="text-box single-line" name="[0].ListGroup[1].Name" type="text" value="0 - name1 - subname" /></td>
<td><input name="[0].ListGroup[1].GroupID" type="text" value="01" /></td>
</tr>
Use the code below to make the solution work. Please don't allow people to edit the groupID as once this is saved it will effect the behavior, IDs must remain unique. This is also true within your DB structure.
Your Controller
public class homeController : Controller
{
//
// GET: /home/
[HttpGet]
public ActionResult Index()
{
List<Group> lst = new List<Group>();
for (var i = 0; i < 5; i++)
{
lst.Add(new Group()
{
GroupID = i.ToString(),
Name = i.ToString() + " - name",
ListGroup = new List<Group>()
});
}
foreach (var g in lst)
{
for (var i = 0; i < 3; i++)
{
g.ListGroup.Add(new Group()
{
GroupID = g.GroupID + i.ToString(),
Name = g.Name + i.ToString() + " - subname",
ListGroup = new List<Group>()
});
}
}
return View(lst);
}
[HttpPost]
public ActionResult Save(List<Group> listModel)
{
#ViewBag.Success = "Update Suceess";
return View(listModel);
}
public class Group
{
public string Name { get; set; }
public string GroupID { get; set; }
public List<Group> ListGroup { get; set; }
}
}
Your View
#model List<TestMVC.Controllers.homeController.Group>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("Save", "Home", FormMethod.Post))
{
<table>
<tr>
<td>Name</td>
<td>GroupID</td>
</tr>
#for (var g = 0; g < Model.Count; g++)
{
<tr>
<td colspan="2">
#Model[g].Name - #Model[g].GroupID
#Html.HiddenFor(x => Model[g].GroupID)
#Html.HiddenFor(x => Model[g].Name)
</td>
</tr>
for (var i = 0; i < Model[g].ListGroup.Count; i++)
{
<tr>
<td>#Html.EditorFor(x => Model[g].ListGroup[i].Name)</td>
<td>#Html.TextBoxFor(x => Model[g].ListGroup[i].GroupID)</td>
</tr>
}
}
</table>
<input type="submit" value="SAVE" />
}
Runtime:
Please take a look at this answer here for a different approach using Model Editor For:
MVC Form not able to post List of objects
Further reading, excelent article on List Binding in MVC:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
Try to use Html.TextBox:
#{ var index = 0; }
#foreach(var items in Model.GroupBy(m=>m.GroupID).Select(g=>g.ToList())){
Html.TextBox("items[" + index + "].Name", items.Name);
Html.TextBox("items[" + index + "].GroupID", items.GroupID);
index++;
}
Related
Advance warning, I am extremely new to ASP.NET.
I'm working on a project which will display rows of data from a db table. When a user clicks the "Ignore" button next to a row, it should update the corresponding "Ignore" column on that row with true in the database.
The view itself works fine, it displays all the data as expected. But when "Ignore" is clicked, and it calls the Ignore() method on the controller, the model is which is passed to the controller is null.
My model, generated by entity framework (with extraneous properties removed):
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IgnoreDailyItems.Models
{
[Table("DataChecks.tbl.DailyItems")]
public partial class DataChecksTblDailyItems
{
[Column("entryId")]
public int EntryId { get; set; }
[Column("ignore")]
public bool? Ignore { get; set; }
}
}
The view:
#model IEnumerable<IgnoreDailyItems.Models.DataChecksTblDailyItems>
#{
ViewBag.Title = "Placeholder";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.EntryId)
</th>
</tr>
#{ var item = Model.ToList(); }
#for(int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item[i].EntryId)
</td>
<td>
#using (Html.BeginForm("Ignore", "Home", FormMethod.Post))
{
#Html.HiddenFor(modelItem => item[i].EntryId)
<button type="submit" class="btn btn-danger">Ignore</button>
}
</td>
</tr>
}
</table>
And the Ignore() method on the controller:
[HttpPost]
public ActionResult Ignore(DataChecksTblDailyItems modelData)
{
using (var context = new IgnoreDailyItemsContext())
{
var query = context.DataChecksTblDailyItems
.Where(b => b.EntryId.Equals(modelData.EntryId));
foreach (var q in query)
{
q.Ignore = true;
}
context.SaveChanges();
return RedirectToAction("Index", "Home");
}
}
You're generating the form in wrong way.
#Html.HiddenFor(modelItem => item[i].EntryId)
It will generate an input hidden with item[0].EntryId, item[1].EntryId... as name/id for each row in the table, for that reason the post model definition does not match.
To solve it, set the input hidden name manually:
#Html.Hidden("EntryId", item[i].EntryId)
You need to pass IEnumerable<IEnumerable> as a parameter.
public ActionResult Ignore(IEnumerable<DataChecksTblDailyItems> modelData)
{
In my ASP.Net MVC project I have following ViewModel
public class ApproveItemViewModel
{
[Required]
public int ItemId { get; set; }
[Required]
public string ItemCode { get; set; }
}
I have following two methods in controller
[HttpGet]
public ActionResult ListPendingItems()
{
var items = new List<ApproveItemViewModel>();
//add few items here in above list
return View(vms);
}
[HttpPost]
public JsonResult ApproveItem(ApproveItemViewModel viewmodel)
{
return Json(new { success = success }, JsonRequestBehavior.AllowGet);
}
Now in my razor view what I want is to call ApporveItem method using Ajax calls for each individual item. so I created multiple ajax form using below code.
#model IEnumerable<ApproveItemViewModel>
#foreach (var item in Model)
{
using (Ajax.BeginForm("ApproveItem", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "dane"
}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table>
<tr>
<td>#Html.LabelFor(m => item.ItemId)</td>
<td>#Html.DisplayFor(m => item.ItemId)</td>
</tr>
<tr>
<td>#Html.LabelFor(m => item.ItemCode)</td>
<td>#Html.TextBoxFor(m => item.ItemCode))</td>
</tr>
<tr>
<td><input type="submit" value="Approve" /></td>
</tr>
</table>
}
}
However in the controller action method parameter I get both ItemId and ItemCode as 0 and null respectively. Whats the problem here, could anyone help please? How can we pass the viewmodel to action using ajax?
The problem is most likely that the Model Binder in the view doesn't know how to bind your list of ApproveItemViewModel's.
See this answer for an explanation: https://stackoverflow.com/a/21191624/2521893
#model IEnumerable<ApproveItemViewModel>
#for (int i = 0; i < Model.Count; i++)
{
using (Ajax.BeginForm("ApproveItem", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "dane"
}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table>
<tr>
<td>#Html.LabelFor(m => Model[i].ItemId)</td>
<td>#Html.DisplayFor(m => Model[i].ItemId)</td>
</tr>
<tr>
<td>#Html.LabelFor(m => Model[i].ItemCode)</td>
<td>#Html.TextBoxFor(m => Model[i].ItemCode))</td>
</tr>
<tr>
<td><input type="submit" value="Approve" /></td>
</tr>
</table>
}
}
Cause of this issue is 'parameter' name. You need to keep same name for both controller post action's parameter(ie, viewmodel) and iterating model(item) in cshtml side, then only binding should happened. See the two different solutions below.
Solution 1 : Modify cshtml page:-
Replace #foreach (var item in Model) with #foreach (var viewmodel in Model)
ie,
#foreach (var viewmodel in Model)
Solution 2 : Modify controller action:-Change ApproveItem action's parameter name.
ie,
[HttpPost]
public JsonResult ApproveItem(ApproveItemViewModel item)
{
return Json(new { success = success }, JsonRequestBehavior.AllowGet);
}
So.. i have a class that has has a List.
I pass it to the view like the code below:
[HttpGet]
[Authorize(Roles="user")]
[CustomChecker]
public ActionResult Index(int? page, int id=0)
{
EmployeeContext emp = new EmployeeContext();
student st = emp.students.Single(x=>x.id ==id);
#ViewBag.id = st.id;
return View(st.subjSel.ToPagedList(page ?? 1, 4));
}
And then the View will receive it like this:
#using PagedList;
#using PagedList.Mvc;
#model PagedList<MvcApplication6.Models.subject>
<div style="font-family:Arial">
<fieldset>
<legend><h3>Open Classes</h3></legend>
#using (Html.BeginForm("Test", "Enrollment"))
{
<input type="hidden" name="id" value="#ViewBag.id" />
<table border="1">
<tr>
<th>#Html.LabelFor(model => model[0].subj)</th>
<th>#Html.LabelFor(model => model[0].days)</th>
<th>#Html.LabelFor(model => model[0].cstart)</th>
<th>#Html.LabelFor(model => model[0].cend)</th>
<th>#Html.LabelFor(model => model[0].professor)</th>
<th>#Html.LabelFor(model => model[0].units)</th>
<th>#Html.CheckBox("test") Select all</th>
</tr>
#for (int i = 0; i < Model.Count; i++)
{
<tr>
#Html.HiddenFor(model => model[i].id)
<td>
#Html.DisplayFor(m => m[i].subj)
#Html.HiddenFor(m => m[i].subj)
</td>
<td>
#Html.DisplayFor(m => m[i].days)
#Html.HiddenFor(m => m[i].days)
</td>
<td>
#Html.DisplayFor(m => m[i].cstart)
#Html.HiddenFor(m => m[i].cstart)
</td>
<td>
#Html.DisplayFor(m => m[i].cend)
#Html.HiddenFor(m => m[i].cend)
</td>
<td>
#Html.DisplayFor(m => m[i].professor)
#Html.HiddenFor(m => m[i].professor)
</td>
<td>
#Html.DisplayFor(m => m[i].units)
#Html.HiddenFor(m => m[i].units)
</td>
<td>
#Html.CheckBoxFor(m => m[i].isSelected)
</td>
</tr>
}
</table>
<br />
<br />
<table>
<tr><td align="center" width="500px"></td></tr>
<tr>
<td align="center" width="500px">
<input type="submit" value="submit" /> | <input type="button" value="clear" />
</td>
</tr>
</table>
<br />
<br />
}
</fieldset>
</div>
#Html.PagedListPager(Model, page => Url.Action("Index", "Enrollment", new { page, id = Request.QueryString["id"] }))
My problem is that it will be rendered like this [0].subj and that won't allow me to bind because it should be something like name[0].subj.
I've been experimenting and trying new methods, are there any ways for me to bind them properly? i want to use Html Helpers and as much as possible, i don't want to re-implement a custom one just for this part.
This is the function where they are supposed to be bound. This class has a List of students (the one that i converted to IPagedList)
[HttpPost]
[Authorize(Roles="user")]
public ActionResult Test(student st)
And this is how my View looks like. I am using CheckBoxFor for selections.
Extra question:
How come my navigation looks so ugly?
The model is the view is #model PagedList<subject> which means the parameter in the must be
[HttpPost]
public ActionResult Test(IEnumerable<subject> model)
If you also need the student ID property, then include an additional parameter
[HttpPost]
public ActionResult Test(IEnumerable<subject> model, int ID)
Since you GET method has a parameter int ID and assuming your using the default routing then the ID will be added to the forms action attribute i.e. it will render action="/Enrollment/Test/2" assuming the value of the ID is 2. If not, you can add this as a route parameter
#using (Html.BeginForm("Test", "Enrollment", new { ID = ViewBag.id }))
Alternatively you can use a view model
public class StudentVM
{
public int ID { get; set; }
public PagedList<student> Students { get; set; }
}
and in the GET method
public ActionResult Index(int? page, int id=0)
{
EmployeeContext emp = new EmployeeContext();
student st = emp.students.Single(x=>x.id ==id);
StudentVM model = new StudentVM()
{
ID = id,
Students = st.subjSel.ToPagedList(page ?? 1, 4)
};
return View(model);
}
and base you view on the view model and post it back to public ActionResult Test(StudentVM model)
To get your list of subject back, you should apply a suitable prefix to it. Since the name of property of Student that contains List<subject> is subjSel and you want to get values in your action in a Student object, so you should set prefix like this, before for statement:
#{ViewData.TemplateInfo.HtmlFieldPrefix = "subjSel";}
So fileds of model will be rendered with names like subjSel[0].fieldname.
Then in your post action you will receive subjects in subjSel property of st.
Additional Resource:
Model Binding to a List
For your extra question:
Check if your rendered code has suitable style and your style tags related to paging has been rendered in page.
I am having difficulty passing an IEnumerable as a model. The data is populating a form on one page - and doing so correctly. Upon submission the model returns as null.
I've seen various posts on this and they mostly reference naming-conventions so I have attempted different methods of naming the parameters to try to avoid any confusion in the model binding.
I have also tried various models and helpers to try and pass the data and all have the same result.
Current implementation:
Models:
public class UserProfileListModel
{
public IEnumerable<UserProfileViewModel> UserProfileViewModels { get; set; }
}
public class UserProfileViewModel
{
public UserProfile UserProfile { get; set; }
public Role UserRole { get; set; }
public Team UserTeam { get; set; }
public Scope UserScope { get; set; }
}
View:
#model Project.WebUI.Models.UserPRofileListModel
SNIP
<fieldset>
<legend>Administrate Users:</legend>
<table class="adminTbl">
<thead>
<tr>
<th>UserName:</th>
<th>Role:</th>
<th>Team:</th>
<th>Scope:</th>
<th>Update:</th>
<th>Delete:</th>
</tr>
</thead>
<tbody>
#{foreach (var user in Model.UserProfileViewModels)
{
<tr>
<td>
<p>#user.UserProfile.UserName
#{if (!user.UserProfile.Membership.IsConfirmed)
{
using (Html.BeginForm("Confirm", "Account", FormMethod.Post, null)){
#Html.AntiForgeryToken()
#Html.Hidden("Token", user.UserProfile.Membership.ConfirmationToken)
#Html.Hidden("Name", user.UserProfile.UserName)
}
<input type="submit" value="Confirm" />}
}
</p>
</td>
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(u => user.UserProfile)
if (user.UserProfile.UserName != User.Identity.Name && user.UserProfile.Membership.IsConfirmed)
{
<td>
#Html.DropDownListFor(u => user.UserRole, Project.WebUI.Controllers.AccountController.RoleList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserTeam, Project.WebUI.Controllers.AccountController.TeamList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserScope, Project.WebUI.Controllers.AccountController.ScopeList, new { #class = "formdrop" })
</td>
<td>
<input type="submit" value="Save Changes" onclick="return confirm('Are you sure you wish to update this user? ')" />
</td>
}
else
{
/*If user is self or not yet confirmed these are here to buffer the delete button into the last cell*/
<td></td>
<td></td>
<td></td>
<td></td>
}
}
}
<td>
#Html.ActionLink("Delete", "Delete", new { user.UserProfile.UserId }, new
{
onclick = "return confirm('Warning: Action cannot be undone. Are you sure you wish to permanently delete this entry?')"
})
</td>
</tr>
}
}
</tbody>
</table>
</fieldset>
Controller:
Populate View:
public ActionResult AdministrateUsers()
{
populateLists();
var query = repository.UserProfiles.OrderBy(e => e.UserName);
List<UserProfileViewModel> list = new List<UserProfileViewModel>();
foreach(UserProfile up in query)
{
UserProfileViewModel vm = new UserProfileViewModel() { UserProfile = up };
list.Add(vm);
}
UserProfileListModel models = new UserProfileListModel()
{
UserProfileViewModels = list.OrderBy(up => up.UserProfile.UserName)
};
return View(models);
}
Accept Post:
public ActionResult SaveUserChanges(UserProfileListModel model)
{
foreach (UserProfileViewModel upvm in model.UserProfileViewModels)
{
UserProfile up = new UserProfile()
{
UserId = upvm.UserProfile.UserId,
UserEmail = upvm.UserProfile.UserName,
UserName = upvm.UserProfile.UserName
};
if (ModelState.IsValid)
{
repository.SaveUserProfile(up);
}
else
{
return View(model);
}
}
return RedirectToAction("Index", "Admin");
}
The code does still need a lot of work but I can't get past getting the model back to the controller on post. I have also tried returning the UserProfileViewModel instead of the entire list.
Can anyone tell what I am doing wrong?
Thanks!
You have a lot of invalid html including form elements as child elements of tr elements and duplicate id attributes. If you want to post back UserProfileListModel then you need a single form element and use an EditorTemplate or a for loop (not foreach) to render the controls so they are correctly named with indexers.
You are also trying to bind your dropdown lists to complex objects (for example UserProfile, Role etc.). <select> elements (and all form controls) only post back key/value pairs so you need to bind to a value type (for example UserProfile.UserId).
Your SaveUserChanges() post method is also trying access properties of UserProfile but you don't even have controls for properties of UserProfile in the form that post back to this method (for example UserId = upvm.UserProfile.UserId, UserEmail = upvm.UserProfile.UserName, ...) so they will always be null.
You probalby need to bind properties in POST method like here:
public ActionResult Create([Bind(Include = "Id,Subject,Text,IsImportant")] Announcment announcment) {... }
So it will be:
public ActionResult SaveUserChanges([Bind(Include = "UserProfile,Role,UserTeam,UserScope")]UserProfileListModel model)
Have you specified your action method is for HTTP Post? And change your action method to accept UserProfileViewModels instead.
[HttpPost]
public ActionResult SaveUserChanges(UserProfileViewModels model)
{
You are also only posting back one model: UserProfileViewModels.
You have your form in your foreach loop, so each UserProfileViewModels has its own form. If you want to change it to post back your UserProfileListModel, move
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
outside of your foreach.
I got a stored procedure which i show in the view for edits. I made a strong type of the stored procedure. When i edit the fields and then press the save button, the parameter "cm" is always empty. And it's not showing a list but just 1 record.
The custom model:
public class CustomModel
{
public string Description { get; set; }
public System.Data.Linq.ISingleResult<GetItems_ListResult> ItemList { get; set;}
}
This part of the controller sends it to the view:
public ActionResult Details(int id)
{
var row = dataContext.Items.FirstOrDefault(x => x.ItemID == id);
var cm = new CustomModel();
cm.ItemList = dataContext.GetItem_List(row);
cm.Description = row.Description;
return View(cm);
}
This controller receives data from the view:
[HttpPost]
public ActionResult UpdateItems(CustomModel cm)
{
return RedirectToAction("Index");
}
This is the view:
#model TestWeb.Models.CustomModel
#using (Html.BeginForm("UpdateItems", "Item", FormMethod.Post))
{
<table>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
#foreach (var p in Model.ItemList.ToList())
{
<tr>
<td>
#Html.HiddenFor(mdl => p.ItemId)
</td>
<td>#p.Name</td>
<td>
#Html.EditorFor(mdl => p.Description)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="save" />
</p>
}
What am i doing wrong here?
Try the following:
Make a GetItems_ListResult.cshtml like this:
<tr>
<td>
#Html.HiddenFor(mdl => mdl.ItemId)
</td>
<td>#Model.Name</td>
<td>
#Html.EditorFor(mdl => mdl.Description)
</td>
</tr>
Then in your for loop do this:
#for (int i = 0; i < Model.ItemList.Count(); i++)
{
#Html.EditorFor(m => m.ItemsList[i])
}
Update: I didn't quite notice you were using an ISingleResult. You could do this instead:
//Since it'll have none or one element..
if(Model.ItemList != null && Model.ItemList.Any())
{
#Html.EditorFor(m => m.ItemList.First())
}
Have you read this blog post? http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ Steve covers editing lists in asp mvc.
Once your read that take a look at this nuget package http://nuget.org/packages/BeginCollectionItem