How can I display data from multiple entities in same view and group them by one of the entity - asp.net-core

I have clients, projects, client comments and project comments. I want to display one table grouped by client followed by all projects and for each client where there is a comment as well as for each project that has a comment display the last provided comment.
The table would have the Client Name at the top followed by the latest respective comment if provided.
It would be followed by the list of all projects for that client with their latest comment if provided.
I have the client model:
public class Client
{
public int Id { get; set; }
public string ClientName { get; set; }
public bool IsActive { get; set; }
public ICollection<ClientComment> ClientComments { get; set; }
public ICollection<Project> Projects { get; set; }
The project model:
public class Project
{
public int Id { get; set; }
public string ProjectName { get; set; }
public int ClientId { get; set; }
public Client Client { get; set; }
public bool IsArchived { get; set; }
public ICollection<ProjectComment> ProjectComments { get; set; }
The client comment model:
public class ClientComment
{
public int Id { get; set; }
public int? ClientId { get; set; }
public Client Client { get; set; }
public string StatusComment { get; set; }
public DateTime LastUpdateDate { get; set; }
public ClientComment ()
{
this.LastUpdateDate = DateTime.UtcNow;
}
The project comment model:
public class ProjectComment
{
public int Id { get; set; }
public int? ProjectId { get; set; }
public Project Project { get; set; }
public string StatusComment { get; set; }
public DateTime LastUpdateDate { get; set; }
public ProjectComment ()
{
this.LastUpdateDate = DateTime.UtcNow;
}
The end result should be with their respective table headers:
ClientName1 | ClientStatusComment
ProjectName1 | ProjectStatusComment
ProjectName2 | ProjectStatusComment
ProjectName3 | ProjectStatusComment
ClientName2 | ClientStatusComment
ProjectName1 | ProjectStatusComment
ProjectName2 | ProjectStatusComment
ProjectName3 | ProjectStatusComment

You could use View Model which contains the properties you need display in the view.Refer to as follows:
ClientVM and ProjectVM
public class ClientVM
{
public string ClientName { get; set; }
public string ClientStatusComment { get; set; }
public List<ProjectVM> Projectlist { get; set; }
}
public class ProjectVM
{
public string ProjectName { get; set; }
public string ProjectStatusComment { get; set; }
}
Populate the ViewModel
public class ClientsDetailsModel : PageModel
{
private readonly MyDbContext _context;
public ClientsDetailsModel(MyDbContext context)
{
_context = context;
}
[BindProperty]
public List<ClientVM> clientVMList { get; set; }
public async Task<IActionResult> OnGet()
{
var clientlist = _context.Clients
.Include(c => c.ClientComments)
.Include(c => c.Projects)
.ThenInclude(p => p.ProjectComments).ToList();
clientVMList = new List<ClientVM>();
foreach (var item in clientlist)
{
ClientVM clientVM = new ClientVM()
{
Projectlist = new List<ProjectVM>()
};
clientVM.ClientName = item.ClientName;
if (item.ClientComments != null && item.ClientComments.Any())
{
clientVM.ClientStatusComment = item.ClientComments.OrderByDescending(cc => cc.LastUpdateDate).First().StatusComment;
}
else
{
clientVM.ClientStatusComment = "No StatusComment";
}
foreach (var projectItem in item.Projects)
{
ProjectVM projectVM = new ProjectVM();
projectVM.ProjectName = projectItem.ProjectName;
if (projectItem.ProjectComments != null && projectItem.ProjectComments.Any())
{
projectVM.ProjectStatusComment = projectItem.ProjectComments.OrderByDescending(pc => pc.LastUpdateDate).First().StatusComment;
}
else
{
projectVM.ProjectStatusComment = "No StatusComment";
}
clientVM.Projectlist.Add(projectVM);
}
clientVMList.Add(clientVM);
}
return Page();
}
}
ClientsDetails.cshtml
#page
#model MultipleEntitiesInSameView.Pages.ClientsDetailsModel
<table class="table">
<thead>
<tr>
<th>
Name
</th>
<th>
LastStatusComment
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.clientVMList)
{
<tr style="background-color:aliceblue;">
<td>
#Html.DisplayFor(modelItem => item.ClientName)
</td>
<td>
#Html.DisplayFor(modelItem => item.ClientStatusComment)
</td>
</tr>
#foreach (var projectItem in item.Projectlist)
{
<tr>
<td>
#Html.DisplayFor(modelItem => projectItem.ProjectName)
</td>
<td>
#Html.DisplayFor(modelItem => projectItem.ProjectStatusComment)
</td>
</tr>
}
}
</tbody>
</table>
4.Result :

Related

API - Blazor server - foreign key ICollection is always null - EF core

I'm new to API and Blazor and I'm trying to follow this example (https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/crud?view=aspnetcore-5.0)
Below you can see my models and code.
Model:
public class Student {
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime EnrollmentDate { get; set; }
[JsonIgnore]
public virtual ICollection<Enrollment> Enrollments { get; set;}
}
API controller:
[HttpGet]
[Route("{id:int}")]
public async Task<ActionResult<Student>> GetStudent(int id)
{
try
{
var result = await context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.StudentId == id);
//var result = await context.Students
// .Where(s => s.StudentId == id)
// .Select(s => new
// {
// Student = s,
// Enrollment = s.Enrollments
// })
// .FirstOrDefaultAsync();
if (result == null)
{
return NotFound();
}
return Ok(result);
}
catch (Exception)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Error receiving data from database");
}
}
Student model in Blazor server
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime EnrollmentDate { get; set; }
[JsonIgnore]
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
My Blazor Student base:
public class StudentDetailsBase : ComponentBase
{
[Inject]
public IEnrollmentService EnrollmentService { get; set; }
[Inject]
public IStudentService StudentService { get; set; }
public Student Student { get; set; }
public List<Enrollment> Enrollments { get; set; }
//public ICollection<Student> Student { get; set; }
[Parameter]
public string Id { get; set; }
protected override async Task OnInitializedAsync()
{
Id = Id ?? "1";
Student = await StudentService.GetStudent(int.Parse(Id));
Enrollments = (await EnrollmentService.GetEnrollmentBySID(int.Parse(Id))).ToList();
//Student = (await StudentService.GetStudent(int.Parse(Id))).ToList();
}
}
And my Student display page:
#if (Student == null)
{
<p>Loading ...</p>
}
else
{
<div>
<table class="table">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Enrollment Date</th>
<th>All enrollments</th>
</tr>
</thead>
<tbody>
<tr>
<td>#Student.FirstName</td>
<td>#Student.LastName</td>
<td>#Student.EnrollmentDate</td>
#foreach (var i in Student.Enrollments)
{
<td>#i.Course.Title</td>
<td>#i.Grade</td>
}
</tr>
</tbody>
</table>
</div>
}
I've tried to google the problem and i can not figure out what i'm doing wrong. My Student.Enrollments in always null. Which causes my Blazor server to throw an error.
When i test my API with Postman it's working fine.
Hopefully someone will point me in the right direction on how to solve this.
Thank you.
Kind regards.

How can I display the total number of records a table holds(the count) in a view by passing the count value in the "return view method"

How can I display the total number of records a table holds(the count). I would like to display the count in a view so I am trying to pass the count as a parameter on return view but I get an error saying cannot convert string to int. I am pretty sure there is a smarter way to do this. I have tried converting the int value using toString() but I still get syntax erros thereafter. I have placed both my controller and view below. Notice what I am trying to do in my controller in the return view method I am trying to insert the count but I get an error that says
cannot convert from int? to string
Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data;
using System.Web.Http;
using System.Configuration;
using PagedList;
namespace App.Web.Controllers
{
public class DisplayUploadedFileController : Controller
{
private EntitiesModel db = new EntitiesModel();
public ActionResult DisplayUploadedFileContents(int? id, int? page, int? totalCount)
{
var vm = new dbclients_invalidEmailsVM();
vm.UploadId = id ?? default(int);
if (page == null)
{
page = 1;
}
int pageSize = 10;
int pageNumber = (page ?? 1);
var rows = from myRow in db.tbl_dataTable
select myRow;
totalCount = rows.Count();
return View(db.tbl_dataTable.OrderByDescending(r => r.ClientId).Where(r => r.UploadId == id).ToList().ToPagedList(pageNumber, pageSize), totalCount);
}
}
}
View
#model PagedList.IPagedList<App.Web.marketingdbclients_dataTable>
#using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link href="http://www.jqueryscript.net/css/jquerysctipttop.css" rel="stylesheet" type="text/css">
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="pagination.js"></script>
</head>
<body>
<div class="container" style="margin-top:50px;">
<table class="table" id="table">
<tr>
<th>
First Name
</th>
<th>
Last Name
</th>
<th>
Cell1
</th>
<th>
Email1
</th>
<th>
Company
</th>
<th>
Job Title
</th>
<th>
Province
</th>
<th>
Source
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Cell1)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email1)
</td>
<td>
#Html.DisplayFor(modelItem => item.Company)
</td>
<td>
#Html.DisplayFor(modelItem => item.JobTitle)
</td>
<td>
#Html.DisplayFor(modelItem => item.PhysicalProvince)
</td>
<td>
#Html.DisplayFor(modelItem => item.Source)
</td>
</tr>
}
</table>
</br>
Number of Records #Model.Count()<br />
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of #Model.PageCount
#Html.PagedListPager(Model, page => Url.Action("DisplayUploadedFileContents", new { uploadId = Model.First().UploadId, page }))
</div>
</body>
</html>
My Table Represented as a model
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace App.Web
{
using System;
using System.Collections.Generic;
public partial class marketingdbclients_dataTable
{
public int ClientDataId { get; set; }
public Nullable<int> ClientId { get; set; }
public Nullable<int> UploadId { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string IdentificationNumber { get; set; }
public string RaceId { get; set; }
public string DateOfBirth { get; set; }
public string Age { get; set; }
public string TitleTypeId { get; set; }
public string GenderTypeId { get; set; }
public string Nationality { get; set; }
public string PhysicalCountry { get; set; }
public string PhysicalProvince { get; set; }
public string PhysicalCity { get; set; }
public string Area { get; set; }
public string HighestQualification { get; set; }
public string CurrentQualification { get; set; }
public string PhysicalAddress { get; set; }
public string PostalAddress { get; set; }
public string Cell1 { get; set; }
public string Cell2 { get; set; }
public string Cell3 { get; set; }
public string Cell4 { get; set; }
public string Work1 { get; set; }
public string Work2 { get; set; }
public string Work3 { get; set; }
public string Work4 { get; set; }
public string Home1 { get; set; }
public string Home2 { get; set; }
public string Home3 { get; set; }
public string Home4 { get; set; }
public string LSMGroup { get; set; }
public string Municipality { get; set; }
public string Crediting_Rating { get; set; }
public string Email1 { get; set; }
public string Email2 { get; set; }
public string Email3 { get; set; }
public string Email4 { get; set; }
public string Income { get; set; }
public string Company { get; set; }
public string Industry { get; set; }
public string JobTitle { get; set; }
public string LeadStage { get; set; }
public string ReggieNumber { get; set; }
public string Source { get; set; }
public System.DateTime DateInserted { get; set; }
//public int totalEntriesCount { get; set; }
}
}
Please create a new ViewModel class and store your two inputs like so:
public class MyViewModel
{
public List<marketingdbclients_dataTable> marketingdbclients_dataTables { get; set; }
public int totalCount { get; set; }
public MyViewModel()
{
this.marketingdbclients_dataTables = new List<marketingdbclients_dataTable>();
this.totalCount = 0;
}
}
Controller file should be
public ActionResult DisplayUploadedFileContents(int? id, int? page, int? totalCount)
{
var vm = new dbclients_invalidEmailsVM();
vm.UploadId = id ?? default(int);
if (page == null)
{
page = 1;
}
int pageSize = 10;
int pageNumber = (page ?? 1);
var rows = from myRow in db.tbl_dataTable
select myRow;
totalCount = rows.Count();
MyViewModel model = new MyViewModel();
model.marketingdbclients_dataTables = db.tbl_dataTable.OrderByDescending(r => r.ClientId).Where(r => r.UploadId == id).ToList().ToPagedList(pageNumber, pageSize);
model.totalCount = totalCount ;
return View(model);
}
Then in your View (index.cshtml), declare MyViewModel like so:
#model WebApp.Models.MyViewModel
<div>
your html
</div>
The concept we just used is called View Model. Please read more about it here:
Understanding ViewModel
You may only pass one model object when calling View(model).
You can create an object that contains both the count and the datatable that you use as the view model.
A simple way to do this may be using an anonymous object:
return View(
new {
Page = db.tbl_dataTable.OrderByDescending(r => r.ClientId).Where(r => r.UploadId == id).ToList().ToPagedList(pageNumber, pageSize)db.tbl_dataTable.OrderByDescending(r => r.ClientId).Where(r => r.UploadId == id).ToList().ToPagedList(pageNumber, pageSize),
Count = totalCount
});

Reference model class in controller, so muliple data can be in one view

I am trying to get data from multiple SQL Server stored procedure to be accessible in 1 view so I can then run comparisons and create graphs/grids etc.
I got each section to work on their own, and I am now trying to get them to work together.
I have change my controller and put the field types into their own classes, and then created a "Ring of Rings" class, and put them all in.
namespace APP.Models
{
public class SP_RESULTS
{
public type Type { get; set; }
public status Status { get; set; }
public condition Condition { get; set; }
public rooms Rooms { get; set; }
}
public class type
{
[Key]
public decimal type_id { get; set; }
public string type_code { get; set; }
public string type_name { get; set; }
}
public class status
{
//status
public decimal status_id { get; set; }
public string status_code { get; set; }
public string status_name { get; set; }
public string rentable { get; set; }
}
public class condition
{
//condition
public decimal condition_id { get; set; }
public string condition_code { get; set; }
public string condition_name { get; set; }
public string rentable { get; set; }
public int colour_code { get; set; }
public int service_order { get; set; }
}
public class rooms
{
//rooms
public decimal room_id { get; set; }
public string room_no { get; set; }
public decimal type_id { get; set; }
public int floor { get; set; }
public decimal status_id { get; set; }
public decimal condition_id { get; set; }
}
}
I then amended each section of my controller that was running SQL Server stored procedures, to use the correct class name instead of the "ring of rings" name, IE:
var outputmodel4 = new List<rooms>();
var command4 = db.Database.Connection.CreateCommand();
command4.CommandText = "SELECT rooms.room_id , rooms.room_no , rooms.type_id , rooms.floor_no , rooms.status_id , rooms.condition_id FROM rooms";
using (var SPOutput4 = command4.ExecuteReader())
{
foreach (var row in SPOutput4)
{
outputmodel4.Add(new rooms()
{
room_id = (decimal)SPOutput4["room_id"],
room_no = (string)SPOutput4["room_no"],
type_id = (decimal)SPOutput4["type_id"],
floor_no = (int)SPOutput4["floor_no"],
status_id = (decimal)SPOutput4["status_id"],
condition_id = (decimal)SPOutput4["condition_id"],
});
}
db.Database.Connection.Close();
return View(outputmodel4);
}
...etc for other SQL stored procedures
..and the same with my view
#model IEnumerable<app.Models.SP_RESULTS >
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Rooms.room_id)
</th>
<th>
#Html.DisplayNameFor(model => model.Rooms.room_no)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Rooms.room_id)
</td>
<td>
#Html.DisplayFor(modelItem => item.Rooms.room_no)
</td>
</tr>
}
</table>
…etc for the other models
Everything looks fine in VBS (no red swiggles), but when I access the view in the browser, I get an error:
Server Error in '/' Application.
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[app.Models.rooms]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable1[app.Models.SP_RESULTS]'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The model item passed into the dictionary is of type 'System.Collections.Generic.List1[app.Models.rooms]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable1[app.Models.SP_RESULTS]'.
If I go back to my "rings of rings" model, and change it to this :
public class SP_RESULTS
{
public IEnumerable<type> Type { get; set; }
public IEnumerable<status> Status { get; set; }
public IEnumerable<condition> Condition { get; set; }
public IEnumerable<rooms> Rooms { get; set; }
}
and change the following lines in in my controller:
var outputmodel4 = new List<rooms>();
outputmodel4.Add(new rooms()
to
var outputmodel4 = new List<SP_RESULTS>();
outputmodel4.Add(new SP_RESULTS()
VBS tells me that that
SP_RESULTS does not contain a definition for room_id
I've tried prefixing the definition with the class name, with no luck.
I've looked on SO, fourms.asp.net and google.. can cannot see a solution (there probably is, but I am unsure what solution I an looking for.... if that makes sense).
Would someone be able to tell me what I need to do to get all my model classes working in the same view ?
I apologise now, as I realize that this question has probably been asked several (million) times, but I cannot seem to fine out that jumps out says THIS is that way to do it.
For anyone else looking for an answer (and for future me), I got this work by putting the models like this :
namespace APP.Models
{
public class SP_RESULTS
{
public type Type { get; set; }
public status Status { get; set; }
public condition Condition { get; set; }
public rooms Rooms { get; set; }
public class type
{
[Key]
public decimal type_id { get; set; }
public string type_code { get; set; }
public string type_name { get; set; }
}
public class status
{
//status
public decimal status_id { get; set; }
public string status_code { get; set; }
public string status_name { get; set; }
public string rentable { get; set; }
}
public class condition
{
//condition
public decimal condition_id { get; set; }
public string condition_code { get; set; }
public string condition_name { get; set; }
public string rentable { get; set; }
public int colour_code { get; set; }
public int service_order { get; set; }
}
public class rooms
{
//rooms
public decimal room_id { get; set; }
public string room_no { get; set; }
public decimal type_id { get; set; }
public int floor { get; set; }
public decimal status_id { get; set; }
public decimal condition_id { get; set; }
}
}
}
Using VIEWDATA in the controller for each Store procedure:
var outputmodel3 = new List<SP_RESULTS.conditions>();
var command3 = db.Database.Connection.CreateCommand();
command3.CommandText = "dbo.pr_PROCEDUREONE";
command3.CommandType = System.Data.CommandType.StoredProcedure;
using (var SPOutput3 = command3.ExecuteReader())
{
foreach (var row in SPOutput3)
{
outputmodel3.Add(new SP_RESULTS.conditions()
{
condition_id = (decimal)SPOutput3["condition_id"],
condition_code = (string)SPOutput3["condition_code"],
condition_name = (string)SPOutput3["condition_name"],
});
}
ViewData["CONDITIONSOutput"] = outputmodel3;
}
var outputmodel4 = new List<SP_RESULTS.rooms>();
var command4 = db.Database.Connection.CreateCommand();
command4.CommandText = "SELECT rooms.room_id , etc FROM rooms";
using (var SPOutput4 = command4.ExecuteReader())
{
foreach (var row in SPOutput4)
{
outputmodel4.Add(new SP_RESULTS.rooms()
{
room_id = (decimal)SPOutput4["room_id"],
room_no = (string)SPOutput4["room_no"],
});
}
ViewData["ROOMSOutput"] = outputmodel4;
db.Database.Connection.Close();
return View();
}
..and then changing my view to read :-
table class="table">
<tr>
<th>
Condition ID
</th>
<th>
Condition code
</th>
<th>
Condition name
</th>
<th>
is it rentable?
</th>
<th>
Condition color code
</th>
<th>
Condition service order
</th>
<th></th>
</tr>
#foreach (var item in ViewData["CONDITIONSOutput"] as IEnumerable<Happ.Models.SP_RESULTS.conditions>)
{
<tr>
<td>
#item.condition_id
</td>
<td>
#item.condition_code
</td>
<td>
#item.condition_name
</td>
<td>
#item.rentable
</td>
<td>
#item.colour_code
</td>
<td>
#item.service_order
</td>
</tr>
}
</table>
<table class="table">
<tr>
<th>
Room ID
</th>
<th>
Room No
</th>
<th>
Rooms type_id
</th>
<th></th>
</tr>
#foreach (var item in ViewData["ROOMSOutput"] as IEnumerable<APP.Models.SP_RESULTS.rooms>)
{
<tr>
<td>
#item.room_id
</td>
<td>
#item.room_no
</td>
</tr>
}
</table>

create CheckboxFor from List in View Model

My View Model Class is
public class StudentQuestions
{
public int StudentId{ get; set; }
public int FormId { get; set; }
public virtual ICollection<Questions> Question { get; set; }
}
and question class is
public partial class Questions
{
public int questionID { get; set; }
public string field_name { get; set; }
public string question { get; set; }
public int qutyp_refID {get,set}
public string description { get; set; }
public int ord { get; set; }
public bool IsEnabled { get; set;}
public virtual ICollection<Answers> Answers { get; set; }
}
in my view
#model Test.ViewModels.StudentQuestions
<table>
<tr><td>#Model.FormId</td><td>#Model.StudentId</td></tr>
#foreach(var q in Model.Question)
{
<tr>
<td> #Html.CheckBoxForFor(i=> i.Question.question)</td>
</tr>
}
</table>
I cant access i.Question.question but I can access in CheckBox, TextBox like following and I want to change Textbox to TextBoxFor and CheckBox to CheckBoxFor and TextBox to TextBoxFor
#foreach(var q in Model.Question)
{
<tr>
#if (#q.qutyp_refID == 4)
{
<td>#Html.CheckBox(q.questionID.ToString())
</td>
}
else if (#q.qutyp_refID <= 2)
{
<td>#Html.TextBox("txtDateQuestions", DateTime.Today.ToString("dd/MM/yyyy"), new { style = "width: 120px" }) </td>
}
else
{
<td>#Html.TextBox(q.questionID.ToString(), null)</td>
}
</tr>
}
Thanks in Advance.........
Try like this,
#Html.CheckBoxFor(i => m.questionID, new { id = #m.questionID, #checked = "checked", Name = "CheckBox" })<span>m.description</span>
Example
Model
public class AssignProject
{
public Guid Id { get; set; }
public string EmployeesName { get; set; }
public Guid? EmployeeId { get; set; }
public Guid? ProjectId { get; set; }
public string AssignEmployeeId { get; set; }
public bool IsChecked { get; set; }
}
View
#foreach (var item in Model)
{
#Html.CheckBoxFor(m => item.IsChecked, new { value = item.EmployeeId, id = "chk_" + #item.EmployeeId, #checked = "checked", Name = "CheckBox" })
}

MVC ViewModel errors

Goal: To create a re-usable drop down menu that lists my website's administrators, managers and agents. These types of users are defined by the .NET Simplemembership webpages_Roles and webpages_UsersInRoles tables.
So Far:
I have a UserProfile table in my database which has 25 columns. I have a corresponding domain model of the same name which is accessed from my UsersContext() EF.
The drop down menu only needs to list the User's FirstName, LastName and UserId so instead of working with the complete domain model, I created the following ViewModel:
namespace MyModels.Models.ViewModels
{
public class AdminsAndAgentsListVM
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int UserId { get; set; }
}
}
I then added the following to my Account controller (notice I'm not working with partial view yet):
public ActionResult AdminsAndAgentsList()
{
UsersContext _db = new UsersContext(); //provides me access to UserProfiles data
var admins = Roles.GetUsersInRole("Admin"); //gets users with this role
var viewModel = _db.UserProfiles
.Where(x => admins.Contains(x.UserName)); //Selects users who match UserName list
return View(viewModel);
}
I then scaffold a list view and base it on the strongly typed ViewModel:
#model IEnumerable<MyModels.Models.ViewModels.AdminsAndAgentsListVM>
#{
ViewBag.Title = "AdminsAndAgentsList";
}
<h2>AdminsAndAgentsList</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.FirstName)
</th>
<th>
#Html.DisplayNameFor(model => model.LastName)
</th>
<th>
#Html.DisplayNameFor(model => model.UserId)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.UserId)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
</tr>
}
</table>
I do a successful build and when I run the web page I get the following error:
The model item passed into the dictionary is of type'System.Data.Entity.Infrastructure.DbQuery1[My.Models.UserProfile]',
but this dictionary requires a model item of type
'System.Collections.Generic.IEnumerable1[My.Models.ViewModels.AdminsAndAgentsListVM]'.
If I recreate the view but strongly type it agains the UserProfile, it works fine. So how to re work this so I can strongly type against my ViewModel instead? Please provide examples if possible. I am new to C# and MVC and really benefit from the seeing the code first hand. Much appreciate the help!
EDIT -----------------------------
Here is the object for the UserProfile:
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
}
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
[Required]
[ReadOnly(true)]
[DisplayName("SubscriberID")]
public int? SubscriberId { get; set; } //Foreign key
[StringLength(50, ErrorMessage = "The {0} must be at least {2} characters long.")]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[StringLength(50, ErrorMessage = "The {0} must be at least {2} characters long.")]
[Display(Name = "Last Name")]
public string LastName { get; set; }
//public DateTime DOB { get; set; }
[DataType(DataType.Date)]
public DateTime? DOB { get; set; } //This allows null
public bool? Gender { get; set; }
[Required]
[MaxLength(250)]
[EmailAddress]
public string Email { get; set; }
[MaxLength(250)]
[EmailAddress]
[NotEqualTo("Email", ErrorMessage = "Alt Email and Email cannot be the same.")]
public string AltEmail { get; set; }
[MaxLength(250)]
[EmailAddress]
public string FormEmail { get; set; }
public Address Address { get; set; }
[MaxLength(20)]
public string Telephone { get; set; }
[MaxLength(20)]
public string Mobile { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime DateAdded { get; set; }
[DataType(DataType.DateTime)]
public DateTime? LastLoginDate { get; set; }
public bool? OffersOptIn { get; set; } //any offers we may have with us or partners
public bool? NewsOptIn { get; set; } //newsletter
public bool? SubscriptionOptIn { get; set; } //account, technical, renewal notices, pp invoices, pp receipts
public bool? OrderOptIn { get; set; } //orders - workflow notices
[DataType(DataType.DateTime)]
public DateTime? LastUpdatedAccountDate { get; set; } //Last time user updated contact info
}
Try this. It will cast your query into your view model.
var viewModel = _db.UserProfiles
.Where(x => admins.Contains(x.UserName))
.Select(x => new AdminsAndAgentsListVM {
FirstName = x.FirstName,
LastName = x.LastName,
UserId = x.UserId});
You're passing the view your query, not your model.
Execute the query as you have it
var query = _db.UserProfiles
.Where(x => admins.Contains(x.UserName));
Then instantiate and populate your view model
var viewModels = new List<AdminsAndAgentsListVM>();
foreach (var item in query)
{
var viewModel = new AdminsAndAgentsListVM();
viewodel.FirstName = item.FirstName;
viewodel.LastName = item.LastName;
viewodel.UserId = item.UserId;
viewModels.Add(viewModel);
}
return View(viewModels);
This assumes, of course, that a UserProfile and AdminsAndAgentsListVM have matching properties.
Change your return line to:
return View(viewModel.AsEnumerable());
You aren't selecting your ViewModel. You need to do a Select(x => new AdminsAndAgentsListVM on your query. I would also do ToList() on there.