ASP MVC Nested Models - asp.net-mvc

I need a view that displays rows of header-detail info. For a trivial case I set up a Course-Student tiny database with 4 simple tables. The Courses and Students tables are linked with a Section table which also has a student grade in it. Also, the Student table is owned by a Student Type Table. There are helper classes to hold the list of "header" data for the course and a "detail" list for each student who takes that class.
namespace SchoolA.Models
{
public class CourseInfo2
{
public int CourseID { get; set; }
public int CourseNumber { get; set; }
public string CourseName { get; set; }
public IEnumerable<CourseSectionList> CourseStudentList { get; set; }
}
}
namespace SchoolA.Models
{
public class CourseSectionList
{
public string Student { get; set; }
public string Type { get; set; }
public string Grade { get; set; }
}
}
The controller is:
public ActionResult Courselisting()
{
List<CourseInfo2> courseInfo2 = new List<CourseInfo2>();
List<CourseSectionList> courseSectionList = new List<CourseSectionList>();
var Cquery = from c in db.Courses select c;
foreach (var item in Cquery)
{
Course course = db.Courses.Find(item.CourseID); // first find the selected course
// get the sections
foreach (var s in query) // go through each section
{
// get the section data
courseSectionList.Add(new CourseSectionList
{
Student = student.StudentName,
Type = studentType.StudentTypeName,
Grade = s.SectionStudentGrade
});
} // end of section loop
courseInfo2.Add(new CourseInfo2
{
CourseID = course.CourseID,
CourseNumber = course.CourseNumber,
CourseName = course.CourseName,
CourseStudentList = courseSectionList
});
} // end of Course loop
return View(courseInfo2); // Course List and Section list for each course
}
The View is:
#model SchoolA.Models.CourseInfo2
#* doesn't work either: model List<SchoolA.Models.CourseInfo2>*#
<div>
<table>
<tr><th>ID</th> <th>Number</th> <th>Course</th></tr>
#foreach (var CourseInfo2 in Model)
{
<tr>
<td>
#CourseInfo2.CourseID
</td>
<td>
#CourseInfo2.CourseNumber
</td>
<td>
#CourseInfo2.CourseName
</td>
<td>
<table class="table">
<tr><th>Student</th> <th>Type</th><th>Grade</th></tr>
#foreach (var s in Model.CourseStudentList)
{
<tr>
<td>
#s.Student
</td>
<td>
#s.Type
</td>
<td>
#s.Grade
</td>
</tr>
}
</table>
</td>
</tr>
}
</table>
</div>
The problem is that I get a variety of errors as I try to pass these two models to the view. In the code as shown above I get this error:
CS1579: foreach statement cannot operate on variables of type 'SchoolA.Models.CourseInfo2' because 'SchoolA.Models.CourseInfo2' does not contain a public definition for 'GetEnumerator'
I've tried a number of variations for passing the models but always run into one error other the other that prevents both models from working in the view. I should note that I tested each part of the view independently and the controller code works fine to deliver the correct data to the view. However, I can't combine the two.
The problem seems to be the way I create instances of the models an how they are passed to the view. What am I doing wrong?

You're passing your view a List<SchoolA.Models.CourseInfo2>, but the view expects a single SchoolA.Models.CourseInfo2.
Change your #model declaration to IEnumerable<SchoolA.Models.CourseInfo2>.
Then change your foreach loops.
Change the first loop to foreach (var courseInfo in Model)
Change the inner loop to foreach (var s in courseInfo.CourseStudentList)

You are passing a model which is list of object, while in view you have single object.
Change your view model bind as below:
#model List<SchoolA.Models.CourseInfo2>

Problem: I have to edit Level2Name (by UI it's a textbox inside the child grid).
I'm able to edit Level1name (parent grid text field) and able to get value in my controller.
Question: How do I able to edit the nested textbox.
What I've tried.
Code:
Model
public class Level0
{
public Level0()
{
Level1= new List<Level1>();
}
public int? ID{ get; set; }
public int? NameText{ get; set; }
public List<Level1> lev1{ get; set; }
}
public class Level1
{
public Level1()
{
Level2= new List<Level2>();
}
public int Lev1ID {get;set;}
public string Level1name{ get; set; }
public List<Level2> level2{ get; set; }
}
public class Level2
{
public string Level2_ID { get; set; }
public string Level2Name{ get; set; }
}
UI Code
#for (int i = 0; i < Model.Level1.Count; i++)
{
#Html.HiddenFor(modelItem => Model.Floors[i].Lev1ID )
#for (int j = 0; j < Model.Level1[i].Level2.Count; j++)
{
#Html.HiddenFor(m => m.Level1[i].Level2[j].OP)
#Html.TextBoxFor(m => m.Level1[i].Level2[j].Level2Name, new { #class = "form-control" })
}
}

Related

Dropdown list population from ViewModel

First of all, I know this question has been asked many, many times. I've read countless articles and Stack Overflow answers. I've tried to figure this problem out for four days and I think I need help if someone doesn't mind.
I have two databases. The employee database has a field called "DisplayName" -- the second database has a relationship with the first and they work together great. I'm able to call the two databases perfectly in another application.
You can see the in the picture Index Page
that I have a list of people. I want a dropdown below it that lists all display names in the database so employees can add themselves to the list. You'll see a dropdown in the image but it's not populated.
Seems simple. But geez. Part of a problem I'm having is my home controller already has a function to populate the list in the picture so I can't do another on that page. I've tried a lot of suggestions on a lot of sites. I get IEnumerable errors or display reference errors....
Here's my controller (again - it has nothing in it that helps the dropdown):
namespace SeatingChart.Controllers
{
public class HomeController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Employee
public ActionResult Index()
{
var lists = db.BreakModels
.Include("Employee")
.Include("TimeEntered")
.Include("TimeCleared")
.Include("DisplayName")
.Select(a => new HomeIndexViewModels
{
Employee = a.Employee,
DisplayName = a.EmployeeModels.DisplayName,
TimeEntered = a.TimeEntered,
TimeCleared = a.TimeCleared.Value,
Id = a.EmployeeModels.Id,
});
return View(lists);
}
View:
#model IEnumerable<SeatingChart.Models.HomeIndexViewModels>
#{
Layout = null;
}
#Html.Partial("_Header")
<div class="container_lists">
<div class="container_break col-md-8">
<h5 style="text-align:center">Break List</h5>
<table class="table-bordered col-lg-12">
#if (Model != null)
{
foreach (var item in Model)
{
if (item.TimeCleared == null)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.DisplayName)
</td>
<td>
 BV
</td>
<td>
 #item.TimeEntered.ToString("HH:mm")
</td>
</tr>
}
}
}
</table>
#using (Html.BeginForm())
{
<div class="row site-spaced">
<div class="col-3">
#Html.DropDownList("DisplayName", new SelectList(new List<string>() { "---Dispatcher---" }), new { #class = "required " })
</div>
</div>
<div class="col-3">
<input type="submit" value="Submit" class="site-control" />
</div>
}
</div>
</div>
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace SeatingChart.Models
{
public class HomeIndexViewModels
{
//Break Model
public int BreakId { get; set; }
public int Employee { get; set; }
public DateTime TimeEntered { get; set; }
public DateTime? TimeCleared { get; set; }
//Employee Model
public int Id { get; set; }
public string DisplayName { get; set; }
public string DisplayNames { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool NotActive { get; set; }
public int Force { get; set; }
public string EmployeeList { get; set; }
}
}
I hope this is clear enough. I've tried so many different ways with so much code - the errors are different with everything I've tried.
Thanks in advance for your patience and help!
You can add to your viewmodel
public List<SelectListItem> Employees { get; set; }
Then you can populate this list with controller then in view just call it with:
#Html.DropDownListFor(m => m.Id, Model.Employees, new { #class = "form-control", required = "required" })
Update - how to populate list. Should work (but not tested code).
public List<SelectListItem> GetEmployeeForDropdown(List<HomeIndexViewModels> list)
{
List<SelectListItem> empList = new List<SelectListItem>();
try
{
if (list != null && list.Count > 0)
{
foreach (var item in list)
{
empList.Add(new SelectListItem { Text = item.DisplayName, Value = item.Id.ToString() });
}
}
else
{
empList.Add(new SelectListItem { Text = "No items", Value = string.Empty });
}
}
catch (Exception ex)
{
//handle exceptions here
}
return empList;
}
Edit: Remember to use your model in view!

Creating a matrix of checkboxes which supports post back in ASP.NET MVC

I'm trying to think of a way to manage the selection of a matrix of values via view models in MVC 5.
I have a list of companies and a list of roles. The roles are the same for each company. What I want is to output a matrix of companies/roles with a checkbox for each combination. This allows the user to select which role the person will have for each company.
I can render the output with nested foreach loops but I can't help but think there's a better way to achieve this with MVC and Editor Templates.
Below is a crude example of the layout. So every company is output as a column and every role is output as a row. A checkbox for each company is then output into every row.
Company/Role COMPANY1 COMPANY2 COMPANY3
ROLE1 [X] [] []
ROLE2 [] [] [X]
ROLE3 [] [] []
The added complexity is being able to handle postback. My view model structure is currently..
public class RequestViewModel
{
public Guid Id { get; set; }
public List<CompanyAccessViewModel> CompanyAccessViewModels { get; set; }
}
public class CompanyAccessViewModel
{
public int Company { get; set; }
public Guid Id { get; set; }
public Guid RequestId { get; set; }
public List<Company> ListCompanies { get; set; }
public List<Role> ListRoles { get; set; }
public List<CompanyAccessRoleViewModel> CompanyAccessRoleViewModels { get; set; }
}
public class CompanyAccessRoleViewModel
{
public Guid AccessRequirementId { get; set; }
public Guid Id { get; set; }
public string Role { get; set; }
}
ListCompanies is the list of all the companies.
ListRoles is a list of all the roles.
As always, start with a view model(s) to represent what you want to display/edit.
public class RoleVM
{
public int ID { get; set; }
public string Name { get; set; }
public List<CompanyVM> Companies { get; set; }
}
public class CompanyVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
and in the GET method
[HttpGet]
public ActionResult Index()
{
IEnumerable<Role> roles = db.Roles;
IEnumerable<Company> companies = db.Companies;
List<RoleVM> model = roles.Select(x => new RoleVM()
{
ID = x.ID,
Name = x.Name,
Companies = companies.Select(y => new CompanyVM()
{
ID = y.ID,
Name = y.Name
}).ToList()
}).ToList();
// For editing existing roles, set the IsSelected property
// of the respective CompanyVM to true
return View(model);
}
and the view
#model List<RoleVM>
#using (Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Company/Role</th>
#foreach(var company in Model.FirstOrDefault().Companies)
{
<th>#company.Name</th>
}
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Model[i].Name
#Html.HiddenFor(m => m[i].ID)
#Html.HiddenFor(m => m[i].Name)
</td>
#for (int j = 0; j < Model[i].Companies.Count; j++)
{
<td>
#Html.HiddenFor(m => m[i].Companies[j].ID)
#Html.HiddenFor(m => m[i].Companies[j].Name)
#Html.CheckBoxFor(m => m[i].Companies[j].IsSelected)
</td>
}
</tr>
}
</tbody>
</table>
<button type="submit">Save</button>
}
which will the post to
[HttpPost]
public ActionResult Index(List<RoleVM> model)
{
// To get the companies for each role
foreach (var role in model)
{
var selectedCompanies = role.Companies.Where(x => x.IsSelected);
Note that using nested foreach loops would never have bound correctly when submitting your form (you need to use for loops). If you want to use EditorTemplates (your properties can be IEnumerable<T> rather that IList<T>, then create a partial in /Views/Shared/EditorTemplates/RoleVM.cshtml
#model RoleVM
<tr>
<td>
#Model.Name
#Html.HiddenFor(m => m.ID)
#Html.HiddenFor(m => m.Name)
</td>
#Html.EditorFor(m => m.Companies)
</tr>
and another for /Views/Shared/EditorTemplates/CompanyVM.cshtml
#model CompanyVM
<td>
#Html.HiddenFor(m => m.ID)
#Html.HiddenFor(m => m.Name)
#Html.CheckBoxFor(m => m.IsSelected)
</td>
and the main view would be
#model List<RoleVM>
.... // as above
<tbody>
#Html.EditorFor(m => m)
</tbody>

MVC - Name of Html Helper change and get prepended with item when using foreach loop

I have a page that show a list of checkboxes, for showing my checkbox on page I use the code:
#foreach (var item in Model)
{
<td>
#Html.EditorFor(
model => item.IsSelected, new { htmlAttributes = new { #name = "IsSelected" } })
</td>
}
now my problem is when the page got rendereded the name of checkboxes is getting prepended with item therefore the name is item.IsSelected instead of IsSelected and as a result binder can't bind it to my ViewModel, I've tried setting its name but no use, is there a way I can keep the name the same or use some trick to bind it to my ViewModel without using plain Html?
Edit:
My ViewModel:
public int NewsLetterId { get; set; }
public string NewsLetterEmail { get; set; }
public string NewsLetterSubscriberName { get; set; }
public bool IsActive { get; set; }
public bool IsSelected { get; set; }
public int? PriCatIDfk { get; set; }
//[ForeignKey("PriCatIDfk")]
public virtual PriCat PriCat { get; set; }
public int? SecCatIDfk { get; set; }
//[ForeignKey("SecCatIDfk")]
public virtual SecCat SecCat { get; set; }
Just don't use EditorFor, try CheckBoxFor instead:
#for (int i = 0; i < Model.Count(); i++)
{
<td>
#Html.CheckBoxFor(x => Model[i].IsSelected)
</td>
}
You should use for loop instead foreach, becouse it's only way to make right names for binding without Editor Template.
Actually i suppose this will work too:
#for (int i = 0; i < Model.Count(); i++)
{
<td>
#Html.EditorFor(x => Model[i].IsSelected)
</td>
}

How to use two IENumerable models in one view

I am trying to use two models in one view, but from what I understand my program just don't see any objects within models.
Here is my code.
Models:
public class Album
{
[Key]
public int ThreadId { get; set; }
public int GenreId { get; set; }
public string Title { get; set; }
public string ThreadByUser { get; set; }
public string ThreadCreationDate { get; set; }
public string ThreadContent { get; set; }
public Genre Genre { get; set; }
public List<Posts> Posty { get; set; }
}
public class Posts
{
[Key]
public int PostId { get; set; }
public int ThreadId { get; set; }
public string PostTitle { get; set; }
public string PostContent { get; set; }
public string PostDate { get; set; }
public string PosterName { get; set; }
public Album Album { get; set; }
}
public class ModelMix
{
public IEnumerable<Posts> PostsObject { get; set; }
public IEnumerable<Album> ThreadsObject { get; set; }
}
Index controller code:
public ActionResult Index(int id)
{
ViewBag.ThreadId = id;
var posts = db.Posts.Include(p => p.Album).ToList();
var albums = db.Albums.Include(a => a.Genre).ToList();
var mixmodel = new ModelMix
{
PostsObject = posts,
ThreadsObject = albums
};
return View(mixmodel);
}
View code:
#model MvcMusicStore.Models.ModelMix
<h2>Index</h2>
#Html.DisplayNameFor(model => model.PostsObject.PostContent)
And when I try to execute my program I am getting this error:
CS1061: The "
System.Collections.Generic.IEnumerable 'does not contain a definition
of" PostContent "not found method of expanding" PostContent ", which
takes a first argument of type' System.Collections.Generic.IEnumerable
"
How I can make it work as intended? There are a lot of questions like mine on the internet but I couldn't find any matching my case.
Looping over models can be a little confusing to begin with in MVC, only because the templated helpers (i.e. Html.DisplayFor and Html.EditorFor) can be provided templates which the helper will automatically invoke for every element in a collection. That means if you're new to MVC, and you don't realise a DisplayTemplate or an EditorTemplate has not been provided for the collection already, it looks as though a simple:
#Html.DisplayFor(m => m.SomePropertyThatHoldsACollection)
is all you need. So if you've seen something like that already, that might be why you made the assumption it would work. However, let's assume for a moment that a template has not been provided. You have two options.
Firstly, and most simply, would be to use foreach over the collection:
#foreach (var post in Model.PostsObject)
{
#Html.DisplayFor(m => post.PostTitle)
// display other properties
}
You could also use a for loop, but with IEnumerable<T>, there is no indexer, so this won't work:
#for (int i = 0; i < Model.PostsObject.Count(); i++)
{
// This generates a compile-time error because
// the index post[i] does not exist.
// This syntax would work for a List<T> though.
#Html.DisplayFor(m => post[i].PostTitle)
// display other properties
}
If you did want to use the for loop still, you can use it like so:
#for (int i = 0; i < Model.PostsObject.Count(); i++)
{
// This works correctly
#Html.DisplayFor(m => post.ElementAt(i).PostTitle)
// display other properties
}
So use whichever you prefer. However, at some point it would be a good idea to look into providing templates for your types. (Note: Although this article was written for MVC 2, the advice still applies.) They allow you to remove looping logic from your views, keeping them cleaner. When combined with Html.DisplayFor, or Html.EditorFor, they will also generate correct element naming for model binding (which is great). They also allow you to reuse presentation for a type.
One final comment I'd make is that the naming of your properties is a little verbose:
public class ModelMix
{
public IEnumerable<Posts> PostsObject { get; set; }
public IEnumerable<Album> ThreadsObject { get; set; }
}
We already know they're objects, so there's no need to add that on the end. This is more readable:
public class ModelMix
{
public IEnumerable<Posts> Posts { get; set; }
public IEnumerable<Album> Threads { get; set; }
}
You need to iterate them like this:
#model MvcMusicStore.Models.ModelMix
<h2>Index</h2>
#for(var i=0; i<model.PostsObject.Count(); i++)
{
#Html.DisplayNameFor(model => model.PostsObject[i].PostContent)
}
And also it's better to save IList instead of IEnumerable, as it will have Count property, instead of using Count() method
Typically you'll need to iterate through each item if you're passing in any type of IEnumerable<>. Since you built a semi-complex model, you'll want to display foreach item in each list. Here is an example based from the ASP.NET MVC tutorials that I think would help you a bit:
#model IEnumerable<ContosoUniversity.Models.Course>
#{
ViewBag.Title = "Courses";
}
<h2>Courses</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.CourseID)
</th>
<th>
#Html.DisplayNameFor(model => model.Title)
</th>
<th>
#Html.DisplayNameFor(model => model.Credits)
</th>
<th>
Department
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
#Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
#Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.CourseID }) |
#Html.ActionLink("Details", "Details", new { id=item.CourseID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.CourseID })
</td>
</tr>
}
</table>
Generally most people use an ICollection for their lists, where an example might be in an object:
public virtual ICollection<Post> Posts { get; set; }
Source:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
I would recommend starting at the beginning as it'll help you understand why you need to do this:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc

How can I access data attributes for a class in my MVC view

I have the following class:
namespace Storage.Models
{
public class AdminDetail
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string Title { get; set; }
public string Status { get; set; }
public string Type { get; set; }
public string Level { get; set; }
[DisplayName("Display Order")]
public string Order { get; set; }
public int Row { get; set; }
}
I had some advice and I set up the following as the model in my view:
#model IEnumerable<AdminDetail>
Is it possible for me to somehow reference attributes of my AdminDetail class such as [DisplayName("Display Order")]. What I would like to do is something like this to show column labels for the first row of my table grid.
<div>#Html.LabelFor(model => Model.Order)</div>
But I am not sure how to do this as my model is of a collection and not a single instance.
Here's the code I use to generate data for the view:
IEnumerable<AdminDetail> details = null;
IList<AdminDetail> detailsList = null;
details = from t in _table
select new AdminDetail
{
PartitionKey = t.PartitionKey,
RowKey = t.RowKey,
Title = t.Title,
Status = t.Status,
Type = t.Type,
Level = t.Level,
Order = t.Order
};
#endregion
detailsList = details
.OrderBy(item => item.Order)
.ThenBy(item => item.Title)
.Select((t, index) => new AdminDetail() {
PartitionKey = t.PartitionKey,
RowKey = t.RowKey,
Title = t.Title,
Status = t.Status,
Type = t.Type,
Level = t.Level,
Order = t.Order,
Row = index + 1 })
.ToList();
return detailsList;
#model IEnumerable<AdminDetail>
#foreach (var detail in Model)
{
<div>#Html.LabelFor(x => detail.Order)</div>
}
or if you are using an editor template which is what I would recommend:
#model IEnumerable<AdminDetail>
#Html.EditorForModel()
and then inside the display template (~/Views/Shared/EditorTemplates/AdminDetail.cshtml):
#model AdminDetail
<div>#Html.LabelFor(model => model.Order)</div>
UPDATE:
You could use IList<AdminDetail> as your view model type which will give you index access to elements and you could fetch the metadata like this:
#model IList<AdminDetail>
<table>
<thead>
<tr>
<th>#Html.LabelFor(x => x[0].Order)</th>
</tr>
</thead>
<tbody>
#Html.DisplayForModel()
</tbody>
</table>
and inside the display template (~/Views/Shared/DisplayTemplates/AdminDetail.cshtml):
#model AdminDetail
<tr>
<td>#Html.DisplayFor(x => x.Order)</td>
</tr>
Since you are trying to make a table, and just need the label one time, just instantiate a new item of the class you want and call the LabelFor methods on it.
#{ var labelModel = new AdminDetail; }
#Html.LabelFor(model => labelModel.Order);
It's a bit of a hacky way to do it, but it will prevent you from writing your own reflection and it won't tie the label in the header to a specific input on the page.

Resources