How to bind List<X> to ViewModel - asp.net-mvc

I have 2 ViewModels - User and Reminder.
public class UserViewModel
{
[Required]
[Display(Name = "Your name")]
public string Name { get; set; }
[Display(Name = "Your reminders")]
public IEnumerable<ReminderViewModel> Reminders { get; set; }
}
public class ReminderViewModel
{
[Required]
[Display(Name = "Time")]
public TimeSpan Time { get; set; }
[Required]
[Display(Name = "Frequency of repair")]
public string Frequency { get; set; }
}
My add-view:
#using (Html.BeginForm("Add", "Test"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#ViewBag.Status
<ul style="list-style: none;">
<li>
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
</li>
???????????? // Reminders
<li>
<input type="submit" value="Add" /></li>
</ul>
}
My question is: how can I bind this IEnumerable<ReminderViewModel> in my View?
I want to achieve situation, where user can put some reminders (selecting Time and Frequency) many times, before send click.
How can I do this?

You need to do the following:
First add a reference to the model in the top of the view like this:
#model UserViewModel
Including your namespace.
Change your collection to user a list so it can be indexed and model binded like this:
#for (var i = 0; i < Model.Reminders.Count(); i++)
{
#Html.LabelFor(m => Model.Reminders[i].Frequency)
#Html.TextBoxFor(m => Model.Reminders[i].Frequency)
#Html.ValidationMessageFor(m => Model.Reminders[i].Frequency)
#Html.LabelFor(m => Model.Reminders[i].Time)
#Html.TextBoxFor(m => Model.Reminders[i].Time)
#Html.ValidationMessageFor(m => Model.Reminders[i].Time)
}
This in your model:
[Display(Name = "Your reminders")]
public List<ReminderViewModel> Reminders { get; set; }
You also need to change your Begin form to set it to post like this:
#using (Html.BeginForm("Add", "Test", FormMethod.Post))
Finally you need to implement the post like this taking the model as a parameter:
[HttpPost]
public ActionResult Add(UserViewModel model)
{
// Do processing here
return View(model);
}

First, you need to change it to List<ReminderViewModel>. In order to get proper model binding you must be able to select the objects using an index value.
Then in your view:
#for (var i = 0; i < Model.Reminders.Count(); i++)
{
#Html.LabelFor(m => m.Reminders[i].Time)
#Html.TextBoxFor(m => m.Reminders[i].Time)
#Html.ValidationMessageFor(m => m.Reminders[i].Time)
#Html.LabelFor(m => m.Reminders[i].Frequency)
#Html.TextBoxFor(m => m.Reminders[i].Frequency)
#Html.ValidationMessageFor(m => m.Reminders[i].Frequency)
}
If Reminders is initially null, you'll need to initialize it with one or more ReminderViewModels in your action:
var model = new UserViewModel {
Reminders = new List<ReminderViewModel> {
new ReminderViewModel()
}
}
return View(model);
Or you can do this in your UserViewModel's constructor:
public class UserViewModel
{
public UserViewModel()
{
Reminders = new List<ReminderViewModel> {
new ReminderViewModel();
}
}
}

Related

MVC Razor - Hierarchy / Nested IList of Checkboxes Posting with Count = 0

I am trying to post back the changes to the nested list of checkboxes for Groups and their Users but keep getting my list Count = 0 when it posts. Right now, there are no groups within groups, but I would still like to make this recursive if we move towards that in the future.
I have a hierarchical IList of GroupsUsers attached to my Activity Model as such:
Activity:
public class Activity
{
public int ActivityId { get; set; }
public string Name { get; set; }
public string Path { get; set; }
public string Description { get; set; }
public Nullable<int> ParentId { get; set; }
public IList<GroupsUsers> Hierarchy { get; set; }
}
GroupsUsers:
public class GroupsUsers
{
public Guid? Guid { get; set; }
public string Name { get; set; }
public bool IsAllowed { get; set; } = false;
public IList<GroupsUsers> Children { get; set; } = new List<GroupsUsers>();
}
I have tried EditorFor, Partial View, and Helper but am having no luck with any of them posting back the Hierarchy. My Model.Hierarchy is posting back with Count = 0.
Here's my current attempt:
Main View (watered down):
#model MyProject.Models.Activity
#using (Html.BeginForm())
{
<!-- Activity stuff -->
<ul style="list-style:none;">
#for (var i = 0; i < Model.Hierarchy.Count(); i++)
{
#Html.EditorFor(model => model.Hierarchy[i])
}
</ul>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
Current Attempt. GroupsUsers.cshtml:
#model MyProject.Models.GroupsUsers
<li>
#Html.HiddenFor(model => model.Guid)
#Html.HiddenFor(model => model.Name)
#Html.CheckBoxFor(model => model.IsAllowed, new { #class = "groupsusers-checkbox", #style = "margin-right:5px; cursor:pointer;", #value = Model.Guid.ToString() }) #Html.LabelFor(model => model.IsAllowed, Model.Name, new { #class = "build-checkbox-label", #style = "font-weight:normal; margin-top:-2px;" })
#if (Model.Children.Any())
{
<ul style="list-style:none;">
#for (var i = 0; i < Model.Children.Count(); i++)
{
#Html.EditorFor(model => model.Children[i])
}
</ul>
}
</li>
I'm looking for my list of checkboxes to display as a list hierarchy recursively and post Model.Hierarchy back properly.
Any help would be appreciated... I only included Attempt #2 and #3 in case I was close to having it correct.

Adding features while creating User in mvc using checkbox

i want to create users with special features in mvc. when user is going to create i want to assign some special feature to each user like particular user having his own house, having his own car using checkbox selection. the particular feature is reside in different table named feature. then how can i add those features with user while creating the user.
i have created a view model named ViewModelUserWithFeature
public class ViewModelUserWithFeature
{
public User User { get; set; }
public Feature Feature { get; set; }
public List<Feature> feature { get; set; }
public IEnumerable<User> IUser { get; set; }
private UserDbContext userDbContext;
private IUserService userService;
public void ViewUserList()
{
userService = new RoleService(userDbContext);
IUser = userService.GetUsers();
}
public void AddNewUser(User userAdd)
{
userService = new UserService(userDbContext);
User = userService.AddUser(userAdd);
userService.SaveUser();
}
}
here is my view in which i want to two textboxes and a list of features which are going to select by checkbox and attached with the user.
#model App.ViewModel.ViewModelUserWithFeature
#using (Html.BeginForm("Create", "User", FormMethod.Post))
{
<div>
#Html.TextBoxFor(m => m.User.UserName)
#Html.ValidationMessageFor(m => m.UserName)
</div>
<div>
#Html.TextBoxFor(m => m.User.UserAddres)
#Html.ValidationMessageFor(m => m.UserAddres)
</div>
#for(int i=0; i < Model.Feature; i++)
{
<div class="cb"><input type="checkbox" name="checkbox"></div>
<div class="per-content">
<label for="1"> Model.Feature.FeatureName</div>
}
<div>
<button type="submit" id="btn-rd">Submit</button>
</div>
}
Controller
[HttpPost]
public ActionResult Create(User user)
{
ViewModelUserWithFeature viewModelUserWithFeature = new ViewModelUserWithFeature(usertDbContext);
if (ModelState.IsValid)
{
viewModelUserWithFeature.AddNewUser(user);
}
return RedirectToAction("Index", viewModelUserWithFeature);
}
not able to achieve that what i have tried so far i have mentioned . please help. thanks in advance.
Use view models to represent what you display and edit
public class FeatureVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class UserVM
{
public string Name { get; set; }
public string Address { get; set; }
public List<FeatureVM> Features { get; set; }
}
Controller
public ActionResult Create()
{
UserVM model = new UserVM();
model.Features = // map all available features
return View(model);
}
[HttpPost]
public ActionResult Create(UserVM model)
{
}
View
#model UserVM
#using(Html.BeginForm())
{
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
.....
for(int i = 0; i < Model.Features.Count; i++)
{
#Html.HiddenFor(m => m.Features[i].ID)
#Html.CheckBoxFor(m => m.Features[i].IsSelected)
#Html.LabelFor(m => m.Features[i].IsSelected, Model.Features[i].Name)
}
<input type="submit" value="Create" />
}
try with this, in you Model of Feature add a new property
public bool isFeatureOf { get; set; }
also in your model for the method AddNewUser change it to
public void AddNewUser(User userAdd,List<Feature> features)
{
userService = new UserService(userDbContext);
User = userService.AddUser(userAdd);
userService.SaveUser();
//featureService = new FeatureService(yourdbcontext)
foreach (Feature item in features)
{
//save to db
featureService.SaveFeature(item,User.Id);
//i don't know if in your database you already have a table,colum or something to map the features by user
}
}
then in your view
for(int index=0; index < Model.Features.Count(); index++)
{
#Html.HiddenFor(m=>Model.Features[index].NameFeature)
#Html.Raw(Model.Features[index].NameFeature)
#Html.CheckBoxFor(m=>Model.Features[index].isFeatureOf)
}
also in your view you'll need to change this
<div>
#Html.TextBoxFor(m => m.User.UserName)
#Html.ValidationMessageFor(m => m.UserName)
</div>
<div>
#Html.TextBoxFor(m => m.User.UserAddres)
#Html.ValidationMessageFor(m => m.UserAddres)
</div>
to:
<div>
#Html.TextBoxFor(m =>Model.User.UserName)
#Html.ValidationMessageFor(m => Model.User.UserName)
</div>
<div>
#Html.TextBoxFor(m => m.User.UserAddres)
#Html.ValidationMessageFor(m =>Model.User.UserAddres)
</div>
in your controller change your param to get the whole Model like this
[HttpPost]
public ActionResult Create(ViewModelUserWithFeature model)
{
if (ModelState.IsValid)
{
model.AddNewUser(model.User,model.Features);
}
return RedirectToAction("Index", viewModelUserWithFeature);
}
hope this can help you

Adding/Update Related Data ASP.NET MVC 5

I'm new to programming so I'm still learning.
I need to add Items to Grocery from a single view. But I can't get the data to save.
When I hit save I don't get any exceptions, the page just loads but nothing is saved to the
database. Can I get some help/guidance as to what I am doing wrong?
Data Class
public class Grocery
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Item> Items { get; set; }
}
public class Item {
public int ItemId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
View Model
public class GroceryViewModel
{
public Grocery Grocery { get; set; }
public virtual Item Item { get; set; }
public GroceryViewModel(int GroceryId)
{
using (var db = new MyContext())
{
Grocery = db.Groceries
.Include("Items")
.SingleOrDefault(a => a.Id == GroceryId);
}
}
}
Controller
public ActionResult Edit(int GroceryId, GroceryViewModel groceryViewModel)
{
var model = new GroceryViewModel(GroceryId);
var plusItems = new Item
{
Name = groceryViewModel.Item.Name,
Description = groceryViewModel.Item.Description,
};
db.Items.Add(plusItems);
db.SaveChanges();
return RedirectToAction(model);
View
#model Project.Models.GroceryViewModel
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Groceries</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Name)
#Html.ValidationMessageFor(model => model.Item.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<p>
<input type="submit" value="Add Item" />
</p>
</fieldset>
try
using (var db = new MyContext())
{
var grocery = db.Groceries.Single(a => a.Id == groceryId);
var plusItems = new Item
{
Name = groceryViewModel.Item.Name,
Description = groceryViewModel.Item.Description,
};
grocery.Items.Add(plusItems);
db.SaveChanges();
}

ASP.NET MVC Generic List of Different SubClasses

I have an MVC model with a property that contains a generic collection of types that inherit from a single type. It displays the editor exactly as I would expect, but when I post back the types of all the items are the base type. How do I get it to return the correct types?
Model...
public class PageEM {
public long Id { get; set; }
public virtual IList<FieldEM> Fields { get; set; }
}
public class FieldEM { // I'd really like this to be abstract.
public long Id { get; set; }
public string Caption { get; set; }
public string Value { get; set; }
}
public class TextFieldEM : FieldEM {
}
public class CheckBoxFieldEM : FieldEM {
public bool ValueData {
get { return (bool)Value; }
set { Value = (string)value; }
}
PageEM View...
#model PageEM
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Fields</legend>
#Html.HiddenFor(m => m.Id)
#Html.EditorFor(m => m.Fields)
<input type="submit" value="Submit" title="Submit" />
</fieldset>
}
TextFieldEM Editor...
#model TextFieldEM
<div>
#Html.HiddenForFor(m => m.Id)
<div>
#Html.LabelFor(m => m.Value, Model.Caption)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.Value)
#Html.ValidationMessageFor(m => m.Value)
</div>
</div>
CheckBoxFieldEM Editor...
#model CheckBoxFieldEM
<div>
#Html.HiddenForFor(m => m.Id)
<div class="editor-field">
#Html.EditorFor(m => m.DataValue)#Html.LabelFor(m => m.DataValue, Model.Caption, new { #class = "checkbox" })
</div>
</div>
Controller...
public partial class PageController : Controller {
public virtual ActionResult Edit() {
PageEM em = new PageEM() {
Id = 123,
Fields = new List<FieldEM>() {
new TextFieldEM() { Id = 1, Caption = "Text Line", Value = "This is test" },
new CheckBoxEM() { Id = 2, Caption = "Check here", ValueData = true }
}
};
return View(em);
}
[HttpPost]
public virtual ActionResult Edit(PageEM em) {
if (!ModelState.IsValid)
return View(em);
// but all of the em.Fields are FieldEM.
}
}
So how do I get it to post back with the subclassed FieldEMs?
You can't do that with the DefaultModelBinder. You'll have to create your own custom model binder in order to do what you want to do.
These might be helpful:
https://gist.github.com/joelpurra/2415633
ASP.NET MVC3 bind to subclass
ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism

Adding a Number of Items to a Model list?

I have a page with the same input box added a number of times.
#Html.TextBoxFor(m => m.Product)
#Html.TextBoxFor(m => m.Product)
#Html.TextBoxFor(m => m.Product)
How to I bind this to the Model.
I've tried:
public class Shop
{
public string ShopName { get; set; }
[Remote("ProductExists", "Account", AdditionalFields = "ShopName", ErrorMessage = "Product is already taken.")]
public List<String> Product { get; set; }
}
But I can only ever see the data in the first field. Also I tried:
#Html.TextBoxFor(m => m.Product[0])
#Html.TextBoxFor(m => m.Product[1])
#Html.TextBoxFor(m => m.Product[2])
But remote validation doesn't work so I'm a little stumped here. Essential what I would like to achieve is to send the list of products with the shop so that it can be validated via a remote call to a function. I tried putting the products within there own public class but then I wasn't able to access the shop name from within that class.
This is the Controller Action I'm trying to use:
public JsonResult ProductExists(List<String> Product, string ShopName)
Any Ideas how I could solve this would be so much appreciated?
EDIT
This Semi works but remote validation still isn't passing ShopName:
public class Shops
{
[Required]
public string ShopName { get; set; }
public List<Products> Product { get; set; }
}
public class Products
{
[Required]
[Remote("ProductExists", "Home", AdditionalFields = "ShopName", ErrorMessage = "Product is already taken.")]
public String Product { get; set; }
}
Controller Action:
public JsonResult ProductExists(List<String> Product, string ShopName)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
View:
#model Shop.Models.Shops
#{
ViewBag.Title = "Shop";
}
<h2>Shop</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Shop</legend>
<div class="editor-label">
#Html.LabelFor(model => model.ShopName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ShopName)
#Html.ValidationMessageFor(model => model.ShopName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Product[0])
#Html.ValidationMessageFor(model => model.Product[0])
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Product[1])
#Html.ValidationMessageFor(model => model.Product[1])
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Product[2])
#Html.ValidationMessageFor(model => model.Product[2])
</div>
<input type="submit" value="Create" />
</fieldset>
}
look at the following answer. I would make product a class on its own like you tried. Loot at the rendered html code for the page and check out the field name for the ShopName textbox. I think it should be ShopName, if so you dont need to change the AdditionalFields attribute if not change it to the name rendered.
So something like this:
public class Shop
{
public string ShopName { get; set; }
public List<Products> Product { get; set; }
}
public class Products
{
[Remote("ProductExists", "Account", AdditionalFields = "ShopName", ErrorMessage = "Product is already taken.")]
public String Product { get; set; }
}
in your view do something like this:
foreach(var item in Model.products)
{
#Html.TextBoxFor(item => item.product) // not sure if the syntax is right
}

Resources