DropDownList items null when posting to edit form
So I have a page to edit employees.
Here is my view model:
public class EmployeesViewModel
{
[HiddenInput(DisplayValue = false)]
public int EmployeeId { get; set; }
[Required(ErrorMessage = "Position is required")]
[DisplayName("Position")]
public int EmployeeTypeId { get; set; }
[Required(ErrorMessage = "Name is required")]
[DisplayName("Name")]
public string Name { get; set; }
public IEnumerable<EmployeeType> EmployeeTypes { get; set; }
}
Here is my controller:
public class EmployeesController : Controller
{
public ActionResult Edit(int id)
{
//get employee from id
var employee = GetEmployee(id);
if (employee != n开发者_如何学Cull)
{
var viewModel = new EmployeesViewModel
{
EmployeeId = employee.EmployeeID,
EmployeeTypeId = employee.EmployeeTypeID,
Name = employee.Name,
EmployeeTypes = _adminRepository.GetAllEmployeeTypes(),
};
return View(viewModel);
}
//if no employee exists for this id, redirect to the Create page and display a friendly message
TempData["message"] = "No employee exists with an ID of " + id + ", you can create a new employee here.";
return RedirectToAction("Create");
}
[HttpPost]
public ActionResult Edit(EmployeesViewModel viewModel)
{
//if editing an employee, fetch it; otherwise, create a new one
Employee employee = GetEmployee(viewModel.EmployeeId);
TryUpdateModel(employee);
if (ModelState.IsValid)
{
SaveEmployee(employee);
TempData["message"] = "Employee has been saved.";
return RedirectToAction("Details", new { id = employee.EmployeeID });
}
return View(viewModel); // validation error, so redisplay same view
}
}
And my Edit view page:
<%@ Page Title="" Language="C#" MasterPageFile="/Admin.Master" Inherits="System.Web.Mvc.ViewPage<EmployeesViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<h1>Edit Employee</h1>
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm("Edit", "Employees", FormMethod.Post)) { %>
<%: Html.HiddenFor(m => m.EmployeeId)%>
<div class="editor-label"><%: Html.LabelFor(m => m.EmployeeTypeId) %></div>
<div class="editor-field">
<%= Html.DropDownListFor(m => m.EmployeeTypeId, new SelectList(Model.EmployeeTypes, "EmployeeTypeID", "Position", Model.EmployeeTypeId), "- Select an Employee Type -")%>
<%: Html.ValidationMessageFor(m => m.EmployeeTypeId)%>
</div>
<div class="editor-label"><%: Html.LabelFor(m => m.Name) %></div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Name)%>
<%: Html.ValidationMessageFor(m => m.Name)%>
</div>
<p>
<input type="submit" value="Save" />
<%: Html.ActionLink("Cancel", "Index") %>
</p>
<% } %>
</asp:Content>
After submitting my form, it fails at if (ModelState.IsValid)
. It tries to redisplay the view when return View(viewModel);
is called, and I get this error message:
Value cannot be null.
Parameter name: items
<%= Html.DropDownListFor(m => m.EmployeeTypeId, new SelectList(Model.EmployeeTypes, "EmployeeTypeID", "Position", Model.EmployeeTypeId), "- Select an Employee Type -")%>
I'm not sure why this is happening. The dropdown is loaded correctly when I navigate to the page, but not when the view is redisplayed.
Does anyone know what's going on here?
You need to reload the EmployeeTypes
property on your view model from your repository before redisplaying the view in case of error. This property is never posted so it would be always be null inside your POST action and when the view is rendered the helper would throw an exception:
[HttpPost]
public ActionResult Edit(EmployeesViewModel viewModel)
{
//if editing an employee, fetch it; otherwise, create a new one
Employee employee = GetEmployee(viewModel.EmployeeId);
TryUpdateModel(employee);
if (ModelState.IsValid)
{
SaveEmployee(employee);
TempData["message"] = "Employee has been saved.";
return RedirectToAction("Details", new { id = employee.EmployeeID });
}
// Reload employee types from repository before redisplaying the view
viewModel.EmployeeTypes = _adminRepository.GetAllEmployeeTypes();
// validation error, so redisplay same view
return View(viewModel);
}
In EmployeesController.Edit(EmployeesViewModel viewModel)
you need to have the following line before you return the view:
EmployeeTypes = _adminRepository.GetAllEmployeeTypes()
In your initial EmployeesController.Edit(int id)
you do this, and it shows up. The reason you don't have to populate the rest of your view model is because their values are submitted with the form. However, the only value for the EmployeeType that is submitted is the one that is selected. The values for the view have to come from somewhere and you aren't putting them anywhere in that controller action.
I don't suggest doing this, but another alternative would be to put the array of employee types (delimited by commas) in a hidden input field named EmployeeTypes
on your View.,, because then the values would at least be submitted to the model binder. It is a better design to get these values from the database.
Code
public ActionResult Edit(int id)
{
var employee = GetEmployee(id);
if(employee == null)
{
TempData["message"] = "No employee exists with an ID of " + id + ", you can create a new employee here.";
return RedirectToAction("Create");
}
var viewModel = new EmployeesViewModel
{
EmployeeId = employee.EmployeeID,
EmployeeTypeId = employee.EmployeeTypeID,
Name = employee.Name
};
return EditEmployeeView(viewModel);
}
public ActionResult EditEmployeeView(EmployeesViewModel viewModel)
{
viewModel.EmployeeTypes = viewModel.EmployeeTypes ?? EmployeeTypes = _adminRepository.GetAllEmployeeTypes();
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(EmployeesViewModel viewModel)
{
Employee employee = GetEmployee(viewModel.EmployeeId);
TryUpdateModel(employee);
if (ModelState.IsValid)
{
SaveEmployee(employee);
TempData["message"] = "Employee has been saved.";
return RedirectToAction("Details", new { id = employee.EmployeeID });
}
return EditEmployeeView(viewModel);
}
精彩评论