Search code examples
model-view-controllerrazorcomputer-science

MVC How to Enumerate through a Second Model's Data


On the "Create" view I am trying to enumerate through some secondary/external model data. Instead, the page returns a NullReferenceError and I can't figure out why the Model is null. If I filter for IsNull on the enumerating fields that are null, the page will load.

Below is the Create functions inside the Controller:

public IActionResult Create()
{
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("SOFT_ID,SOFT_NAME,DEPT_ID,IT_CONTACT,SOFT_EXP_DATE,SOFT_SUP_PERIOD,SOFT_OUT_OF_SERVICE,VEND_ID,SUPP_ID,SOFT_IS_RENEWED")] Software software)
{
    if (ModelState.IsValid)
    {
        _context.Add(software);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View();
}

Next are the Models. Below is the Software model, which is the main model, and the Department model which is the secondary models. There are two other models I want to load data from and they are both set up exactly like the Department Model.

Software Model:

public class Software
{
    [Key]
    public int SOFT_ID { get; set; }
    [Required]
    [StringLength(50, ErrorMessage = "Software Name cannont be longer than 50 characters.")]
    [Display(Name = "Software Name")]
    public string SOFT_NAME { get; set; }
    [Required]
    public virtual Department DEPT_ID { get; set; }
    [Required]
    [Display(Name = "IT Contact")]
    public string IT_CONTACT { get; set; }
    [Required]
    [Display(Name = "Expiration Date")]
    [DataType(DataType.Date)]
    public DateTime SOFT_EXP_DATE { get; set; }
    [Required]
    [Range(1,5)]
    [Display(Name = "Support Period")]
    public int SOFT_SUP_PERIOD { get; set; }
    [Display(Name = "No Longer Used")]
    public bool SOFT_OUT_OF_SERVICE { get; set; }
    [Required]
    public virtual Vendor VEND_ID { get; set; }
    [Required]
    public virtual Supplier SUPP_ID { get; set; }
    [Display(Name = "Renewed?")]
    public bool SOFT_IS_RENEWED { get; set; }
    
    public IEnumerable<Department> Departments { get; set; }
    public IEnumerable<Supplier> Suppliers { get; set; }
    public IEnumerable<Vendor> Vendors { get; set; }
}

Department Model:

public class Department
{
    [Key]
    public int DEPT_ID { get; set; }
    [Required]
    [Display(Name = "Department Name")]
    public string DEPT_NAME { get; set; }
    [Required]
    [Display(Name = "Department Contact Name")]
    public string DEPT_CONTACT { get; set; }
    [Required]
    [Display(Name = "Email")]
    public string DEPT_EMAIL { get; set; }
    [Required]
    [Display(Name = "Phone")]
    public string DEPT_PHONE { get; set; }
}

And finally there is the View. The sections that are throwing the NullReferenceError are the @foreach loops for the departments, suppliers, and vendors. These are supposed to be populating drop down menus.

@model SoftwareManager_SSO.Models.Software

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

<h1>Create</h1>


<h4>Software</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="SOFT_NAME" class="control-label"></label>
                <input asp-for="SOFT_NAME" class="form-control" />
                <span asp-validation-for="SOFT_NAME" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="DEPT_ID.DEPT_NAME" class="control-label"></label>
                <select class="form-control">
                    <option value="" disabled selected>Select A Dept</option>
                    @foreach (var item in Model.Departments)
                    {
                        <option value="@item.DEPT_ID">@item.DEPT_NAME</option>
                    }
                </select>
                <span asp-validation-for="DEPT_ID.DEPT_NAME"></span>
            </div>

            <div hidden="hidden" class="form-group">
                <label asp-for="DEPT_ID" class="control-label"></label>
                <input asp-for="DEPT_ID" class="form-control" disabled="disabled" />
                <span asp-validation-for="DEPT_ID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="DEPT_ID.DEPT_CONTACT" class="control-label"></label>
                <input id="dept_contact" asp-for="DEPT_ID.DEPT_CONTACT" disabled="disabled" class="form-control" />
                <span asp-validation-for="DEPT_ID.DEPT_CONTACT" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="IT_CONTACT" class="control-label"></label>
                <select class="form-control">
                    <option value="" selected>Select IT Person</option>
                    <option value="[email protected]">Umit Ciltas</option>
                    <option value="[email protected]">Stephen Slaven</option>
                    <option value="[email protected]">Ismael Moreno</option>
                    <option value="[email protected]">David Kearney</option>
                </select>
                <span asp-validation-for="IT_CONTACT" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="SOFT_EXP_DATE" class="control-label"></label>
                <input asp-for="SOFT_EXP_DATE" class="form-control" />
                <span asp-validation-for="SOFT_EXP_DATE" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="SOFT_SUP_PERIOD" class="control-label"></label>
                <select class="form-control">
                    <option value="" selected>Select A Support Period</option>
                    <option value="1">1 Year</option>
                    <option value="2">2 Years</option>
                    <option value="3">3 Years</option>
                    <option value="4">4 Years</option>
                    <option value="5">5 Years</option>
                </select>
                <span asp-validation-for="SOFT_SUP_PERIOD" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="VEND_ID.VEND_NAME" class="control-label"></label>
                <select class="form-control">
                    <option value="" disabled selected>Select a Vendor</option>
                    @foreach (var item in Model.Vendors)
                    {
                        <option value="@item.VEND_ID">@item.VEND_NAME</option>
                    }
                </select>
                <span asp-validation-for="VEND_ID.VEND_NAME"></span>
            </div>

            <div class="form-group">
                <label asp-for="SUPP_ID.SUPP_NAME" class="control-label"></label>
                <select class="form-control">
                    <option value="" disabled selected>Select a Vendor</option>
                    @foreach (var item in Model.Suppliers)
                    {
                        <option value="@item.SUPP_ID">@item.SUPP_NAME</option>
                    }
                </select>
                <span asp-validation-for="SUPP_ID.SUPP_NAME"></span>
            </div>

            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="SOFT_IS_RENEWED" /> @Html.DisplayNameFor(model => model.SOFT_IS_RENEWED)
                </label>
            </div>
            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="SOFT_OUT_OF_SERVICE" /> @Html.DisplayNameFor(model => model.SOFT_OUT_OF_SERVICE)
                </label>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

I am quite honestly at a loss and can't figure this out. I've read through numerous other posts on this site and I can't figure out what is different between my code and similar code that I've seen posted. I will continue to search though posts, but any help is appreciated.


Solution

  • You need to pass the information about those departments into the view. This is typically done by creating a viewmodel and passing it to the view.

    In your code, your view is expecting a "software" model, so you could create one (and be sure to populate "departments") and pass that into your view.

    Once you have the model, you could use a Html helper to generate the drop-down, rather than manually building it using a loop.

    For example:

    @Html.DropDownListFor(model => model.departments )  
    

    Or maybe

    @Html.DropDownListFor(model => model.departments as selectlist)
    

    Sorry if some syntax not quite right, on my mobile so can't check very well. But that should give you the right idea.

    EDIT

    You need to build the view model in your controller and pass it to the view.

    var viewModel = new Software();
    viewModel.Departments = new List<Department>() {
        new Department() { DEPT_NAME = "Some department name"}
    };
    return View(viewModel);
    

    Or if pulling dynamically from database (for example via entity framework):

    var viewModel = new Software();
    viewModel.Departments = _db.Departments.ToList()
    return View(viewModel);