I have created a viewmodel
public VMPosition
{
public VMPosition(){}//for model binder
public VMPosition(int EmployeeID)
{
PositionStatusList = new SelectList(_repo.getStatuses);
//populate other properties
}
public int CurrentPositionID { get; set; }
public int EmployeeID { get; set; }
public int CurrentPositionHistoryID { get; set; }
public bool AddingNew { get; set; }
public bool ClosingCurrent { get; set; }
public string CurrentPosition { get; set; }
public DateTime CurrentPositionStartDate { get; set; }
public string ReasonForDeparture { get; set; }
public SelectList PositionStatusList { get; set; }
}
My GET ActionResult is defined as
public ActionResult UpdatePosition(int id)
{
return View(new VMPosition(id));
}
My POST actionresult is defined as
public ActionResult UpdatePosition(int id, VMPosition Position)
{
if(ModelState.IsValid){
Position Current = new Position{Position.Title etc..}
//save to db
return redirectToAction("someAction");
}
return View(Position);//here is the problem
}
My SelectList is populated in a constructor that accepts one parameter. Modelbinder cannot and should not call the constructor if the modelstate is invalid. I will have to return the View with model object (which in this case don't contain SelectList value). How can handle this scenario when using view Models.
I can manually populate these values in actionresult but that will violate the DRY principle. However, for the purposes of this question, I'd like help addressing the bigger design question.
When dealing with dropdowns in my viewmodels, I usually have a single property associated with the selected list item's value and I have a property that returns a list of selectlistitems. Then, I use Html.DropDownListFor(m => m.ValueProperty, Model.DropDownValues) to render the dropdown.
I imagine in your scenario, you don't have a value corresponding to the selected listitem's value?
Edit: Here's an example from one of my apps...
public class MyVM
{
public int MyObjectId { get; set; }
public List<SelectListItem> MyObjectList
{
get
{
List<SelectListItem> list = (from o in MyObjects select new SelectListItem
{ Value = o.ObjectId.ToString(), Text = o.ObjectName }).ToList();
list.Insert(0, new SelectListItem
{ Value = "0", Text = "[Select an object]" });
return list;
}
}
}
<%: Html.DropDownListFor(m => m.MyObjectId, Model.MyObjectList)%>
You might have noticed the LINQ query that populates the list. In this example, I have a list (MyObjects) that was already populated by AutoMapper. You could simply return a static list if you prefer.
Why not follow the convention which I reckon the majority of people use? You have coupled your ViewModel to your repo which I would also recommend changing. By putting the repo.GetStatuses inside your Controller/Action is simple and it works. I also prefer putting the SelectList inside my view and have the ViewModel house the list of items - but that's my personal preference. You can then clearly see/understand what type of objects your ViewModel deals with. DRY is a principle not a requirement.
ViewModel
public VMPosition
{
public int StatusId { get; set; }
public IList<Status> StatusList { get; set; }
}
Controller
public ActionResult UpdatePosition(int id)
{
var model = new VMPosition(id);
model.StatusList = _repo.getStatuses;
return View(model);
}
public ActionResult UpdatePosition(int id, VMPosition Position)
{
if(!ModelState.IsValid)
{
Position.StatusList = _repo.getStatuses;
return View(Position);
}
...
}
View
<%= Html.DropDownListFor(m => m.StatusId, new SelectList(Model.StatusList)...
Edit - Refactor PopulateSelectLists
public ActionResult UpdatePosition(int id)
{
var model = new VMPosition(id);
PopulateSelectLists(model);
return View(model);
}
public ActionResult UpdatePosition(int id, VMPosition Position)
{
if(!ModelState.IsValid)
{
PopulateSelectLists(Position);
return View(Position);
}
...
}
private void PopulateSelectLists(VMPosition Position)
{
Position.StatusList = _repo.GetStatuses;
Position.OtherSelectList = ...
...
}