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.
- Check whether the value of the property has really changed
- Execute the prior to change user code, if any
- Raise the
OnPropertyChanging
notification - Update the value of the backing field
- Execute the post-change user code, if any
- Raise the
OnPropertyChanged
notification - 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.
Old | New |
---|---|
[ICommand] | [RelayCommand] |
[AlsoNotifyChangeFor] | [NotifyPropertyChangedFor] |
[AlsoNotifyCanExecuteFor] | [NotifyCanExecuteChangedFor] |
// 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.
- Generic – Pass data with type safety
- CanExecute – Option to enable/disable the command’s availability
- Concurrent Execution – Option to execute in parallel
- 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”
[…] The second article in this MVVM – Made Easy series is now available to […]
LikeLike
Wonderful! Finally I discovered more from this article than from YT and toolkit documentation or samples on Github! You’re awesome!
LikeLiked by 1 person
That’s the intended objective. Share with your inner circle and stay connected.
LikeLike