Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there two kinds of ViewModels in MVVM Light?

Many people advice WPF MVVM developers to not expose Model instances from the ViewModel to the View. To display the information from a collection of Model instances, wrap all individual items into ViewModel instances and expose a collection of ViewModels to the View.

However, using MVVM Light it seems to me there are two kinds of ViewModels then:

  • ViewModels that have a one-to-one relationship with a view (e.g. MainWindowViewModel or CustomerEditorViewModel). Assumed there is only one MainWindow, there will only be one MainWindowViewModel.
  • ViewModels that have a one-to-one relationship with a model instance (e.g. CustomerViewModel) and are some kind of "mech suit" for the model instance, providing additional functionality like calculated properties (e.g. Duration from StartTime and EndTime). A usual company has many Customers so there will be many CustomerViewModels.

How to wrap Model instances then?

One idea could be to create wrapper classes deriving from ViewModelBase but not registering and instantiating those with the ViewModelLocator. I do not think it is a good idea to have two seperate things both called ViewModel.

Another idea could be to use a new base class for the second type of ViewModels, maybe called ModelInfo. In a single instance of MainViewModel there would be a collection of CustomerInfo instances, providing the additional functionality for the Customer model data.

I would tend to the latter but as this seems to be a pretty general case using MVVM Light, I am sure there must be a common solution to this issue.


Update

I have found an article by Laurent Bugnion, the author of MVVM Light. In his article from the year 2012 Bugnion uses two different approaches of initializing ViewModels:

  1. MainViewModel is registered with the ViewModelLocator and has no constructor arguments. Thus it is suitable for Dependency Injection and can be instantiated through the ServiceLocator.
  2. FriendViewModel is not registered with the ViewModelLocator and its constructor takes a Model instance as an argument. It can not be instantiated with the ServiceLocator but only by directly calling the constructor and passing the model instance.

This pretty much aligns with the differentiation mentioned in my original question, and with the first idea how to wrap model instances then.

like image 840
Andre Avatar asked Dec 24 '22 07:12

Andre


2 Answers

Here is my personal 7 years experience with MVVM. I say personal because you will find a lot of contradiction on this topic, especially when you refer to the official MSDN definition

"it seems to me there are two kinds of ViewModels"

Absolutely not, but it is a common misunderstanding. The first role of a ViewModel is to be a testable and maintainable representation of your View. The correct abstraction is then a one-one relationship with your view, not your Model.

"Many people advice WPF MVVM developers to not expose Model instances from the ViewModel to the View."

Yes, it is still true, because if your Model is correctly implemented from an OOP perspective, you put the responsibility and business logic inside it. Thus your ViewModel is just INotifiedPropertyChanged, and wrap the information you want to expose from your Model, and the command you want to call in your Model.

The classic explanation (including MSDN's one) is incomplete, because it assumes you can align one View with one ViewModel with one Model. Thus your problem is often overlooked, because in a very CRUD system, you can easily have this one-to-one-to-one relationship. Another way to have this relationship is to use CQRS, because it allows you to generate a Model without any logic and directly aligned on your view for query.

But as you already experienced (if you don't work on a CRUD or CQRS system), in most classic implementation, your ViewModel represents your View, but needs several Models to work well (which is perfectly natural). You have to put as much business logic as you can in these Models. And to manage the flow between calls to your different Models, you add another abstraction, which could be called a Service. This Service should represent a business case, which need to use several Models.

You can think about it in this way: your BusinessService should work independently of the infrastructure. It should not care if it's called from a ViewModel, or from a Controller in a Web App. It just manages the flow between some Models to meet a business need.

Let me try recap:

  • A ViewModel should not have business logic (but you already get that)

  • A ViewModel is an abstraction of your View (and not of your Model, even if in some cases you can obtain a one-to-one-to-one relationship)

  • If a ViewModel needs several Models to meet a Business need, use a Service to manage the flow between your different models (it becomes by definition a BusinessService)

Here is a blog post with code example to clarify my point: http://ouarzy.azurewebsites.net/2016/04/14/clarifying-mvvm-with-ddd/

Hope it helps.

like image 104
Ouarzy Avatar answered Dec 29 '22 00:12

Ouarzy


From what I can gather, as above a ViewModel has a one-to-one relationship with a View.

In practice, I find I use ViewModels without their view - usually when displaying a collection of them using ItemsSource. If it needs it, I'll have an ItemsSource of views - but if it's something very simple I'll just use the "parent" view. Not sure if this is best practice, but there comes a point where you need to bind to something other than the Model's plain properties and need View related properties to bind to, but a dedicated view doesn't make sense (in a DataGrid row for example, a Row might be a ViewModel, but has no View).

Here's an example of what I'm talking about. A random example app has three "Main" ViewModels and Views:

enter image description here

In more detail, this is how they are structured. Note there's no "EmployeeView" for the "EmployeeViewModel". Similarly, these "Screen" view models don't have any models at all, they're not really associated with a data object and a model isn't needed:

enter image description here

If we were only binding to straight up data items (and had no other View related logic, like say a "Hidden" property to show/hide our employees) then we wouldn't need an EmployeeViewModel, we could just bind our EmployeeTrackerScreenViewModel to many EmployeeModels.

like image 29
Joe Avatar answered Dec 28 '22 22:12

Joe