aggie
aggie

Reputation: 798

Who populates the ViewModel in ASP MVC 5

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

Upvotes: 11

Views: 10300

Answers (4)

CSharper
CSharper

Reputation: 5580

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.

Upvotes: 10

Michael
Michael

Reputation: 3061

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.

Upvotes: 7

Tobias
Tobias

Reputation: 2840

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.

Upvotes: 3

Tommy
Tommy

Reputation: 39807

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.

Upvotes: 13

Related Questions