While looking at this answer to the question Why do we use ViewModels?, I came across this section:
"A view should not contain any non-presentational logic" and "You
should not trust the View" (because a View could be user-provided). By
providing a Model object (potentially still connected to an active
DatabaseContext) a view can make malicious changes to your database.
What exactly does this refer to? If I have UserId
and Password
in my Model and my ViewModel, where does the security come in? Some kind of check in the controller? What do we check?
How do we determine we can trust the data from the view? Is this handled by the antiforgery token?
I believe the answer is referring to the over-post problem. When you utilize an entity class directly with your view, and particularly if you save that posted entity directly to your database, a malicious user could modify the form to post fields they should not be able to modify.
For example, let's say you had a form that allows a user to edit widgets. Let's also say that you have row-level permissions, such that a user can only edit widgets that belong to them. So, Joe, our fictitious malicious user, edits a widget he's allowed to edit with id 123. But, he decides he wants to mess with Jane's widget, so he adds a field to the form named Id
and gives it the value of Jane's widget id. When Joe then posts the widget form, Jane's widget is updated instead.
A view model is not solely for solving this problem, but it does basically negate the issue because, inherently, you cannot directly save the view model to the database. Instead, you must map the view model's values onto the entity, before saving the entity to the database. As a result, you then explicitly control what does and does not get mapped, so in the same example above, Joe changing the id ends up having no effect because you're not mapping that onto the entity.
In truth, the real problem here is in directly saving anything posted by a user directly to the database. You could actually still feed your entity class to the view as the "model", but then not save the posted instance. Instead, you then create a new instance of the entity or pull an instance from the database fresh, and simply map the values from the posted instance over to that. Again, you wouldn't map a property like Id
, so again Joe is foiled. In other words, it's not the view model that solves the problem, it's the never trusting a user enough to directly save anything created via a POST that solves the issue.
Microsoft gives another alternative solution in the form of the Bind
attribute, which essentially allows you to include/exclude certain properties on an entity class from the modelbinding process (ignoring any posted values, in other words). So, for example, you could potentially solve the issue above by decorating the param on your action with [Bind(Exclude = "Id")]
, which would then discard any posted value for Id
. However, Bind
is horrible for a number of reasons, and you should not actually use it. Always use a view model instead, or simply don't ever directly save the entity instance created by the modelbinder.