ViewModels with SelectList Design Decison

2019-03-30 08:27发布

问题:

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.

回答1:

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.



回答2:

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 = ...
    ...
}