Who populates the ViewModel in ASP MVC 5
Asked Answered
P

4

11

Whose responsibility is it to populate the values in an ASP MVC 5 architecture (C#, EF), for e.g. if we have PurchaseRecordsViewModel , PurchaseRecords Domain Model , PurchaseController

  • Does the code to populate data (time, cost etc) go it the viewmodel, right in its very own the viewmodel go in the PurchaseRecordsViewModel ?

  • Or, does the code go in the Action method of the PurchaseController

Pupil answered 15/10, 2014 at 20:11 Comment(0)
W
10

Expanding upon Tommy's answer, here is some code to go along with his description.

//Controller

public ActionResult Index()
{
  List<OrderViewModel>() model = new List<OrderViewModel>();  
  model = new ServiceClass().GetOrders();

  return View(model);
}

//here is your Service Class, this layer transfers the Domain Model into your ViewModel
public List<OrderViewModel> GetOrders()
{
   List<OrderDomain> model = new List<OrderDomain>();

   model = new DataAccess().GetOrders();

   List<OrderViewModel> viewModel = new List<OrderViewModel>();

   foreach (var order in model)
   {
        OrderViewModel vm = new OrderViewModel();
        vm.OrderId = order.OrderId;
        vm.OrderName = order.OrderName;

        viewModel.Add(vm);
   }      

    return viewModel;        
}

//some DataAccess class, this class is used for database access

Public List<OrderDomain> GetOrders()
{
     List<OrderDomain> model = new List<OrderDomain>();

      using (var context = new MyEntities())
      {
          model = (from x in context.Order
                   select new OrderDomain
                   {
                     OrderId = x.OrderId,
                     OrderName = x.OrderName
                   }).ToList();                     
      }
   return model;
}

Edit: This seems to be a mildly popular answer so I would like to mention I no longer follow this pattern. Instead I've been using mediatr and vertical slice architecture.

What answered 15/10, 2014 at 20:52 Comment(7)
well explained! In the populate do you mean something like DBContext<tableClass>.Populate() - My deeper question is what happens with relationships between tables, who is handling that responsibility, for e.g. like in a master details grid scenarioPupil
No Populate() is just the name of the method I am calling. Your DataAccess class will handle database operations. I'll reverse the ordering so the code will execute sequentially as you read, starting with the controller.What
@What - I am trying to figure out right approach for my project and came across this post and your Answer... I have one doubt: Generally we keep ViewModel in Web Project, whereas Service in separate Business Layer project, In this case, to populate viewmodel in service layer, I have to add reference to web project. Isn't that bit odd ?Kerakerala
@MilindThakkar The service project won't return the viewmodel, instead you will return a "Response data transfer object" . You're web project will receive the response and will map the response dto to the view model (look into automapper for mapping objects)What
@What More confusion :-) In the example above, service is returning ViewModel. Secondly, if web project (mostly in controller) if I have to map from DTO to ViewModel, then why not map from EntityModel to ViewModel in there (in controller),Kerakerala
You cannot return a model defined in your web application from your service layer as your web application depends on the service layer. You need to define this model, whatever you want to call it somewhere that both your service and web projects can access it.Vantage
@Tommy: Tommy, CSharper: I think I am seeing the light :-) ThanksKerakerala
V
13

View models are typically just dumb collections of properties. Populating a view model typically rests inside of your service layer or, if you don't have one, your action method.

Think of the roles this way.

  • A domain model is a direct mapping to a database table.
  • A view model is a collection of properties needed to display a view.
  • A service layer gets/uses one or more domain models and populates a view model.
  • A service layer also can take a view model and create/update one or more domain models
  • A controller action method is the glue between the two. It calls a service layer to get (GET) a view model and passes it to a view. These action methods also take (POST) a view model and pass it to the service layer to do whatever needs to be done to it.

Another question typically asked is why can't I use domain models for a view? You can, but typically you run into things like, needing data from more than one domain model, not needing all the properties that are in the domain model and lastly, you now would have to worry about properties being updated on the domain model that you did not intend.

Vantage answered 15/10, 2014 at 20:34 Comment(9)
Even from your answer Service layer smells like extra layer. Except enterprise apps, service layer is overkill.Humanitarian
@Humanitarian - actually, most MVC apps that I have ever seen make use of a service layer (business layer). Regardless, I am using the "service" layer here as an example to show how you use and populate domain and view models. You are right, it doesn't matter where your view models are generated. However, many examples here on SO and elsewhere(codebetter.com/iancooper/2008/12/03/the-fat-controller) caution against "fat" controllers.Vantage
I agree with Tommy, skinny controllers are definitely better than fat onesWhat
@Vantage with the new web api is there a change in this approach since services come with web api 2. But then now there is MVC 6, which is another confusion item as I upgrade... can you expand on which one to pick and what they offer form your perpectivePupil
@Vantage - Hi, I have one doubt as explained in the answer of CSharper above (not repeating un-necessarily here). Would like to know your views please.Kerakerala
@MilindThakkar I am not sure I understand your question?Vantage
@Tommy: I am trying to figure out right approach for my project and came across this post and your Answer... I have one doubt: Generally we keep ViewModel in Web Project, whereas Service in separate Business Layer project, In this case, as you said "A service layer gets/uses one or more domain models and populates a view model", I have to add reference to web project (where my viewmodel resides). Isn't that bit odd ?Kerakerala
ViewModels should not be at the web project simply because as you kind of stated, how do you return that from the service layer?!? My view models are usually defined in the service layer. Regardless, you need to have them somewhere that both your service layer and web layer can access without creating a circular reference. Hope this helps?Vantage
To avoid fat controller and extra service layer, it is good idea to populate ViewModel properties inside ViewModel itself ? I have this approach at one of my project but i did not know the service layer pattern.Medea
W
10

Expanding upon Tommy's answer, here is some code to go along with his description.

//Controller

public ActionResult Index()
{
  List<OrderViewModel>() model = new List<OrderViewModel>();  
  model = new ServiceClass().GetOrders();

  return View(model);
}

//here is your Service Class, this layer transfers the Domain Model into your ViewModel
public List<OrderViewModel> GetOrders()
{
   List<OrderDomain> model = new List<OrderDomain>();

   model = new DataAccess().GetOrders();

   List<OrderViewModel> viewModel = new List<OrderViewModel>();

   foreach (var order in model)
   {
        OrderViewModel vm = new OrderViewModel();
        vm.OrderId = order.OrderId;
        vm.OrderName = order.OrderName;

        viewModel.Add(vm);
   }      

    return viewModel;        
}

//some DataAccess class, this class is used for database access

Public List<OrderDomain> GetOrders()
{
     List<OrderDomain> model = new List<OrderDomain>();

      using (var context = new MyEntities())
      {
          model = (from x in context.Order
                   select new OrderDomain
                   {
                     OrderId = x.OrderId,
                     OrderName = x.OrderName
                   }).ToList();                     
      }
   return model;
}

Edit: This seems to be a mildly popular answer so I would like to mention I no longer follow this pattern. Instead I've been using mediatr and vertical slice architecture.

What answered 15/10, 2014 at 20:52 Comment(7)
well explained! In the populate do you mean something like DBContext<tableClass>.Populate() - My deeper question is what happens with relationships between tables, who is handling that responsibility, for e.g. like in a master details grid scenarioPupil
No Populate() is just the name of the method I am calling. Your DataAccess class will handle database operations. I'll reverse the ordering so the code will execute sequentially as you read, starting with the controller.What
@What - I am trying to figure out right approach for my project and came across this post and your Answer... I have one doubt: Generally we keep ViewModel in Web Project, whereas Service in separate Business Layer project, In this case, to populate viewmodel in service layer, I have to add reference to web project. Isn't that bit odd ?Kerakerala
@MilindThakkar The service project won't return the viewmodel, instead you will return a "Response data transfer object" . You're web project will receive the response and will map the response dto to the view model (look into automapper for mapping objects)What
@What More confusion :-) In the example above, service is returning ViewModel. Secondly, if web project (mostly in controller) if I have to map from DTO to ViewModel, then why not map from EntityModel to ViewModel in there (in controller),Kerakerala
You cannot return a model defined in your web application from your service layer as your web application depends on the service layer. You need to define this model, whatever you want to call it somewhere that both your service and web projects can access it.Vantage
@Tommy: Tommy, CSharper: I think I am seeing the light :-) ThanksKerakerala
A
7

Ideally, PurchaseRecordViewModel should populate itself by getting PurchaseRecordsDomainModel. It should contain simple mapping of properties, and possibly some formatting of the output you're going to use in your view.

PurchaseRecordsViewModel

public class PurchaseRecordsViewModel
{
   public IEnumerable<PurchaseRecordViewModel> PurchaseRecords {get;set;}
}

PurchaseRecordViewModel

 public class PurchaseRecordViewModel
 {
    public DateTime Date {get;set;}
    public decimal Cost {get;set;}
    // .... some other properties
    public PurchaseRecordsViewModel(PurchaseRecordsDomainModel domainModel)
    {
       Date = domainModel.Date;
       Cost = domainModel.Cost;
       // .... some other property mappings
    }
 }

What your action method on PurchaseController should do, is orchestrating the process of getting your PurchaseRecordsDomainModel, creation of PurchaseRecordsViewModel from PurchaseRecordsDomainModel and passing it to the View. Action method itself shouldn't contain any code that deals with connecting and retrieving data from database (in your case querying EF context), or any business logic. You should try to have loosely coupled modules, talking to each other via abstractions, this way you will ensure your application is maintainable, extensible and testable.

Also, try to draw clear separation between various layers of your system. For example, it is not a good idea to have EF entities as Domain Model Entites. You don't want your business logic layer to depend on data access layer, think of it this way, what if at some point of time in the future, you are moving away from EF and using some other ORM or even other technology to store and query data. You don't want to change business logic layer just because you're changing your data access layer. So to go from words to code in your case.

Considering that you already have your view and view model, I would create PurchaseRecordsService class in domain layer(please note depending in your case you might not use Repositories, but some other technique, this example is mainly to illustrate my point)

public class PurchaseRecordsService
{
   private readonly IPurchaseRecordsRepository _purchaseRecordsRepository;
   public PurchaseRecordsService(IPurchaseRecordsRepository purchaseRecordsRepository)
   {
      if(purchaseRecordsRepository == null)
      {
         throw new ArgumentNullException("purchaseRecordsRepository");
      }

      _purchaseRecordsRepository = purchaseRecordsRepository;
   }

   public IEnumerable<PurchaseRecordsDomainModel> GetPurchaseRecords()
   {
      // trivial case, real code can be more complex
      return _purchaseRecordsRepository.GetPurchaseRecords();
   }
}

Then in your domain layer, you could define IPurchaseRecordsRepository

public interface IPurchaseRecordsRepository
{
   IEnumerable<PurchaseRecordsDomainModel > GetPurchaseRecords();
}

The idea is, our PurchaseRecordsService needs a way to communicate with databases, so whoever uses it, must supply implementation of IPurchaseRecordsRepository. Next step is to move to our data access layer and create implementation class of IPurchaseRecordsRepository.

public class EfPurchaseRecordsRepository: IPurchaseRecordsRepository
{
   private readonly EfObjectContext _objectContext;
   public EfPurchaseRecordsRepository(string connectionString)
   {
      _objectContext = new EfObjectContext(connectionString);
   }

   public IEnumerable<PurchaseRecordsDomainModel > GetPurchaseRecords()
   {
      var purchaseRecords = (from p in _objectContext.PurchaseRecords
                            ....
                            select p).AsEnumerable();

      return purchaseRecords .Select(p => p.ConvertToDomainPurchaseRecord());
   }
}

And the last piece - we need to define our Action in PurchaseController

public class PurchaseController: Controller
{
   private readonly IPurchaseRecordsRepository _repository;

   public PurchaseController(IPurchaseRecordsRepository repository)
   {
      if(repository == null)
      {
         throw new ArgumentNullException("repository");
      }
      _repository = repository;
   }

   public ActionResult Index()
   {
      var purchaseRecordsService = new PurchaseRecordsService(_repository);

      var purchaseRecordsViewModel = new PurchaseRecordsViewModel();

      var purchaseRecords = purchaseRecordsService.GetPurchaseRecords();

      foreach(var purchaseRecord in purchaseRecords)
      {
          var purchaseRecordViewModel = new PurchaseRecordViewModel(purchaseRecord);
          purchaseRecordsViewModel.PurchaseRecords.Add(purchaseRecordViewModel);
      }

      return View(purchaseRecordsViewModel);
   }
}

To recap, what we have is loosely coupled code, our Presentation and Data Access Layers don't know about each other, and they depend only on Domain layer. If you need, you can replace MVC front end with WPF for example, move from EF to another technology, your code is testable.

Athalee answered 15/10, 2014 at 21:28 Comment(1)
All three were good answers that helped.. I liked C Shaper's response too... but you have a detailed sample!Pupil
L
3

Ideally, your view model should be unaware of your domain model, so I'd say that you put your population logic in your controller, perhaps packed away in some sort of mapping/population utility class.

But remember, when it comes to questions about where to put certain logic, personal preference goes a long way.

Liles answered 15/10, 2014 at 20:36 Comment(1)
Ok, so thats user/devleoper discretion!!Pupil

© 2022 - 2024 — McMap. All rights reserved.