HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Select list items in ViewModel fetched using a Repository

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
fetcheditemsviewmodelusingrepositoryselectlist

Problem

Doing database access in ViewModel is generally considered a bad practice. However, when you have a ` in your view, you need to populate it using the ViewBag or the ViewModel (I prefer the ViewModel). This leads to the following code:

public class NewUserViewModel
{
    public string Username {get;set;}
    public IEnumerable Cities {get;set;}
    public User User // Helper to retrieve user
    {
        get { return new User() { Username = this.Username; }}
        set { this.Username = value.Username; }
    }
}


We now have to populate the Cities twice:

public ActionResult New()
{
    var vm = new NewUserViewModel();
    vm.Cities = addressRepository.GetAllCities().Select(c => new SelectListItem() { Text = c.Name, Value = c.Id });
    return View(vm);
}

[HttpPost]
public ActionResult Create(NewUserViewModel vm)
{
    if(ModelState.IsValid)
    {
        userRepository.Add(vm.User);
        return RedirectToAction("Index");
    }
    else
    {
        vm.Cities = addressRepository.GetAllCities().Select(c => new SelectListItem() { Text = c.Name, Value = c.Id }); // Copy this line
        return View(vm);
    }
}


I came up with an approach to solve this problem:

public NewUserViewModel(AddressRepository addressRepository) {
    this.Cities = addressRepository.GetAllCities().Select(c => new SelectListItem() { Text = c.Name, Value = c.Id });
}


I overrode the DefaultModelBinder to inject the Repository into the ViewModel.

protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
    return DependencyResolver.Current.GetService(modelType);
}


Now my controller is free:

``
public ActionResult New()
{
// Is this bad too?
return View(DependencyResolver.Current.GetService());
}

public ActionResult Create(NewUserViewModel vm)
{
if(ModelState.IsValid)
{
userRepository.Add(vm.User);
return RedirectToAction("Index");
}
else

Solution

Yes I think its bad practise because you have introduced both complexity and dependencies when it simply isn't necessary. Its going to make your unit testing more difficult than it needs to be and it isn't the intended purpose of the your view models.

Just create a private method in your controller that loads the data and populates the ListItems and call it where necessary. Agreed that it's not pretty but it will cause you less issues in the long run than what you are doing now.

public class AdminController : Controller
{
    ...ctor etc...

    public ActionResult New()
    {
        var vm = new NewUserViewModel();
        PopulateListItems(vm);
        return View(vm);
    }

    [HttpPost]
    public ActionResult Create(NewUserViewModel vm)
    {
        if (ModelState.IsValid)
        {
            userRepository.Add(vm.User);
            return RedirectToAction("Index");
        }
        else
        {
            PopulateListItems(vm);
            return View(vm);
        }
    }

    private static void PopulateListItems(NewUserViewModel vm)
    {
        vm.Cities = _addressRepository.GetAllCities().Select(c => new SelectListItem() { Text = c.Name, Value = c.Id });
    }
}

Code Snippets

public class AdminController : Controller
{
    ...ctor etc...

    public ActionResult New()
    {
        var vm = new NewUserViewModel();
        PopulateListItems(vm);
        return View(vm);
    }

    [HttpPost]
    public ActionResult Create(NewUserViewModel vm)
    {
        if (ModelState.IsValid)
        {
            userRepository.Add(vm.User);
            return RedirectToAction("Index");
        }
        else
        {
            PopulateListItems(vm);
            return View(vm);
        }
    }

    private static void PopulateListItems(NewUserViewModel vm)
    {
        vm.Cities = _addressRepository.GetAllCities().Select(c => new SelectListItem() { Text = c.Name, Value = c.Id });
    }
}

Context

StackExchange Code Review Q#26422, answer score: 3

Revisions (0)

No revisions yet.