0

I'm still learning MVC and am confused. I am converting a windows application for CRUD with a database to an MVC website. I got an entire ViewModel's CRUD working that uses dropdownlist's already and the code is identical but throwing object reference not set errors with it in another page.

Controller

public ActionResult Create()
{
    var shiftsEmployees = new ShiftsEmployeesViewModel();

    var oEmployees = new CEmployees();
    oEmployees.GetActiveEmployees();

    shiftsEmployees.Employees = oEmployees;

    return View(shiftsEmployees);
}

// POST: Shifts/Create
[HttpPost]
public ActionResult Create(ShiftsEmployeesViewModel shiftsEmployees)
{
    try
    {
        shiftsEmployees.InsertShift();
        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

View

@model StatTracker.ASPMVC.ViewModels.ShiftsEmployeesViewModel

@{
ViewBag.Title = "Add Shift";
Layout = "~/Views/Shared/_Layout.cshtml";
}

@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal text-center">
    @Html.ValidationSummary(true, "", new {@class = "text-danger"})

    <div class="form-group">
        <div class="input-group date col-md-12">
            @Html.LabelFor(model => model.Shift.Date, new {@class = "control-label"})&nbsp;
            @Html.EditorFor(model => model.Shift.Date, new {@class = "form-control datepicker", placeholder = "Pick a date"})
            @Html.ValidationMessageFor(model => model.Shift.Date, "", new {@class = "text-danger"})

            @Html.LabelFor(model => model.Employee.FullName, new {@class = "control-label"})&nbsp;
            @Html.DropDownListFor(model => model.Employee.Id, new SelectList(Model.Employees.Employees, "Id", "FullName", Model.Employee), null, null)
            @Html.ValidationMessageFor(model => model.Employee.Id, "", new {@class = "text-danger"})

            @Html.LabelFor(model => model.Shift.Hours, new {@class = "control-label"})&nbsp;
            @Html.EditorFor(model => model.Shift.Hours, new {@class = "form-control", placeholder = "Hours worked"})
            @Html.ValidationMessageFor(model => model.Shift.Hours, "", new {@class = "text-danger"})
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-12">
            <input type="submit" value="Add Shift" class="btn btn-default"/>
        </div>
    </div>
</div>
}
<div class="text-center col-md-12">
      @Html.ActionLink("Back to List", "Index")
</div>

ViewModel:

namespace StatTracker.ASPMVC.ViewModels
{
    public class ShiftsEmployeesViewModel
    {
        public CShift Shift { get; set; }
        public CEmployee Employee { get; set; }
        public CEmployees Employees { get; set; }

        public void InsertShift()
        {
            CShift.InsertShift(hours: Shift.Hours, employeeid: Employee.Id, date: Shift.Date);
        }

        public void UpdateShift()
        {
            CShift.UpdateShift(hours: Shift.Hours, employeeid: Employee.Id, date: Shift.Date, shiftid: Shift.Id);
        }
    }
}

working code with same idea controller

 public ActionResult Create()
    {
        var oSalesEmployeeService = new SalesEmployeeServiceViewModel();

        var oServices = new CServices();
        oServices.GetServices();

        var oEmployees = new CEmployees();
        oEmployees.GetActiveEmployees();

        oSalesEmployeeService.Employees = oEmployees;
        oSalesEmployeeService.Services = oServices;

        return View(oSalesEmployeeService);
    }

    // POST: Sales/Create
    [HttpPost]
    public ActionResult Create(SalesEmployeeServiceViewModel oSalesEmployeeService)
    {
        try
        {
            oSalesEmployeeService.InsertSale();
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

viewmodel

using StatTracker.BL;

namespace StatTracker.ASPMVC.ViewModels
{
public class SalesEmployeeServiceViewModel
{
    public CSale Sale { get; set; }
    public CEmployees Employees { get; set; }
    public CServices Services { get; set; }
    public CEmployee Employee { get; set; }
    public CService Service { get; set; }

    public void InsertSale()
    {
        CSale.InsertSale(service: Service.Id, date: Sale.Date, employee: Employee.Id);
    }

    public void UpdateSale()
    {
        CSale.UpdateSale(service: Service.Id, date: Sale.Date, employee: Employee.Id, salesid: Sale.Id);
    }
  }
}

view

@model StatTracker.ASPMVC.ViewModels.SalesEmployeeServiceViewModel

@{
    ViewBag.Title = "Add Sale";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal text-center">
    @Html.ValidationSummary(true, "", new {@class = "text-danger"})

    <div class="form-group">
        <div class="input-group date col-md-12">
            @Html.LabelFor(model => model.Sale.Date, new {@class = "control-label"})&nbsp;
            @Html.EditorFor(model => model.Sale.Date, new {@class = "form-control datepicker", placeholder = "Pick a date"})
            @Html.ValidationMessageFor(model => model.Sale.Date, "", new {@class = "text-danger"})

            @Html.LabelFor(model => model.Employee.FullName, new {@class = "control-label"})&nbsp;
            @Html.DropDownListFor(model => model.Employee.Id, new SelectList(Model.Employees.Employees, "Id", "FullName", Model.Employee), null, null)
            @Html.ValidationMessageFor(model => model.Employee.Id, "", new {@class = "text-danger"})

            @Html.LabelFor(model => model.Service.Description, new {@class = "control-label"})&nbsp;
            @Html.DropDownListFor(model => model.Service.Id, new SelectList(Model.Services.Services, "Id", "Description", Model.Service), null, null)
            @Html.ValidationMessageFor(model => model.Service.Id, "", new {@class = "text-danger"})
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-12">
            <input type="submit" value="Add Sale" class="btn btn-default"/>
        </div>
    </div>
</div>
}

<div class="text-center col-md-12">
    @Html.ActionLink("Back to List", "Index")
</div>
Khaneliman
  • 334
  • 3
  • 13
  • This is likely to be closed very quickly as a duplicate to the master question regarding `Object reference not set to an instance of an object` unless you can demonstrate how the answers there aren't useful to your situation; More specifically, you haven't even indicated here which variables are null or which line of code is throwing the error. – Claies Apr 21 '15 at 02:19
  • It happens on the DropDownListFor... like stated in the title. That reference doesn't help me...as I show you that I am instantiating the object. I have looked over this for hours and need new eyes. – Khaneliman Apr 21 '15 at 02:22
  • The link does help you. You need to learn to debug your code, which would tell you that the value of `Model.Employees.Employees` is null when you return the view. But you have other issues as well s I have added an answer. –  Apr 21 '15 at 02:58

2 Answers2

0

Yes, because model.Employee has not been initialized and therefore, model.Employee.Id will throw a null reference exception. After that You still need to initialize the shift object as it will also throw a null reference exception when model.Shift.Hours is accessed. Please see my suggestions below

You will have to initialize the remaining properties

public ActionResult Create()
{
    var oEmployees = new CEmployees();
    oEmployees.GetActiveEmployees();

    var shiftsEmployees = new ShiftsEmployeesViewModel
    {
        Employee = new CEmployee(),
        Shift = new CShift(),
        Employees = oEmployees;
    };

    return View(shiftsEmployees);
}

Another option is to just always initialize them in the constructor of the view model (you just have to make sure that somewhere GetActiveEmployees is getting called)

public class ShiftsEmployeesViewModel
{
    public ShiftsEmployeesViewModel()
    {
        this.oEmployees = new CEmployees();
        this.Employee = new CEmployee();
        this.Shift = new CShift();
    }
 }

Also change the implementation in your view for the dropdownlist

@Html.DropDownListFor(model => model.Employee.Id, 
                      new SelectList(Model.Employees.Employees ?? new List<CEmployee>(), 
                                     "Id", 
                                     "FullName"))
Amir
  • 486
  • 4
  • 14
  • implemented both solutions and still getting an exception on the same line :S @Html.DropDownListFor(model => model.Employee.Id, new SelectList(Model.Employees.Employees, "Id", "FullName", Model.Employee), null, null) – Khaneliman Apr 21 '15 at 02:35
  • My assumption is that GetActiveEmployees is not initializing Employees. Can you try this? @Html.DropDownListFor(model => model.Employee.Id, new SelectList(Model.Employees.Employees ?? new List(), "Id", "FullName")) – Amir Apr 21 '15 at 02:50
  • same thing... this is so defeating – Khaneliman Apr 21 '15 at 03:11
0

You have not indicated exactly where the error is being thrown but there will be numerous reasons

Firstly, a view model is not just a holder for a series of data models. It should contain only those properties which you need to display/edit in the view. Currently if any properties of CShift or CEmployee have validation attributes (other that the Date and Hours properties), your model will be invalid.

Secondly, when you return the view, you need to return the model to the view and also assign the value of Employees which currently will be null (hence the exception when you access Model.Employees.Employees in the DropDownList() method).

Based on the view you have shown, your view model should be just (add validation attributes as required)

public class ShiftsEmployeesViewModel
{
  public DateTime Date { get; set; }
  public int Employee { get; set; }
  public float Hours { get; set; }
  public SelectList EmployeeList { get; set; }
}

Controller

public ActionResult Create()
{
  ShiftsEmployeesViewModel model = new ShiftsEmployeesViewModel();
  model.Employee = ? // set default selection
  ConfigureCreateModel(model);
  return View(model);
}

[HttpPost]
public ActionResult Create(ShiftsEmployeesViewModel model)
{
  if (!ModelState.IsValid)
  {
    ConfigureCreateModel(model);
    return View(model);
  }
  .... // map the view model properties to a new instance of the data model, save and redirect
}

private void ConfigureCreateModel(ShiftsEmployeesViewModel model)
{
  var oEmployees = (new CEmployees()).GetActiveEmployees();
  model.EmployeeList = new SelectList(oEmployees, "Id", "FullName");
}

View

@Html.LabelFor(m => m.Date)
@Html.TextBoxFor(m => m.Date)
@Html.ValidationMessageFor(m => m.Date)

@Html.LabelFor(m => m.Employee)
@Html.DropDownListFor(m => m.Employee, Model.EmployeeList, "-please select-")
@Html.ValidationMessageFor(m => m.Employee)

@Html.LabelFor(m => m.Hours)
@Html.EditorFor(m => m.Hours)
@Html.ValidationMessageFor(m => m.Shift.Hours)

Side note: Your current usage of SelectList(Model.Employees.Employees, "Id", "FullName", Model.Employee) where you use the 4th parameter is pointless. Your binding to a property, so attempting to set the value of the Selected is ignored (the selected option is based on the value of the property your binding to)

  • I appreciate the lessons on MVC design, I know that I was getting frustrated and my responses were less than warranting of this level of help. I am sorry I can't +1 your answer, but it solved my problem with the addition of converting CEmployee to IEnumerable. – Khaneliman Apr 21 '15 at 16:55