I have an ASP.NET MVC 5 site.
I have several complex view models – usually subsets of a far larger model. Should I create items in the repository that take the ViewModels – then just use Dapper.net to map them to some SQL?
If not I would have to map the ViewModel to the Model somewhere. I can then use the standard Model Insert or Update. This causes two problems – how and where to map from ViewModel to Model (I don't want to create lots of work).
Also, where I am updating a subset of fields of a larger model – I want to avoid setting the fields that aren't in the ViewModel to NULL. If I just map a ViewModel to a Model – I will lose data.
It seems the simplest easiest solution is just to pass ViewModels in and out of Repositories. But is this bad practice? If so how can I make my other option (or a third option) work better?
Best Answer
This wouldn't necessarily be wrong for a very small, trivial app, or a quick'n'dirty KISS/YAGNI solution just to get something out of the door with minimal time or effort, but doesn't really fit with the spirit of the MVC pattern so I would caution against that as a general approach.
A few notes on MVC:
By pushing ViewModels directly into your Repository, you risk introducing technical debt in a number of ways:
If anything about your View changes, it can affect the ViewModel, which means it potentially affects the
Repository
too; this exposes you to the risk of future changes cascading and being "snowballed" across the app unnecessarily.Other views needing to re-use the same repository method would also need to know about the ViewModel used by the repository; even if those Views have their own separate ViewModels - that adds further danger to the cascading/snowballing effect of changes to the ViewModel.
Using the ViewModel in this way implies direct interaction between the
Controller
and aRepository
- that's all good in simple cases, but for anything nontrivial it involves additional logic which doesn't strictly belong in either class:Controller
should ideally be limited to Creating aViewModel
from the service/business layer and injecting it into aView
(or conversely, receiving an HTTP Request and using the request data to call into the service/business layer).Repository
should ideally be limited to object persistence.Arguably, using a ViewModel in this way means it ceases to be a real ViewModel because you would need to move it into your Business layer or service layer to work; the result is having that layer effectively 'contaminated' by UI-specific concerns
When a simple call into your repository isn't enough and you need additional business logic to pull down the right kind of data into your controller, consider putting that logic into a separate
Service
class to ensure that neither your controllers nor repositories have that burden, for example:Bridging the divide between your Business Layer and your Presentation Layer is the responsibility of your Controller, which includes mapping to/from your ViewModels. You could consider doing the actual work within Helper methods or use a library such as AutoMapper. (Related question on SO)
Sometimes its easier to use a different model for your POST and PUT requests. A ViewModel is for your UI state, so it's only real purpose is to be used in an action which returns a
View()
.POST/PUT requests are about issuing commands, and a POST/PUT action usually isn't expected to
return View(model);
(usually aRedirectToAction
instead). A POST/PUT action usually only needs to be a 'pass through' to some service class, in which case the domain models could be used for the request instead.Update in response to the comment about Data Annotations:
Luckily, Dapper itself ignores Data Annotations so that shouldn't cause you any problems. Most of the time each framework uses it's own Data Annotations in fact; the real danger of using across many layers is not really a technical one and more of a 'code cleanliness' issue, and conceptually it starts to feel like a violation of SRP (a single model which serves different purpose depending where it's used).
As an aside, I once reviewed some code whereby somebody had managed to reuse the same Model for a whole bunch of different libraries and tools - it had various properties and attributes which were used in different frameworks in it. The reality of reusing Models in too many places can often look a bit like this:
As somebody trying to read a model like this and figure out which attributes or properties are used at which layer it becomes a lot to think about; and all in the name of merely "saving" a few lines of code to map between models.
Furthermore, you do run the risk that some of the annotations could end up having an effect in the wrong place; again this is more of a risk with an ORM such as EntityFramework which also uses some of those same attributes as ASP.NET