- Garcia's Guide to the Galaxy
- Posts
- Blazor With MVVM
MVVM is a development pattern that has been around for a while now. It was designed to facilitate the development of WPF applications for Windows and is still used for the likes of Maui apps. I reckon it has a place in Blazor apps as a neat way to separate view from logic, and as a sort of volatile state management for when you don’t want state to persist between pages.
I won’t go into detail of what MVVM is or what it can offer, that’s already been explained better than I could hope to. This article assumes you know what you’re doing with your IDE and .NET in general, it also helps to have some familiarity with Blazor.
In summary, MVVM is short for Model, View, ViewModel. From a Blazor application perspective:
.razor
fileSome key benefits of MVVM with regards to a Blazor application:
INotifyPropertyChanged
and INotifyCollectionChanged
interfaces streamline component refreshesFor this example I will be using the default Blazor WASM template provided with .NET 6.0. Later versions of .NET should be able to follow the same principles outlined here. I will also be using the following packages:
This demonstration won’t do anything particularly fancy but should serve as a good introduction to how MVVM can work effectively in a Blazor app.
Start by creating a Blazor WASM project:
dotnet new blazorwasm -n BlazorWithMvvm
Go into the new directory and add the three packages mentioned previously:
dotnet add package MvvmBlazor
dotnet add package CommunityToolkit.Mvvm
dotnet add package MudBlazor
MvvmBlazor and MudBlazor both require additional setup.
MvvmBlazor’s setup documentation can be found here.
In Program.cs
, register the MvvmBlazor service:
builder.Services.AddMvvm();
Add the relevant MvvmBlazor namespaces to _Imports.razor
:
@using MvvmBlazor
@using MvvmBlazor.Components
MudBlazor’s setup documentation can be found here.
In Program.cs
, register the MudBlazor service:
builder.Services.AddMudServices();
Add the MudBlazor namespace to _Imports.razor
:
@using MudBlazor
Add font and stylesheet references inside the <head>
tag in index.html
:
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
Add a script reference alongside the existing Blazor script reference near the bottom of <body>
in index.html
:
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
Add the components you need to MainLayout.razor
; only MudThemeProvider
is required. This can go underneath the @inherits...
line.
<MudThemeProvider/>
<MudDialogProvider/>
<MudSnackbarProvider/>
MvvmBlazor relies on code generators to do a lot of the heavy lifting so VMs are pretty easy to put together. Here’s an example (using
statements and namespace removed for brevity):
public partial class MyButtonViewModel : ViewModelBase
{
[Notify]
private string _myMessage = default!;
[Parameter]
public int CurrentCount { get; set; }
private bool _canSetCount => CurrentCount > 3;
[RelayCommand(CanExecute = nameof(_canSetCount))]
private void SetCount()
{
MyMessage = "S'cool!";
}
}
Here’s what you need to know about this ViewModel:
partial
for source generators to workViewModelBase
[Notify]
attribute is applied to a member variable, source generators will handle the INotify
goodies and expose it as a publicly accessible property[Parameter]
property will tie up with the component itself to pass parameters straight through to the ViewModelSetCount
has a RelayCommand
attribute from MVVM Toolkit which uses source generators to build an IRelayCommand
object around the SetCount()
method called SetCountCommand
. It also references the _canSetCount
variable to determine whether or not the command can be run with CanExecute
. The method updates the source generated property MyMessage
mentioned before, triggering notification updates automaticallyAdd the ViewModel to the services registration in Program.cs
. In this case I’m using AddTransient
so that it is disposed of when the component is destroyed; navigating away and back again will reset it.
builder.Services.AddTransient<MyButtonViewModel>();
The following code demonstrates how to take advantage of the ViewModel for a component:
@inherits MvvmComponentBase<MyButtonViewModel>
<MudButton Command="@BindingContext.SetCountCommand" Disabled="@(!BindingContext.SetCountCommand.CanExecute(null))">Is it cool?</MudButton>
<MudText>
@Bind(x => x.MyMessage)
</MudText>
@code {
[Parameter]
public int CurrentCount { get; set; }
}
Here’s what you need to know about this Component:
MvvmComponentBase<ComponentViewModel>
BindingContext
propertyMudButton
, the Command
property is not sufficient to disable it when CanExecute()
is false
so I’ve set it up manually here@Bind(x => x.BindingPropertyName)
@code
blockTo demonstrate how this component works I plugged it in to the Counter.razor
page by adding the following just above the @code
block:
<MyButton CurrentCount="currentCount" />
currentCount
is a property on that component already and we’re passing it down. The idea is that the user can increment that count, but we’ll only see our button enable when the count is higher than 3 (see the predicate in the ViewModel).
Run the app, go to the Counter
page and the button should be disabled. Increment the counter a few times and the button should be enabled, clicking it should show a wee message.
A sentient being
© Copyright 2023
Phil Watson
Last updated
Thursday, 09/Nov/2023 14:20 AEST