This one is geared toward mid level developers who have been doing MVC for awhile, but find themselves in these situations:
1) They havent gone back to revisit their MVC code in quite some time
2) They are using a tutorial or a beginner’s project and now need to move the design to a production system
3) Want to take it to the next level because their application logic and MVC skills have grown.
My history with MVC goes back a few years, and I’ve seen a few ways to build (and not build) web apps with it.
Disclaimer: Don’t copy paste all 3,000 lines of your web forms code into your controller. Just don’t. That’s called technical debt, and MVC isn’t a magic bullet for it!
I’ve been a part of simple “data in data out” pages, to giant migrations from web forms, to simple classification and migration of stored procedures to a web API backend (where my happy place is now).
I want to talk about 2 critical concepts, that if ignored, will still allow you to build your web apps, but will cripple your ability to scale, refactor, and keep clean logic.
Don’t let your web app turn into “that system” that everyone hates working with and has to constantly fight to get changes in.
I’m calling these out because about a year ago I found (and subsequently proved out) a critical, but easy to miss, piece of MVC, and it’s resulted in some amazing flexibility and organization of logic, even in an enterprise grade financial services backend.
Sharing of ViewModels – Don’t Do It.
This is a classic case of developers being brainwashed that all code duplication is evil, and should be avoided at all costs.
Now, I agree that code duplication is code smell, and we should seek to minimize or eliminate it, as it pollutes logic and increases the number of
touch points when logic or data changes. But here’s an observation –
MVC ViewModels are dead classes with no logic.
The ‘S’ in SOLID. Can we define their responsibility?
A number of definitions might exist out there, but let’s go with something to the effect of “To facilitate binding of HTTP route data to a
strongly typed object to pass to our controller.
Possible changes that would break or violate this responsibility:
- If the protocol changed from HTTP to something else
- If the dependencies of our controller relied too heavily on the ViewModel (Leaky abstraction)
- If the routing engine bound data differently
- If anything other than controllers consumed these ViewModels
Amount of logic – 0.
Amount of intelligent methods – 0.
In short, they are dumb property bags. Dumb property bags make very flexible data binding objects.
To this end, and to facilitate maximum refactorability, a ViewModel should not be shared across controllers or its Actions. – I don’t care how simple your ‘AccountsViewModel’ is.
For any non trivial app, requirements will change – which means your data will change. This change may be focused, or it may be broad, and
your accounts VM will have its properties refactored in some workflows (controller actions) and not others. Do yourself a huge favor – you’re no longer in the “Intro to MVC” course – Split those ViewModels up. If you have trouble finding a name for them, then you need to narrow your logical responsiblity for controller actions.
You’d be surprised at how hard of a time I have getting other developers to do this. They either don’t understand the justification, or counter me with code duplication.
There’s really no legitimate counter argument to this technique. Just do it – and you’ll thank me when you’re refactoring 6 months from now.
Having split those ViewModels, you’ve allowed yourself some flexibility – but now we’re going capitalize on it.
If you haven’t heard of CQRS (or its predecessor – CQS), go Google it. Greg Young and Jimmy Bogard are your guys, and I wouldn’t want to cover their material less efficiently or insightfully than they could. Despite its simplicity – CQRS is a profound concept that can be the foundation for almost any data centric .Net app. Honestly, even in my integration systems (whose role is solely to integrate and not to own a data store), the entire design is a facade over a vendor’s data service – and there it still plenty of opportunity for CQRS to play a role – although I have to make some compromises (some more or less serious than others) in the design since the vendor system did not respect CQRS, so I can only go so far when their most critical services aren’t idempotent. Some of these legacy systems were designed by people who are only able to think in XML, and don’t understand that XML is merely the representation of an object.
A gross generalization of CQRS can be stated simply as separating your read and write models.
Well, that works nicely since we just split off our ViewModels! So now we get to take this one step further.
The usual convention is to suffix these after their entity – so you’ll have things like CustomerViewModel, TransactionViewModel, and so forth.
This doesn’t describe whether the model is being used for a logical read or write (C and Q), so, following CQRS convention, our read models are named as queries,
and the view models that hold data to instruct a mutation of state in the system are named as commands.
So, where before, you had to shoehorn properties and member conversions in your AccountViewModel, you’ll now have things like:
Do you now see how easy it is to refactor these if any one of these workflows change? Plus, even a new developer will have a clear understanding of why these classes exist.
Today, all the web app needs is an Id to pass in to the GetAccountByIdQuery, but – at any point in the future, the web app might also have to pass in the account type, or a new feature may be added where a limited amount of information could be shown for querying an account which is already closed (which could require special transformation of the Id).
If you’ve been working with WebAPI’s for even a short period of time, you should be pretty comfortable with your verbs. At a high level –
GET – Cacheable, retrieval of data, idempotent
POST – Not cacheable or idempotent, used to send data to a server for processing
See how easily these ViewModels map to their respective verbs?
I have some apps that have such a thin controller layer – I actually fully substituted the Command and Query classes as my ViewModels! There is ZERO model binding logic in these controllers, and my CQRS handlers consume them directly. This is descriptive of a system which worked well with that design, not prescriptive of you to do it for your web app. In fact, I might suggest most traditional MVC apps would have to do some amount of mapping from their ViewModel to a more robust query object (which may contain some methods to keep its state consistent), if they are all in on Query and Command handlers.
When I initially started doing this, it almost felt like cheating – but it makes my massive API very easy to reason with and understand.
If you’re working on a larger, more RESTful service, you’ll appreciate these even more, as they map to the other verbs just as well (I’ve used these with PUT, DELETE, and
even the obscure OPTIONS verb).
This should be an easy refactor in your app, and will help set you up for the next level of CQRS – tailoring your services and repositories around a polyglot
design – meaning your read services could defer a read action to an in memory redis cache, or a write service could coordinate multiple actions if you have things
like pub-sub or messaging frameworks, with all sorts of logic in between.