Reputation: 798
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
Upvotes: 11
Views: 10300
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
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
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
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.
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