ASP.NET MVC: using same ViewModel for rendering page and getting results

asp.net-mvc

Here's simple view model that I use:

public class ViewModel
{
  public Order Order { get; set; }
  // returned from page - also can be pre-selected
  public string[] SelectedProducts { get; set; }
  // data for page to render available products to select from
  public IList<ProductViewModel> AvailableProducts { get; set; }
}

public ActionResult Edit(Order order)
{
   return View(new ViewModel { Order = order, SelectedProducts = new string[0], AvailableProducts = repository.GetAvailable() });
}

(note that Order is auto-bound by custom model binder on POST)

Now, I do return View(new ViewModel(…)) and it works. GET /Edit page renders available products, user selects them and then submitted POST /Edit action picks selected from SelectedProducts.

The problem is when user input is invalid – I need to re-display the page without touching entered user data, while still providing AvailableProducts. Just return View(data) won't work because AvailableProducts is not part of the submitted data and is null.

The question is: how do I solve the problem? I can do

public ActionResult(ViewModel data)
{
   if (!ModelState.IsValid)
   {
      data.AvailableProducts = repository.GetAvailable();
      return View(data);
   }
}

but it is a bit verbose, and error-prone since it's not obvious what properties are submitted and what are to be re-set. Is there a better way?

For example, I can have ViewModel getting the data itself (e.g. using ServiceLocator) but this is not good since the controller should prepare the data.

Best Answer

I don't know of a better way to do it, though you might want to refactor it into a separate method that can be re-used by both actions.

private ViewModel PopulateModelForView( Order order, string[] products )
{
     return new ViewModel
            {
                Order = order,
                SelectedProducts = products ?? new string[],
                AvailableProducts = repository.GetAvailable()
            };
}

public ActionResult Edit(Order order)
{
   return View( PopulateModelForView( order, null) );
}

public ActionResult Update(ViewModel data)
{
   if (!ModelState.IsValid)
   {
      return View( PopulateModelForView( data.Order, data.SelectedProducts ) );
   }
}
Related Topic