I've had issues with viewmodels for a while and I'd like to clear something up. In my viewmodel, I can show "index" and I can add a new employee "create", but the "Edit" doesn't work.
I can show the "edit" page, do my edits (like changing the Name) but when I post back, all the data shows up as null. In the "create", after I post the insert, the controller DOES SHOW the changes (EmployeeViewModel) and inserts the record. It just doesn't show when a do the "edit".
Is this something inherent in viewmodel's or is there something else?
Here's my viewmodel class (database first):
public partial class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public Nullable<int> DepartmentId { get; set; }
public string Address { get; set; }
public virtual Department Department { get; set; }
}
public partial class Department
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Department()
{
this.Employees = new HashSet<Employee>();
}
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Employee> Employees { get; set; }
}
public class EmployeeViewModel
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public Nullable<int> DepartmentId { get; set; }
public string Address { get; set; }
public string DepartmentName { get; set; }
}
Here's my controller:
public class TestController : Controller
{
public db dContext = new db();
public ActionResult Index()
{
List<Employee> employeelist = dContext.Employees.ToList();
EmployeeViewModel employeeVM = new EmployeeViewModel();
List<EmployeeViewModel> employeeVMList = employeelist.Select(x => new EmployeeViewModel
{
Name = x.Name,
EmployeeId = x.EmployeeId,
Address = x.Address,
DepartmentId = x.DepartmentId,
DepartmentName = x.Department.DepartmentName
}).ToList();
return View(employeeVMList);
}
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create( EmployeeViewModel employeeVM)
{
if (ModelState.IsValid)
{
Employee e = new Employee();
e.EmployeeId = employeeVM.EmployeeId;
e.Name = employeeVM.Name;
e.DepartmentId = employeeVM.DepartmentId;
e.Address = employeeVM.Address;
dContext.Employees.Add(e);
dContext.SaveChanges();
return RedirectToAction("Index");
}
return View("Index");
}
public ActionResult Edit( EmployeeViewModel em , int? id)
{
var dbEmpVM = (from e in dContext.Employees
join d in dContext.Departments
on e.DepartmentId equals d.DepartmentId
where e.EmployeeId == id
select new EmployeeViewModel
{
EmployeeId = e.EmployeeId,
DepartmentId=e.DepartmentId,
Address=e.Address,
Name=e.Name,
DepartmentName=d.DepartmentName
}).ToList();
return View( dbEmpVM );
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EmployeeViewModel model, int id)
{
string name = Request.Form["EmployeeId"];
string naaanm = model.EmployeeId.ToString();
return RedirectToAction("Index");
}
}
And here's my Edit:
@model IEnumerable<MVCTutorial.Models.EmployeeViewModel>
@{
ViewBag.Title = "Edit";
}
<h4>(EmployeeViewModel)</h4>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
foreach (var item in Model)
{
<div class="form-group">
@Html.LabelFor( i => item.EmployeeId, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(i => item.EmployeeId, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(i => item.EmployeeId, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(i => item.Name, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(i => item.Name, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(i => item.Name, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(i => item.DepartmentId, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(i => item.DepartmentId, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(i => item.DepartmentId, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(i => item.DepartmentName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(i => item.DepartmentName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(i => item.DepartmentName, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(i => item.Address, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(i => item.Address, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(i => item.Address, "", new { @class = "text-danger" })
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Edit" class="btn btn-default" />
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Your current code(in the edit view) will generate input markup like below.
<input class="form-control text-box single-line"
id="item_DepartmentName" name="item.DepartmentName" type="text" value="SomeName" />
See the name attribute value. It is "item.DepartmentName"
. Your HttpPost action method parameter is the EmployeeViewModel
type which does not have a property with this name! So model binder will not be able to map the form data posted from your form with this name ("item.DepartmentName"
) to any properties of your method parameter.
The solution is to update your input field's name attribute value to match with the property names of your EmployeeViewModel
. Ideally your code should generate markup like below.
<input name="DepartmentName" type="text" value="Some value" />
You can explcitily specify the name
attribute alue when calling the html helpers. For example, with the Html.TextBoxFor
helper
@Html.TextBoxFor(i => item.DepartmentName,
new { @class = "form-control", NAME = "DepartmentName" })
Now when you submit the form, Model binder will be able to map the value of this form field to the DepartmentName
property of your EmployeeViewModel
object.