Search code examples
c#asp.net-core-mvcviewmodelasp.net-core-5.0

ASP.NET Core 5 MVC Using View Models with enumeration


I have 2 models; TypeOne and Project_Screen. I need a view that makes 2 tables with information from both of those tables. I tried to use this guide to making a view model which helped but isnt doing quite the same thing I am: https://dotnettutorials.net/lesson/view-model-asp-net-core-mvc/

This is the View Model I made:

 public class MyProjectsViewModel
    {
        public Project_Screen Project_Screen { get; set; }
        public TypeOne TypeOne { get; set; }
    }

This is the controller:

 public class ProfileController : Controller
    {
        private readonly Natural_ResourcesContext _context;

        public ProfileController(Natural_ResourcesContext context)
        {
            _context = context;
        }
        // GET: Profile
        public ActionResult Index()
        {
            Project_Screen project_Screen = (Project_Screen)(from s in _context.Project_Screen
                                                            where s.DSN_PM == User.Identity.Name
                                                            select s);
            TypeOne typeOne = (TypeOne)(from x in _context.TypeOne
                                        where x.Name == User.Identity.Name
                                        select x);
            MyProjectsViewModel myProjectsViewModel = new MyProjectsViewModel()
            {
                Project_Screen = project_Screen,
                TypeOne = typeOne
            };

            return View(myProjectsViewModel);
        }
    }

As you can see, project_Screen and typeOne are all records in which Manager = Name.

Then in the view I'd like to display these something like this:

@model EnvApp.ViewModels.MyProjectsViewModel

@{
    ViewData["Title"] = "Projects";
}

<h1>My Projects</h1>
<hr />
<h4>Screening Forms</h4>
<table class="table">
    <thead>
        <tr>
            <th>
                State Project Number
            </th>
            <th>
                Federal Project Number
            </th>
            <th>
                Project Name
            </th>
            <th>
                County
            </th>
            <th>
                Coordinates
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.State_Project_Number)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Federal_Project_Number)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Project_Name)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.County)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Coordinates)
                </td>
                <td>
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> 
                </td>
            </tr>
        }
    </tbody>
</table>

<br />
<hr />

<h4>Type One Projects</h4>
<table class="table">
    <thead>
        <tr>
            <th>
                State Project Number
            </th>
            <th>
                Federal Project Number
            </th>
            <th>
                Project Name
            </th>
            <th>
                County
            </th>
            <th>
                Coordinates
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.State_Project_Number)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Federal_Project_Number)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Project_Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.County)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Coordinates)
            </td>
            <td>
                <a asp-action="Details" asp-route-id="@item.ID">Details</a>
            </td>
        </tr>
        }
    </tbody>
</table>

I get the compiler error CS1579 on ````Model```:

foreach statement cannot operate on variables of type 'MyProjectsViewModel' because 'MyProjectsViewModel' does not contain a public instance or extension definition for 'GetEnumorator'

Which I think makes sense, because I have not denoted Project_Screen or TypeOne as an object that holds multiple records, but I'm not quite sure how to do this.

Am I on the right track or have I misunderstood this? What am I missing?

EDIT 1:

I did exactly what Md Farid Uddin Kiron said so the code in his answer reflects my view model and view. However I am still having issues with my controller.

  using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using EnvApp.ViewModels;
using EnvApp.Models.DB;

namespace EnvApp.Controllers
{
    public class ProfileController : Controller
    {
        private readonly Natural_ResourcesContext _context;

        public ProfileController(Natural_ResourcesContext context)
        {
            _context = context;
        }
        // GET: Profile
        public ActionResult Index()
        {
            Project_Screen project_Screen = (Project_Screen)(from s in _context.Project_Screen
                                                            where s.DSN_PM == User.Identity.Name
                                                            select s);
            TypeOne typeOne = (TypeOne)(from x in _context.TypeOne
                                        where x.Name == User.Identity.Name
                                        select x);
            MyProjectsViewModel myProjectsViewModel = new MyProjectsViewModel()
            {
                Project_Screen = project_Screen,
                TypeOne = typeOne
            };

            return View(myProjectsViewModel);
        }
    }
}

The issue is that project_Screen and typeOne aren't lists or IEnumerable types, which makes sense, but I'm not the syntax to get them to be enumerable.

IF you need to see it, here is my view model:

public class MyProjectsViewModel
    {
        public List<Project_Screen> Project_Screen { get; set; }
        public List<TypeOne> TypeOne { get; set; }
    }

And here is the View:

<h1>My Projects</h1>
<hr />
<h4>Screening Forms</h4>
<table class="table">
    <thead>
        <tr>
            <th>
                State Project Number
            </th>
            <th>
                Federal Project Number
            </th>
            <th>
                Project Name
            </th>
            <th>
                County
            </th>
            <th>
                Coordinates
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Project_Screen)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.State_Project_Number)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Federal_Project_Number)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Project_Name)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.County)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Coordinates)
                </td>
                <td>
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> 
                </td>
            </tr>
        }
    </tbody>
</table>

<br />
<hr />

<h4>Type One Projects</h4>
<table class="table">
    <thead>
        <tr>
            <th>
                State Project Number
            </th>
            <th>
                Federal Project Number
            </th>
            <th>
                Project Name
            </th>
            <th>
                County
            </th>
            <th>
                Coordinates
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.TypeOne)
        {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.State_Project_Number)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Federal_Project_Number)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.County)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Coordinates)
            </td>
            <td>
                <a asp-action="Details" asp-route-id="@item.ID">Details</a>
            </td>
        </tr>
        }
    </tbody>
</table>

Again, the issue is that project_Screen and typeOne aren't lists or IEnumerable types IN THE CONTROLLER. They are Lists in actuallity and everywhere EXCEPT the controller but they need to be. The error is on the lines

MyProjectsViewModel myProjectsViewModel = new MyProjectsViewModel()
            {
                Project_Screen = project_Screen,
                TypeOne = typeOne
            };

on project_Screen and typeOne and reads

cannot implicitly convert type Envapp.Models.DB.Project_Screen to System.Collection.Generic.List<Envapp.Models.DB.Project_Screen>

This appears to be happening because the code there is trying to point to the DB model table for Project_Screen rather than the ViewModel representation of items in the Project_Screen model. How do I fix this?

EDIT 2:

I'm not sure what it is but something in my code seems to be really confusing people. I am posting all of my models for clarification:

File Structure

Project_Screen.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

#nullable disable

namespace EnvApp.Models.DB
{
    public partial class Project_Screen
    {
        [Key]
        public long ID { get; set; }
        public string State_Project_Number { get; set; }
        public string? Federal_Project_Number { get; set; }
        public string Project_Name { get; set; }
        public string County { get; set; }
        public DateTime? Memo_Date { get; set; }
        public string From { get; set; }
        public string? Authorization { get; set; }
        public string DSN_PM { get; set; }
        public string? History { get; set; }
        public string History_PM { get; set; }
        public bool Review_Exempt_H { get; set; }
        public bool SHPO_Approval_H { get; set; }
        public string? Archaeology { get; set; }
        public string Archaeology_PM { get; set; }
        public bool Review_Exempt_A { get; set; }
        public bool SHPO_Approval_A { get; set; }
        public bool ESA_Key { get; set; }
        public bool Crayfish { get; set; }
        public bool Crayfish_Habitat_Assessment { get; set; }
        public bool NLEB_4D { get; set; }
        public bool USFWS { get; set; }
        public string USFWS_Type { get; set; }
        public bool Mussel_Habitat { get; set; }
        public bool Mussel_Stream { get; set; }
        public string Within_Airport { get; set; }
        public string? ToPo_Quad_Name { get; set; }
        public bool Bat_Habitat { get; set; }
        public string? Bars { get; set; }
        public string Coordinates { get; set; }
        public string? Natural_Resources_Notes { get; set; }
        public string Adduser { get; set; }
        public DateTime? Date_Added { get; set; }
        public string Crayfish_Notes { get; set; }
        public string Mussel_Notes { get; set; }

    }
}

TypeOne.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

#nullable disable

namespace EnvApp.Models.DB
{
    [Table("Type_One")]
    public partial class TypeOne
    {
        [Key]
        public long ID { get; set; }
        [MaxLength(50)]
        public string State_Project_Number { get; set; }
        [MaxLength(50)]
        public string Federal_Project_Number { get; set; }
        [MaxLength(50)]
        public string Name { get; set; }
        [MaxLength(50)]
        public string Route_Number { get; set; }
        public string County { get; set; }
        [MaxLength(50)]
        public string Work_Type { get; set; }
        [MaxLength(100)]
        public string Coordinates { get; set; }
        public string Project_Description { get; set; }
        public bool? Federal_Aid { get; set; }
        public bool? Minimal_Project_Verification { get; set; }
        [MaxLength(3)]
        public string CE_Category { get; set; }
        [MaxLength(10)]
        public string Amms { get; set; }
        public bool Activities_Agreement { get; set; }
        public string Arch_RE { get; set; }
        public string Hist_RE { get; set; }
        public DateTime? Arch_RE_Date { get; set; }
        public DateTime? Hist_RE_Date { get; set; }
        public bool? Through_Lanes { get; set; }
        public bool? Close_Road { get; set; }
        public bool? ROW_Acquisition { get; set; }
        public bool? Access_Control { get; set; }
        public bool? Fifty_Year_Structure { get; set; }
        public bool? Agency_Coordination { get; set; }
        public bool? IPAC_Screening_Zone { get; set; }
        public bool? Section_404_Permit { get; set; }
        public bool? Ground_Disturbance { get; set; }
        public bool? Waterway { get; set; }
        public bool? Special_Use_Permit { get; set; }
        public bool? Floodplain { get; set; }
        public string Prepared_By { get; set; }
        public string Approved_By { get; set; }
        public string Adduser { get; set; }
        public DateTime? Date_Added { get; set; }
    }
}

Natural_ResourcesContext

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

#nullable disable

namespace EnvApp.Models.DB
{
    public partial class Natural_ResourcesContext : DbContext
    {
        public Natural_ResourcesContext()
        {
        }

        public Natural_ResourcesContext(DbContextOptions<Natural_ResourcesContext> options)
            : base(options)
        {
        }

        public virtual DbSet<NR_User> NR_Users { get; set; }
        public virtual DbSet<Project_Screen> Project_Screen { get; set; }
        public virtual DbSet<TypeOne> TypeOne { get; set; }
        partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
    } 
}

Solution

  • The error is because you are using foreach on the view model itself, not on the properties you want to enumerate.

    For Project_Screen and TypeOne, declare them as an enumerable type (eg. Array, List, IEnumerable, etc).

    public class MyProjectsViewModel
    {
        public IEnumerable<Project_Screen> Project_Screen { get; set; }
        public IEnumerable<TypeOne> TypeOne { get; set; }
    }
    

    You will need to populate those with a collection of values from the controller

    var viewModel = new MyProjectsViewModel();
    
    viewModel.Project_Screen = _context.Project_Screen.Where(x => SOME_CONDITION);
    viewModel.TypeOne = _context.TypeOne.Where(x => SOME_CONDITION);
    

    In the view you will enumerate those properties (not the model itself).

    @foreach (var item in Model.Project_Screen)
    

    and

    @foreach (var item in Model.TypeOne)