Besides what is already mentioned (separation of a concern,decoupling,etc), a separate ViewModel stops abstraction leaking that may come along with the DB Model. This is true especially if you are using EF with navigation properties enabled.
Let's say you have cars and wheels. You are showing the cars in the view.
Case 1 (No Separate ViewModel For cars): In the razor view, it is very easy to have something like below:
public class CarModelFromDB
{
public string CarName{get;set;}
//More properties
public IEnumerable<Wheel> Wheel{get;set;}
}
@model IEnumerable<CarModelFromDB>
@foreach(var car in Model)
{
//some View Code
@foreach(var wheel in car.Wheels.Where(x=>x.IsRound=true))
{
<span>wheel.Color</span>
// some View Code
}
}
Now, your logic for getting wheels with the cars has leaked into the view and also has enabled select N+1 situation. I don't think there is any easy ways of testing it either.
Case 2 (With ViewModel For cars): In this scenario, you can restrict the view by sending only things that it needs. It may look like below:
public class CarViewModel
{
public string CarName{get;set;}
//More properties
public IEnumerable<string> WheelColors{get;set;}
}
@model IEnumerable<CarViewModel>
@foreach(var car in Model)
{
//some View Code
@foreach(var wheelColor in WheelColor)
{
<span>wheelColor</span>
// some View Code
}
}
Now, your view code is very restricted in terms of what it can do, and it won't send any rogue queries to the database. Your controller is truly in control of what view is getting. You can push the wheel logic in there or ideally in some service method that gets called from the action method. Also, you can do proper testing on the action method and feel confident in your system. I hope this helps.
Update
Case 3 (dynamic ViewModel):
If you are comfortable with dynamic types, you can avoid all the casting and mapping. As long as your view gets the properties it needs, it will be happy. It does not matter where those come from. So the code will be:
public class CarViewModel
{
public string CarName{get;set;}
//More properties
public IEnumerable<string> WheelColors{get;set;}
}
// pass the List<CarViewModel> to the view
@model dynamic
@foreach(var car in Model)
{
//some View Code
@foreach(var wheelColor in WheelColor)
{
<span>wheelColor</span>
// some View Code
}
}
The potential downside/extra work is to make sure you have tests for the existence of those properties on the model.
Again like it is mentioned earlier, this should not be considered as the one size fit all type of solution. These are some options and used only if they make sense.