Html.BeginForm passing Model item value - c#

Am using mvc4 and am calling another controller in my view using Html.BeginForm
It work fine!but here am using textbox to pass the value.
How to modify this code so am using
#Html.DisplayFor(modelItem => item.UserName)
....instead of
#Html.TextBox("UserName")
here my view :
image of it:
#using OTMS.Models
#model IEnumerable<OTMS.Models.UserProfile>
#{
ViewBag.Title = "Index";
}
<!-- Table Continer -->
<div class="spacer_10px"></div>
<div class="container clearfix">
<div class="grid_12">
<div class="table_wrapper table_gray">
<table>
<tr>
<th>
<p>User Name</p>
</th>
<th>
<p>Role</p>
</th>
<th>
<p>Role</p>
</th>
</tr>
#if (Model != null) {
foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
#using(Html.BeginForm("GetRoles", "Account",FormMethod.Post)){
#Html.AntiForgeryToken()
<div class="editor-label">Username : </div>
#Html.TextBox("UserName") //here user will enter user name / I dont want user to enter that ,it should be done Automatically
<div class="spacer_20px"></div>
<div class="button button-orange"> <span class=" form_button clearfix">
<input type="submit" class="submit" name="submit" value="Get Roles for this User" />
</span> </div>//by clicking that will pass the user name to controller (GerRole)/I dont want button
}
</td>
<td>
#using (Html.BeginForm("Submit", "Account", FormMethod.Post))
{
#Html.Hidden("userName", item.UserName)
#Html.DropDownList("selectedRole", (SelectList)ViewBag.Roles)
<div class="button button-orange"> <span class=" form_button clearfix">
<input type="submit" class="submit" name="submit" value="Update Change" />
</span> </div>
}
</td>
</tr>
}
}
</table>
</div> </div>
here my controller :
public ActionResult Index()
{
var model = _db.UserProfiles.ToList();
ViewBag.Roles = new SelectList(Roles.GetAllRoles());
return View(model);
}
[HttpPost]
public ActionResult GetRoles(string UserName)
{
if (!string.IsNullOrWhiteSpace(UserName))
{
ViewBag.RolesForThisUser = Roles.GetRolesForUser(UserName);
SelectList list = new SelectList(Roles.GetAllRoles());
ViewBag.Roles = list;
}
return View("showrole");
}
another view:
image of it :
#{
ViewBag.Title = "showrole";
}
<h2>showrole</h2>
#if(ViewBag.RolesForThisUser != null) {
<text>
<h3>Roles for this user </h3>
<ol>
#foreach (string s in ViewBag.RolesForThisUser){
<li>#s</li>
}
</ol>
</text>
}

What you definetely need to do is to create a view model for your view, for me it looks something like this:
public class UserViewModel
{
public string UserName {get;set;}
public IEnumerable<string> UserRoles { get; set; }
}
Then in your index action you would return a list of these view models.
You certainly could do it like this:
public ActionResult Index()
{
var model = _db.UserProfiles.ToList()
.Select(u => new UserViewModel{
UserName = u.UserName,
UserRoles = Roles.GetRolesForUser(u.UserName)
.AsEnumerable()
})
.ToList();
ViewBag.Roles = new SelectList(Roles.GetAllRoles());
return View(model);
}
but I wouldn't. It's because with this code you're doing one aditional query for every user just to get his roles. I think you need to add roles table to your EntityFramework model and try to do this with single query. So you need to extend your UserProfile with roles:
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public ICollection<UserRoles> UserRoles { get; set; }
}
[Table("webpages_Roles")]
public class UserRoles
{
[Key]
public int RoleId { get; set; }
public string RoleName { get; set; }
public ICollection<UserProfile> UserProfiles { get; set; }
}
Then update your DbContext with info about many to many relationship between UserProfils and UserRoles:
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserRoles>()
.HasMany<UserProfile>(r => r.UserProfiles)
.WithMany(u => u.UserRoles)
.Map(m =>
{
m.ToTable("webpages_UsersInRoles");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
}
}
After that in your index action - you can simply do:
var model = _db.UserProfiles.Select(u => new UserViewModel()
{
UserName = u.UserName,
UserRoles = u.UserRoles.Select(ur=>ur.RoleName)
}).ToList();
And it will be one query instead of several in cycle.
EDIT:
Your model changed so you need to change #model IEnumerable<OTMS.Models.UserProfile> to #model IEnumerable<OTMS.Models.UserViewModel>
And then:
#foreach(var user in Model)
{
//display user
#foreach(var role in user.UserRoles)
{
//display roles with #role
}
}
If you want to use DisplayTemplates - you can move logic for displying user into template. For this you need to create view by path
~/Views/Shared/DisplayTemplates/UserViewModel.cshtml
#model OTMS.Models.UserViewModel
//display user
#foreach(var role in user.UserRoles)
{
//display roles with #role
}
then in Index.cshtml you can change code to this:
#foreach (var user in Model)
{
#Html.DisplayFor(n => user)
}

First and for All ,The Main Confusion is with this Helper..have a Breif Look here
Use HiddenFor when you want to provide posted data that the user does not need to be aware of."
Use DisplayFor when you want to show records but not allow them to be editted.
Use TextBoxFor when you want to allow user input or allow the user to edit a field.
`
Now your question is Like this..
How can i use displayfor to hit my controller!!!!
You could accomplish this with a duo of HiddenFor and DisplayFor. Use HiddenFor to have the values ready to be posted, and DisplayFor to show those values.
so to meet your Requirement
<div class="editor-label"> Username : </div>
#Html.TextBox("UserName")
Replace
<div class="editor-label"> Username : </div>
#Html.HiddenFor(modelItem=>item.username)
#Html.DisplayFor(modelItem=>item.username)
Remember Displayfor Renders Only Label In the Browser,to post it back to Controller you need HiddenFor

Try this:
Controller
[HttpPost]
public ActionResult GetRoles(string UserName)
{
if (!string.IsNullOrWhiteSpace(UserName))
{
ViewBag.RolesForThisUser = Roles.GetRolesForUser(UserName);
SelectList list = new SelectList(Roles.GetAllRoles());
ViewBag.Roles = list;
}
return View("......");
}
View
#ViewBag.Name
#using(Html.BeginForm("GetRoles", "Account")){
#Html.AntiForgeryToken()
<div class="editor-label">Username : </div>
#Html.TextBox("UserName")
<div class="spacer_20px"></div>
<div class="button button-orange">
<span class=" form_button clearfix">
<input type="submit" class="submit" name="submit" value="Get Roles for this User" />
</span>
</div>
}
Here is the DEMO

Related

ASP.NET MVC - View is posting null model

I have been trying to call a POST action from my view. I see POST method parameter is always null. What I am doing wrong ?
My model class :
public class Survey
{
public int Id { get; set; }
public int QuestionId { get; set; }
public string QuestionText { get; set; }
public int ResponseId { get; set; }
public string ResponseText { get; set; }
public string Comments { get; set; }
}
My controller method:
[HttpPost]
public ActionResult Publish(List<Survey> surveyToSave)
{
if (ModelState.IsValid)
{
return View("Index");
}
//Save logc
return View("Index");
}
View code:
#model List<Survey>
#using (Html.BeginForm("Publish","Home",FormMethod.Post))
{
<div class="center-div">
#foreach (var item in Model)
{
<div id=#item.QuestionId class="Question">
<table>
<tr>
<td>#item.QuestionText</td>
</tr>
<tr>
<td>
<div>
<label>
#Html.RadioButtonFor(m => m.Find(i => i.QuestionId == item.QuestionId).ResponseText, "Not sure")
Not sure
</label>
</div>
<div>
<label>
#Html.RadioButtonFor(m => m.Find(i => i.QuestionId == item.QuestionId).ResponseText, "Agree")
Agree
</label>
</div>
</td>
</tr>
</table>
</div>
}
}
</div>
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Publish" class="btn btn-default" />
</div>
}
Most properties in model is supposed to be populated from view. Idea was to make the model in UI and submit that in post to save.
Your current view code is generating the input element like this, for all the items in the collection.
<input id="ResponseText" name="ResponseText" type="radio" value="Not sure" />
Model binding will work when the property structure/names matches with the form data posted to your method matches. The default model binder will look into the name attribute value of each item in the posted form data and tries to find a matching property in the class object you used as the method parameter and map the values. Since your HttpPost action methods parameter is List<Survey>, for model binding to work, your input element names should be some thing like this
<input name="[n].ResponseText" type="radio" value="Not sure" />
Where n is the index, starting from 0 to Model.Count-1
You can convert your view code to use a for loop and create input element with above name attribute value
#model List<Survey>
#using (Html.BeginForm("Publish", "Home", FormMethod.Post))
{
<div class="center-div">
#for (var i = 0; i < Model.Count; i++)
{
<div>
<label>#Model[i].QuestionText</label>
<label>#Html.RadioButtonFor(m => Model[i].ResponseText, "Not sure")No</label>
<label>#Html.RadioButtonFor(m => Model[i].ResponseText, "Agree")Agree </label>
</div>
#Html.HiddenFor(a => Model[i].QuestionId)
}
</div>
<input type="submit" value="Publish" class="btn btn-default" />
}

Return Viewmodel to HttpPost ActionResult

I'm trying to update my collection of users' roles in my ASP.NET Identity project, but I'm currently stuck because I'm getting a null UsersAndRolesDictionary property in the ViewModel sent to my [HttpPost] method.
Here is my ViewModel, UpdateUserRolesViewModel:
namespace Project_Name.Models
{
public class UpdateUserRolesViewModel
{
public IDictionary<ApplicationUser, ICollection<IdentityUserRole>> UsersAndRolesDictionary { get; set; } // <-- This is returning null currently
}
}
Here's my HomeController's methods:
[Authorize(Roles = "Admin")]
public ActionResult RoleManager()
{
ViewBag.Message = "Role Management Page";
var databaseContext = new ApplicationDbContext(); // Get the Database Context
var users = databaseContext.Users.Include(u => u.Roles); // Get all users from the Database and their Roles
var newDict = new Dictionary<ApplicationUser, ICollection<IdentityUserRole>>();
// Add each user and their roles to the dictionary
foreach (var user in users)
{
newDict.Add(user, user.Roles);
}
// Update the ViewModel with the collection of users and roles
var updateUserRolesViewModel = new UpdateUserRolesViewModel {UsersAndRolesDictionary = newDict};
return View(updateUserRolesViewModel);
}
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UpdateUsersRolesAsync(UpdateUserRolesViewModel updateUserRolesViewModel)
{
try
{
//TODO: Attempt to update the user roles or delete the user
return View("RoleManager");
}
catch
{
//TODO: Properly catch errors
return View("RoleManager");
}
}
Here is my View, RoleManager:
#using Project_Name.Models
#model UpdateUserRolesViewModel
#{
ViewBag.Title = "Role Manager";
var databaseContext = new ApplicationDbContext(); // Get the Database Context
var roles = databaseContext.Roles; // Get all Roles from the database, use this to compare against
}
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
#using (Html.BeginForm("UpdateUsersRolesAsync", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-group">
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Email</th>
<th>Roles</th>
<th>Delete User?</th>
</tr>
</thead>
<tbody>
#{
int i = 0; // Used to make unique IDs for the user's table row, and deleteUserCheckbox
int j = 0; // Used to make unique IDs for the role checkboxes
foreach (var user in Model.UsersAndRolesDictionary.Keys)
{
i++;
<tr id="userTableRow_#i">
<td>#user.Email</td>
<td>
#* Show each role availabe as a checkbox. Check them if the user has that role. *#
#foreach (var role in roles)
{
#Html.CheckBox("userRoleCheckbox_" + j++, user.Roles.Any(identityUserRole => identityUserRole.RoleId.Contains(role.Id)))
<span>#role.Name</span>
<br />
}
</td>
<td>
#Html.CheckBox("deleteUserCheckbox_" + i)
<span>Delete User</span>
</td>
</tr>
}
}
</tbody>
</table>
</div>
#* Reset and Submit buttons *#
<div class="col-lg-2 col-lg-push-8 col-md-2 col-md-push-8 col-sm-2 col-sm-push-8 col-xs-2 col-xs-push-8">
<input type="reset" class="btn btn-danger btn-block" value="Reset" />
</div>
<div class="col-lg-2 col-lg-push-8 col-md-2 col-md-push-8 col-sm-2 col-sm-push-8 col-xs-2 col-xs-push-8">
<input type="submit" class="btn btn-primary btn-block" value="Submit" />
</div>
</div>
}
</div>
</div>
I'm using the dictionary UsersAndRolesDictionary to collect all the users and their roles, then enumerating through that to produce my view in the form of a table.
I'm hoping to change the checkbox values of potential multiple users, then passing that updated ViewModel to my [HttpPost] UpdateUsersRolesAsync method in order to update my user roles, but right now I'm getting a null value for the UsersAndRolesDictionary property and I'm not sure why or how to fix it.
Thanks to Stephen Muecke's links/answers in the comments I was able to answer this question. See my answer post below.
Following the suggestions of Stephen Muecke in the comments, I have gotten a valid ViewModel to be returned.
Added/updated three ViewModels that combine together:
The first being RoleViewModel:
public class RoleViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
Second being UserViewModel:
public class UserViewModel
{
public string Id { get; set; }
public string Email { get; set; }
public List<RoleViewModel> RoleViewModels { get; set; }
public bool DeleteUser { get; set; } // Doesn't work yet, might be in the wrong place
}
And finally the third being an updated version of UpdateUserRoleViewModel:
public class UpdateUserRolesViewModel
{
public int Id { get; set; }
public List<UserViewModel> UserViewModels { get; set; }
}
In my updated HomeController are the methods again:
[Authorize(Roles = "Admin")]
public ActionResult RoleManager()
{
ViewBag.Message = "Role Management Page";
var databaseContext = new ApplicationDbContext(); // Get the Database Context
var users = databaseContext.Users.Include(u => u.Roles).ToList(); // Get all users from the Database and their Roles
// Create the UpdateUserRolesViewModel
var updateUserRolesViewModel = new UpdateUserRolesViewModel
{
Id = 0, // Not sure what else the Id would be
UserViewModels = new List<UserViewModel>()
};
// Add each user to the UserViewModels list
for (int i = 0; i < users.Count(); i++)
{
var userViewModel = new UserViewModel
{
Id = users.AsEnumerable().ElementAt(i).Id,
Email = users.AsEnumerable().ElementAt(i).UserName,
RoleViewModels = new List<RoleViewModel>(),
DeleteUser = false
};
// Add each role from the Roles table to the RoleViewModels list, check if user has that role
foreach (var role in databaseContext.Roles)
{
var roleViewModel = new RoleViewModel
{
Id = role.Id,
Name = role.Name,
IsSelected = users.AsEnumerable().ElementAt(i).Roles.Any(identityUserRole => identityUserRole.RoleId.Contains(role.Id))
};
userViewModel.RoleViewModels.Add(roleViewModel);
}
updateUserRolesViewModel.UserViewModels.Add(userViewModel);
}
return View(updateUserRolesViewModel);
}
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UpdateUsersRolesAsync(UpdateUserRolesViewModel updateUserRolesViewModel)
{
try
{
// Attempt to update the user roles
foreach (var user in updateUserRolesViewModel.UserViewModels)
{
// Delete user
//TODO: Prompt user to confirm deletion if one or more people are being deleted
if (user.DeleteUser)
{
var userToDelete = await UserManager.FindByIdAsync(user.Id); // Get the ApplicationUser object of who we want to delete
await UserManager.DeleteAsync(userToDelete); // Delete the user
continue; // Don't try to update the roles of a deleted user.
}
// Remove all roles from the User
var rolesToRemove = await UserManager.GetRolesAsync(user.Id);
await UserManager.RemoveFromRolesAsync(user.Id, rolesToRemove.ToArray());
// Add roles to the User
foreach (var roleViewModel in user.RoleViewModels.Where(r => r.IsSelected))
{
await UserManager.AddToRoleAsync(user.Id, roleViewModel.Name);
}
}
return RedirectToAction("RoleManager");
}
catch
{
//TODO: Properly catch errors
return RedirectToAction("RoleManager");
}
}
Finally, here is my View, RoleManager
#using Project_Name.ViewModels
#model UpdateUserRolesViewModel
#{
ViewBag.Title = "Role Manager";
}
#* Debugging text *#
#foreach (var user in Model.UserViewModels)
{
<div>User ID: #user.Id</div>
<div>User Name: #user.Email</div>
<p>
#foreach (var roleViewModel in user.RoleViewModels.Where(r => r.IsSelected))
{
<div>Role ID: #roleViewModel.Id</div>
<div>Role Name: #roleViewModel.Name</div>
}
</p>
<hr />
}
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
#using (Html.BeginForm("UpdateUsersRolesAsync", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Id)
<div class="form-group">
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Email</th>
<th>Roles</th>
<th>Delete User?</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.UserViewModels.Count; i++)
{
<tr id="userTableRow_#i">
<td>
#Html.HiddenFor(m => m.UserViewModels[i].Id)
#Html.HiddenFor(m => m.UserViewModels[i].Email)
#Model.UserViewModels[i].Email
</td>
<td>
#for (int j = 0; j < Model.UserViewModels[i].RoleViewModels.Count; j++)
{
#Html.HiddenFor(m => m.UserViewModels[i].RoleViewModels[j].Id)
#Html.HiddenFor(m => m.UserViewModels[i].RoleViewModels[j].Name)
#Html.CheckBoxFor(m => m.UserViewModels[i].RoleViewModels[j].IsSelected)
#Html.DisplayTextFor(m => m.UserViewModels[i].RoleViewModels[j].Name)
<br/>
}
</td>
<td>
#Html.CheckBoxFor(m => m.UserViewModels[i].DeleteUser)
#Html.DisplayNameFor(m => m.UserViewModels[i].DeleteUser)
</td>
</tr>
}
</tbody>
</table>
</div>
#* Reset and Submit buttons *#
<div class="col-lg-2 col-lg-push-8 col-md-2 col-md-push-8 col-sm-2 col-sm-push-8 col-xs-2 col-xs-push-8">
<input type="reset" class="btn btn-danger btn-block" value="Reset" />
</div>
<div class="col-lg-2 col-lg-push-8 col-md-2 col-md-push-8 col-sm-2 col-sm-push-8 col-xs-2 col-xs-push-8">
<input type="submit" class="btn btn-primary btn-block" value="Submit" />
</div>
</div>
}
</div>
</div>
This now updates the user's Roles, and Deletes them (though there is no confirmation check so be careful with that!)

Submitting an actionLink to a form mvc4

We have a list of action links
Partial View
#foreach (var item in Model.Regions) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.RegionName)
</td>
<td>
<input type="submit" value="Select" />
</td>
#Html.HiddenFor(modelItem => Model.Id)
</tr>
}
</table>
I assume that this isn't the correct way to do this, but if you could point me in the right direction it would be appreciated. I want to submit this data into an existing form
Region View
#using (Html.BeginForm()){
<fieldset>
#Html.Partial("_RegionsPartial");
<legend>Create new region</legend>
<ol>
<li>#Html.LabelFor(m => m.RegionName)</li>
<li>#Html.EditorFor(m => m.RegionName)</li>
</ol>
<input type="submit" value="Next" />
#Html.HiddenFor(model => model.RegionId)
</fieldset>
}
So you can either submit a new one or submit an existing one. Im not sure how to get the id of an existing one into my model. Here is the controller:
public ActionResult Region()
{
var model = new WizardModel();
var getRegions = _facade.FetchRegion();
model.Regions = getRegions;
return View(model);
}
[HttpPost]
public ActionResult Region(WizardModel model)
{
if (model.RegionName != null)
{
var newRegion = _facade.CreateRegion(model.RegionName);
model.RegionId = newRegion.Id;
}
else
{
model.RegionName = _facade.FetchRegion(model.RegionId).RegionName;
}
TempData["suburbModel"] = model;
return RedirectToAction("Suburb");
}
Thanks for taking the time
So heres my example of passing an instance of a model. I've got a view with many courses so I need to click a button and fire an action, thus carrying all data (including relevant ID) of the course clicked. So in the end I carry the instance I need with the hidden fields.:)
My course model...
public class CourseModel
{
public int RecordId { get; set; }
public string StudentNameField { get; set; }
public string SubjectField { get; set; }
public string CatalogField { get; set; }
public string SectionField { get; set; }
public string InstrNameField { get; set; }
public string MtgStartField { get; set; }
public string MtgEndField { get; set; }
}
My main View...Called "CourseList" in Views folder
<div id="container">
<div class="selectLabel">Select a Course:</div><br />
#foreach (var item in Model)
{
#Html.DisplayFor(model=>item)
}
</div>
My Display template - Its a view called "CourseModel" in Shared\DisplayTemplates ...For your display template, you could make a unique model for existing & new. Using your "existing" model in the displaytemplate, it results in multiple forms, each using a button type=submit to submit the form with model instance. Use CSS to model the button like a link. If you still need to use actionlink, carry the iD as one of the params.
#using LecExamRes.Helpers
#model LecExamRes.Models.SelectionModel.CourseModel
#using (Html.BeginForm("CourseList", "Home", null, FormMethod.Post))
{
<div class="mlink">
#Html.AntiForgeryToken()
#Html.EncryptedHiddenFor(model => model.RecordId)
#Html.EncryptedHiddenFor(model => model.CatalogField)
#Html.EncryptedHiddenFor(model => model.SectionField)
#Html.EncryptedHiddenFor(model => model.SubjectField)
#Html.EncryptedHiddenFor(model => model.InstrNameField)
#Html.EncryptedHiddenFor(model => model.MtgStartField)
#Html.EncryptedHiddenFor(model => model.MtgEndField)
<p>
<input type="submit" name="gbtn" class="groovybutton" value="#Model.SubjectField - #Model.CatalogField - #Model.SectionField : #Model.InstrNameField">
</p>
</div>
}
My controller, Courselist [POST] Action...
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult CourseList(SelectionModel.CourseModel model)
{
//....do something with my model
}

MVC4 Selecting from a List of Items in a view

I'm looking for a way to select from a lists of items that I have retrieve from a database. I send these items to a view and I want to select from the list and return this to the controller to populate a secondary table within the database. I can pass the items to the view and get them to display but I can not seem to pass these items back to the controller.
Controller Calls (Updated Again):
public ActionResult Create()
{
var myMeal = new CreateMeal();
List<ProductsToInclude> pti = new List<ProductsToInclude>();
myMeal.ProductsToInclude = pti;
IList<Product> prods = db.Products.ToList();
foreach(var prod in prods)
{
pti.Add(new ProductsToInclude { Product = prod });
}
return View(myMeal);
}
//
// POST: /Meal/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateMeal myMeal)
{
if (ModelState.IsValid)
{
/* Add code to handle my meal and create a meal for data base*/
db.Meals.Add(meal);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(pti);
}
The ProductsToInclude ViewModel
public class ProductsToInclude
{
public Product Product { get; set; }
public Boolean Include { get; set; }
}
New CreateMeal ViewModel:
public class CreateMeal
{
public String Name { get; set; }
public IList<ProductsToInclude> ProductsToInclude { get; set; }
}
The Create View:
#model MealPlanner.ViewModels.CreateMeal
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Meal</legend>
<div>
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
</div>
<div>
#Html.EditorFor(m => m.ProductsToInclude)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And the Editor Templete (UpDated) :
#model MealPlanner.ViewModels.ProductsToInclude
<tr>
<td>
#Html.CheckBoxFor(m => m.Include)
</td>
<td>
#Model.Product.Name
#Model.Product.Quantity
#Model.Product.unit.UnitName
#Html.HiddenFor(m => m.Product.Name)
#Html.HiddenFor(m => m.Product.Quantity)
#Html.HiddenFor(m => m.Product.unit.UnitName)
</td>
</tr>
Meal Model:
public class Meal
{
public int MealId { get; set; }
public String Name { get; set; }
//public virtual IList<Product> Products { get; set; }
public int UserId { get; set; }
}
UPDATE:
Switching over to EditorTemplete I can not get this to display. I get an error now with myMeal.ProductsToInclude.Add(new ProductsToInclude { Product = prod, Include = false}); in the Create Method. prod is populated has 8 products in it.
You have two basic problems here. (and a third, you're not passing any model to the view, but your view is expecting a Meal object) First, is that you are posting a different model than you are rendering from. So, when you post your form it's not going to recognize the Meal.Name object because the post action is expecting a list of ProductsToInclude.
If you don't intend to change the value of the Meal.Name, then I would suggest rendering it with a DisplayFor rather than an EditorFor.
Second, the way you are rendering your form fields will not create the correct naming structure to allow the default model binder to bind to the collections. You can use EditorTemplates, which is the method I prefer.
Instead of using the Partial, you would create an editor template called ProductsToInclude.cshtml, set the model type to be ProductsToInclude (without the List) and then render as if it were a single item. You will also want to move the table and header info out into the main view.
EditorTemplates handle this situation without having to do anything special. They automatically iterate over collections, and they render names correctly. Use them wherever you can.
<div>
<table>
<tr>
<th>
#Html.DisplayName("Include")
</th>
<th>
#Html.DisplayName("Available Products")
</th>
<th></th>
</tr>
#Html.EditorFor(m => m.ProductsToInclude)
</table>
</div>
Your ProductsToInclude.cshtml should live in a folder called EditorTemplates in either ~/Views/Shared or in your local view folder.
#model ProductsToInclude
<tr>
<td>
#Html.CheckBoxFor(m => m.Include)
</td>
<td>
#Html.DisplayFor(m => m.Product.Name)
#Html.DisplayFor(m => m.Product.Quantity)
#Html.DisplayFor(m => m.Product.unit.UnitName)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=Model.PrimaryKey }) |
#Html.ActionLink("Details", "Details", new { id=Model.PrimaryKey }) |
#Html.ActionLink("Delete", "Delete", new { id=Model.PrimaryKey })
</td>
</tr>

How to have List Objects retained in the model on HttpPost?

I have a Model that contains a List of a custom type.
I want the data from this type to be passed back in when a model is submitted as a HttpPost call the the controller.
However, it does not seem to do what I want. I've got where I am so far by following Passing IEnumerable or list Model to Controller using HttpPost but I'm having a problem.
My controller method:
[HttpPost]
public ActionResult UpdateStock(int id, ProductModel model)
{
return View("UpdateStock", model);
}
Now, the View is like this (trimmed):
#using (Html.BeginForm())
{
<div>
<p>
<input type="submit" value="Save" />
</p>
#Html.HiddenFor(m => m.ProductNo)
<div class = "title">
#Html.LabelFor(m => m.ProductName)
#Html.EditorFor(m => m.ProductName)
</div>
#for ( int i = 0; i < Model.Stock.Count; i++ )
{
var item = Model.Stock[i];
<div class="editor-field">
<input type="text" name="Model.Stock[#i].Key"
value="#item.Key" />
</div>
<div class="editor-field">
<input type="text" name="Model.Stock[#i].Value"
value="#item.Value" />
</div>
}
}
My problem is, that it seems the #Html.EditorFor() and <input type=.../> tags don't seem to play well with each other. If I have it like above, then the ProductNo and other properties using #Html methods won't be passed through to the model.
Any advice much appreciated.
I would simply use editor templates:
Model:
public class ProductModel
{
public string ProductNo { get; set; }
public string ProductName { get; set; }
public IEnumerable<Stock> Stocks { get; set; }
}
public class Stock
{
public string Key { get; set; }
public string Value { get; set; }
}
Controller:
public class HomeController: Controller
{
public ActionResult Index()
{
var model = new ProductModel
{
ProductNo = "123",
ProductName = "p name",
Stocks = new[]
{
new Stock { Key = "key1", Value = "value1" },
new Stock { Key = "key2", Value = "value2" },
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(ProductModel model)
{
...
}
}
View:
#model ProductModel
#using (Html.BeginForm())
{
<p>
<input type="submit" value="Save" />
</p>
#Html.HiddenFor(m => m.ProductNo)
<div class = "title">
#Html.LabelFor(m => m.ProductName)
#Html.EditorFor(m => m.ProductName)
</div>
#Html.EditorFor(x => x.Stocks)
}
and then you define a custom editor template for the Stock type (~/Views/Shared/EditorTemplates/Stock.cshtml):
#model Stock
<div class="editor-field">
#Html.EditorFor(x => x.Key)
</div>
<div class="editor-field">
#Html.EditorFor(x => x.Value)
</div>

Categories