Skip to content
Home » Basics of .NET MAUI – Part 17 – Introduction to MVVM

Basics of .NET MAUI – Part 17 – Introduction to MVVM

Spread the love

As I mentioned in the previous part of the series, we’re going to structure our application using the MVVM pattern. This pattern is often used in XAML-based frameworks like WPF or Xamarin.Forms. It’s also used in .NET MAUI.

The MVVM acronym stands for:

M – Model (used to define the basic domain classes)

V – View (used to display stuff to the user)

VM – ViewModel (used like a code-behind, but completely decoupled, to provide data for the View).

We’re going to talk about all these elements in this series. But let’s take it slowly.

Well, in .NET MAUI (as well as in the other aforementioned frameworks) you don’t have to use the MVVM pattern in your app. You can put all the logic in the code-behind files and it will still work fine. If your app is small, this will do. But if your app is more complex or you think it might grow in complexity in the future, maintenance problems may become an issue. If you put all the code in the code-behind files, the app will be harder to maintain and test. Besides all the views and pages will be tightly coupled with the logic. MVVM allows you to decouple logic from presentation. As MVVM is used in many modern apps, we’re also going to implement it in our app.

We’ll need some folders to implement the pattern. We already have the Views folder (A). Add two more folders to the root of the project and name them Models (B) and ViewModels (C):

Before we create any models and view models, let’s have a look at how MVVM actually works.

How MVVM Works

It’s important to understand how the three components, model, view and view model, relate to one another. Have a look at this diagram:

The dashed arrows demonstrate how data and information flows between the components. They go in only one direction, so the view has direct access to the view model and the view model has direct access to the model. The view doesn’t have direct access to the model, though. What does it mean? It means the view can bind to properties in the view model and the view model can use classes defined in the model.

The model knows nothing about the view model and the view model knows nothing about the view. The view model separates the other two components so that the model and the view can be developed independently.

What about the solid arrows? They indicate that there is some data flow in the opposite direction as well. In case of TwoWay binding or commands (we’re going to talk about commands soon), data flows from the view to the view model and the view model may update the model.

Let’s briefly characterize the three components.

The Three Components of MVVM

The model and the view model are coded in C#. The view is usually coded in XAML, although it can also be coded in C# if this is what you prefer. Anyway, what exactly is each of the three components responsible for?

The model represents the app’s domain model. It contains classes that represent data models and contain business logic.

The view is responsible for the visual representation of data. It defines the structure of a page. It also may contain some logic in the code-behind, although this should be limited to visual behaviors, not the business logic of the app.

The view model implements properties and commands the view can bind to. It also notifies the view if any property changes so that it can update itself. The view model prepares data it takes from the model for the view to consume. It sometimes requires data conversions.

MVVM is pretty flexible. It means you don’t need a model for each and every view model and you don’t need a separate view model for each view. A view model may use multiple models and each model can be used in multiple view models. If all your view needs is a string, you can just define the string in the view model – there’s no need to create a model class for that. Also, if more than one view requires the same data, they can share one view model. Besides, if a view only displays static data that doesn’t require any data binding, there’s no need to create a view model for this view.

In our app, we’re going to be using the .NET Community MVVM Toolkit framework. There are also some other MVVM frameworks. They are used to facilitate our work by, for example, simplifying the implementation of property change notification or handling commands, navigation, dependency injection, etc. But before we start using the toolkit framework, let’s have a look at a basic implementation of the pattern, without any frameworks. This way you will know what is going on behind the scenes and you will be even more grateful for what the framework does for you when we start using it.

Basic MVVM

So, let’s practice the basic, traditional approach on the TestPage. The TestPage is already in the Views folder and it’s going to be our view. Let’s simplify the code in the TestPage.xaml file like so:

Here we have an entry and two labels. We don’t actually need a model, but let’s create a view model that will contain the data our TestPage requires. It definitely requires a string the second label will bind to. In case of the entry there’s two-way binding, so the entry’s Text property will both consume the string from the view model and update it there.

In the ViewModels folder create a new class and name it TestViewModel:

Make the class public:

As you can see, I’m also using a file-scoped namespace, but this isn’t necessary. The view model is going to contain some data and also notify the view when this data changes. We can achieve that by implementing the INotifyPropertyChanged interface:

As you can see, the interface provides an event. Let’s create a method that will invoke the event when a property changes:

To see how it works, let’s create the FavoriteColor property of type string that the entry in the view will bind to. We’ll also create a backing field for it. The getter will just return the value of the backing field. The setter will first check if the value really has changed and if so, it will set the new value and call the OnPropertyChanged method. The method takes the name of the property as an argument:

We can simplify the code slightly by using the CallerMemberName attribute. Then we won’t have to pass the name of the property to the OnPropertyChanged method:

The CallerMemberName attribute can only be applied to parameters with default values, so we set the propertyName parameter to null. As you can see, we can now call the OnPropertyChanged method without the name of the property. This may be useful if there are many properties in the class.

The OnPropertyChange method will notify the view of the change and the entry’s text will be automatically updated. And vice versa, if we set the Text property in the entry, it will be automatically updated in the view model due to its two-way binding mode.

But first, we must set the binding context of the view to the view model and bind the entry’s Text property to the FavoriteColor property defined in the view model:

As you can see, I also bound the second label’s Text property to FavoriteColor. If you now start entering text into the entry, the FavoriteColor property in the view model will be updated to the new value. The label will display the new value because it’s bound to it. So, here the data goes from the entry to the view model and then from the view model to the label:

Sometimes one property in the view model depends on another property. Then, when one of them changes, the other should change too. Let’s modify our view model:

Here we have the LetterCount property that simply returns the length of the string that we enter in the entry. This property will change whenever the FavoriteColor property changes. We can achieve this by calling the OnPropertyChange method as many times as there are properties that should be affected. In our case it means twice. We don’t have to pass the name of the property on which the method is called, but we have to provide the names of other properties, which is why we pass LetterCount to it.

Now let’s add another label in the view and bind it to the newly created property:

If we now enter some text in the entry, the label will display its length:

Let’s say we want to add a button to our view, which, when pressed, will set the FavoriteColor property to a fixed value, like for instance “red”. The problem is, we can’t use events anymore. Events work in the code-behind and we now put the logic in the view model. So, there must be another way to do it. And there is – commands.

Commands

Let’s start by creating a public property of type ICommand in the view model. This is the property we can bind to. Then, in the constructor, let’s create a new Command instance and pass to it the method that we want to be invoked when the button is pressed. We don’t have the method yet, so let’s create it. The method is named UseFixedColor and it just sets the FavoriteColor property to a fixed value. Here’s the code:

Now we have to create the button in the view and bind its Command property to the property we just created in the view model:

That’s it. If you now run the app and press the button, the FavoriteColor property will change to “red”. This change will be immediately reflected in all controls in the view that bind to FavoriteColor:

We can also use the command with a parameter. To this end we use the CommandParameter property. Let’s add two more buttons that will pass a different color name to the command as an argument:

As now the method takes a parameter, we have to use the generic version of Command in the view model with the type corresponding to the type of the parameter. Let’s modify the code like this:

Now you can press the buttons to change the FavoriteColor property:

It works fine because we set the binding context to TestViewModel. Let’s have a closer look at the binding context.

Binding Context

We set the binding context in XAML, but we often do it in the code-behind using dependency injection. In the TestPage.xaml.cs file we’ll set the binding context in the constructor:

We’re using here an instance of TestViewModel through dependency injection. We need to register the page and the view model with the dependency service in the MauiProgram.cs file:

We’re using the AddSingleton method here, which means the same instance will be used throughout the application.  If we used AddTransient instead, a new instance of TestPage and TestViewModel would be delivered whenever requested. It’s important to register the classes for dependency injection before the Build method on builder is called.

Finally, remove the part of the markup in the TestPage.xaml file where the binding context was set. I mean the following part:

We don’t need it anymore because now the binding context is set in the code-behind.

The app works as before, but we have a problem with Intellisense in the XAML file. It looks like the properties defined in the view model are not listed in it. We can fix it by setting the x:DataType property on the page to the view model class:

Now Intellisense works as before.

This is it as far as the basics of MVVM are concerned. We could implement our Slugrace application in the same way – just add view models and models to the already existing views. However, if you look at the TestViewModel class, you will see that we need quite a lot of code to implement a single property. We also have to implement the INotifyPropertyChanged interface for each view model.

We could simplify it a bit by creating a base view model where the interface would be implemented and then deriving the other view models from this base class. But there is even a better solution. We can use a framework that will do some of the tedious work for us. I already mentioned the .NET Community MVVM Toolkit framework that we are going to use. It relies on source generators, so it will generate optimized C# code with all the required functionality for you. We’re going to implement it in the next part of the series. We’ll first use it for the TestPage so that you can see the difference and then we’ll start implementing it for the actual application.


Spread the love
Tags:

Leave a Reply