R – Who has the responsibilty of loading data

asp.net-mvcmodel-view-controller

In the MVC model, where does the responsibility of loading the view model lie?

Should the Controller load the data?
Should the View Model itself load the data ala:
MyViewModel viewModel = MyViewModel.Create(someValue);
Should a Service Layer load it ala:
MyViewModel viewModel = MembershipService.Instance.Load(someValue);

Best Answer

See this example of the really clean technique: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/06/29/how-we-do-mvc-view-models.aspx

Alternatively you can do it manually: see "ASP.NET MVC In Action" book or CodeCampServer sources for examples. Basically you inject IViewModelMapper { public ViewModel Map(data); } to the controller. The neat thing is that it makes your IoC automatically pass services and repositories to your ViewModel mapper. However this can really make controllers be bloated with mapper interfaces so something like Jimmy Bogard's technique, even without AutoMapper, but with action filters than do pick IViewModelMapper, would be better.

If you can't do this, then I'd suggest stick with ViewModel handling mapping as Mathias suggested.

UPDATE: here's an example of automapper-like configuration with bits of CodeCampServer way. Not sure if it will work as is (or useful at all), just a demonstration.

public abstract class ViewModelMapper<Source, ViewModel> where Source: class, ViewModel: IViewModel
{
  public abstract ViewModel Map(Source source);
}

public class ProductDetailsViewModel
{
  public ProductViewModel Product { get; set; }
  punlic IList<Language> AvailableProductLanguages { get; set; }
}

public class ProductDetailsViewMapper: ViewModelMapper<Product, ProductDetailsViewModel>
{
  private ILanguageRepository languages;
  public ProductDetailsViewMapper(ILanguageRepository languages)
  {
     this.languages = languages;
  }
  public override ProductDetailsViewModel Map(Product source)
  {
     var vm = new ProductDetailsViewModel();
     AutoMapper.Map<Product, ProductDetailsViewModel>(product, vm);
     vm.AvailableProductLanguages = languages.GetAppropriateFor(product);
  }
}

public class ViewModelMapperActionFilter: ActionFilter
{
  Type mapperType;
  public ViewModelMapperActionFilter()
  {
  }
  public ViewModelMapperActionFilter(Type mapperType)
  {
  }
  public void OnActionExecuted(ControllerContext context)
  {
    var model = context.Result.ViewData.Model;
    var mapperType = this.MapperType ?? this.GetMapperTypeFromContext(context);
    // this is where magic happens - IoC grabs all required dependencies
    var mapper = ServiceLocator.GetInstance(mapperType);
    var method = mapperType.GetMethod("Map");
    Check.Assert(method.GetArguments()[0].ArgumentType == model.GetType());
    context.Result.ViewData.Model = method.Invoke(mapper, new[]{model});
  }
}

public class ProductsController: Controller
{
  [ViewModelMapper(typeof(ProductDetailsViewMapper))]
  // alternatively [ViewModelMapper()] will auto-pick mapper name by controller/action
  public ActionResult Details(EntityViewModel<Product> product)
  {
    // EntityViewModel is a special type, see 
    // http://stackoverflow.com/questions/1453641/my-custom-asp-net-mvc-entity-binding-is-it-a-good-solution
    return View(product.Instance); 
  }
}

//Global.asax.cs:
IoC.Register(AllTypes.DerivedFrom(typeof(ViewModelMapper<>)));