Categories
.NET MAUI Deep Dive Desktop Developer Mobile MVVM Xamarin

MVVM – Made Easy with Microsoft MVVM Toolkit – Part 2

An in-depth look into the options available in this toolkit for auto-generating the Properties and Commands to make MVVM programming a delight.

This article is the second part of this MVVM – Made Easy series. The first part of this article, covering the basics, is linked here.

As seen in the first part, INotifyPropertyChanged (INPC) is the interface that defines the event which raises the notification whenever the value of the property gets changed.

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

In fact, there’s yet another interface named INotifyPropertyChanging, in the same System.ComponentModel namespace, that raises a similar notification. But the difference is, as the name implies, that this raises the notification prior to the value of the property getting changed.

public interface INotifyPropertyChanging
{
    event PropertyChangingEventHandler PropertyChanging;
}

Even the base class implementation in this toolkit, ObservableObject, implements both of these interfaces.

So, the order in which an ideal property implementation would be as follows. Here #3 and #6 are the notifications and we’ll see #2 and #5 in detail.

  1. Check whether the value of the property has really changed
  2. Execute the prior to change user code, if any
  3. Raise the OnPropertyChanging notification
  4. Update the value of the backing field
  5. Execute the post-change user code, if any
  6. Raise the OnPropertyChanged notification
  7. Other notifications, if any

Most of the time, we’ve to execute some sort of business logic as soon as the value of the property gets updating/updated. For example, refreshing the cart value whenever a quantity of an item gets modified. For scenarios like this, there needs a provision to hook in the user-defined code at the appropriate place.

And partial methods come to the rescue. Methods are generated without any access specifier, will not return anything (void), and are marked as partial. So that if not utilized, will be removed by the compiler without causing any behavioral change to the program.

As highlighted in the below code, two partial methods have been generated for adding program logic before- and after- the property value gets changed and it follows a convention: On<PropertyName>Changing and On<PropertyName>Changed.

The source generated by this MVVM Toolkit is even more optimized as it handles the resources in an efficient way.

Update: Starting with v8.0.0-preview4, the following attributes have been renamed.

OldNew
[ICommand][RelayCommand]
[AlsoNotifyChangeFor][NotifyPropertyChangedFor]
[AlsoNotifyCanExecuteFor][NotifyCanExecuteChangedFor]
What’s new in the latest preview release (v8.0.0-preview4)
// User-defined Code
namespace SampleApp.ViewModels;

public partial class UserViewModel : ObservableObject
{
    [ObservableProperty]
    [AlsoNotifyChangeFor(nameof(FullName))]
    [AlsoNotifyCanExecuteFor(nameof(SaveCommand))]
    private string firstName = string.Empty;

    [ObservableProperty]
    [AlsoNotifyChangeFor(nameof(FullName))]
    [AlsoNotifyCanExecuteFor(nameof(SaveCommand))]
    private string lastName = string.Empty;

    public string FullName => $"{FirstName} {LastName}";

    [ICommand(CanExecute = nameof(CanSaveExecute), IncludeCancelCommand = true)]
    private async Task SaveAsync(CancellationToken cancelToken)
    {
        // Code to save the user details
        await Task.FromResult(0);
    }

    private bool CanSaveExecute()
        => !string.IsNullOrWhiteSpace(FirstName) && !string.IsNullOrWhiteSpace(LastName);
}
// Generated Code
namespace SampleApp.ViewModels;

partial class UserViewModel
{
    // Shown only for FirstName property for brevity
    public string FirstName
    {
        get => firstName;
        set
        {
            if (!EqualityComparer<string>.Default.Equals(firstName, value))
            {
                OnFirstNameChanging(value);
                OnPropertyChanging();
                firstName = value;
                OnFirstNameChanged(value);
                OnPropertyChanged();
                // And raise notifications for all other subscriptions (like FullName)
                OnPropertyChanged(nameof(FullName));
                SaveCommand.NotifyCanExecuteChanged();
            }
        }
    }

    // Auto generated partial methods
    // Pre
    partial void OnFirstNameChanging(string value);
    // Post
    partial void OnFirstNameChanged(string value);
}

The DateCalculator MVVM sample app, available on GitHub, made use of this post-change partial method to arrive at the new date in the Add/Subtract mode of operation (change in the value of Year/Month/Day).

Moving on to Commands, the following are the supported features.

  1. Generic – Pass data with type safety
  2. CanExecute – Option to enable/disable the command’s availability
  3. Concurrent Execution – Option to execute in parallel
  4. Cancellation – Option to cancel a running command

The commands can have a parameter, passed via the CommandParameter property, this toolkit generates generic type-safe commands based on the parameter type defined in the method signature.

In the sample, CanSaveExecute is the method, that returns a boolean value, and validates whether the command can be executed in the current context. True means, it can be executed.

Similar to how AlsoNotifyChangeFor attribute raises notification for the change in property value, the AlsoNotifyCanExecuteFor attribute, decorated on a field, invokes the command’s NotifyCanExecuteChanged method causing it to revalidate whether a command can be executed in the current context or not.

Here, any change in the value of the FirstName or LastName property will invoke this method to check whether the command can be executed. If either of the property is invalid, the Save operation cannot be invoked as this will return False.

In certain cases, the same command can be executed in parallel, this is controlled by the AllowConcurrentExecutions boolean property and is applicable only for Async commands. If set to True, the command can be invoked multiple times at once.

Cancellation is also for Async commands, without this toolkit, some synchronization logic needs to be handled such that the Cancel command is active only during the execution of its associated command.

But this toolkit does everything with a single boolean property, IncludeCancelCommand. When set to True, it auto-generates a Cancel command with all the necessary boilerplate. The only requirement is that the method, decorated with the ICommand attribute, needs to have a CancellationToken parameter that is necessary to cancel the async Task.

Undoubtedly, this toolkit is powerful as it generates all the necessary boilerplate code resulting in improved developer productivity. But I see an issue during code refactoring, whenever a field/method, marked with these attributes, is renamed, all its existing generated references become invalid as they’re not tracked as one unit.

Also in this month’s (May 2022) .NET MAUI community standup, David Ortinau, Principal PM of the .NET MAUI team, raised another query (video linked here) regarding how the Go To Definition feature of navigating to the corresponding Property/Command would work. Unfortunately, it’s not working from XAML as of now. There’re certain issues that need to be addressed, which could be an enhancement in Visual Studio, to make it feature complete.

In this article, we’ve seen in detail the options available in this toolkit for Property and Command. In the next article, we’ll take a look at the Validations, powered by the attributes from the System.ComponentModel.DataAnnotations namespace.

Happy coding. Stay connected as we continue to learn and share the experiences from this exciting journey of being a .NET developer.

3 replies on “MVVM – Made Easy with Microsoft MVVM Toolkit – Part 2”

Wonderful! Finally I discovered more from this article than from YT and toolkit documentation or samples on Github! You’re awesome!

Liked by 1 person

Comments are closed.