https://github.com/gragra33/blazing.mvvm
🔥 Blazing.Mvvm - Full MVVM support for Blazor with CommunityToolkit.Mvvm integration. Supports all hosting models (Server, WASM, SSR, Auto, Hybrid, MAUI). Features strongly-typed navigation, automatic ViewModel registration, parameter resolution, validation support, and comprehensive lifecycle management. Includes samples and full documentation.
https://github.com/gragra33/blazing.mvvm
blazor blazor-server blazor-webassembly blazorhybrid blazorssr mvvm
Last synced: 9 months ago
JSON representation
🔥 Blazing.Mvvm - Full MVVM support for Blazor with CommunityToolkit.Mvvm integration. Supports all hosting models (Server, WASM, SSR, Auto, Hybrid, MAUI). Features strongly-typed navigation, automatic ViewModel registration, parameter resolution, validation support, and comprehensive lifecycle management. Includes samples and full documentation.
- Host: GitHub
- URL: https://github.com/gragra33/blazing.mvvm
- Owner: gragra33
- License: mit
- Created: 2023-05-10T06:14:23.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2025-09-04T23:24:52.000Z (10 months ago)
- Last Synced: 2025-09-19T08:33:29.434Z (9 months ago)
- Topics: blazor, blazor-server, blazor-webassembly, blazorhybrid, blazorssr, mvvm
- Language: C#
- Homepage:
- Size: 481 KB
- Stars: 77
- Watchers: 5
- Forks: 15
- Open Issues: 2
-
Metadata Files:
- Readme: readme.md
- License: LICENSE
Awesome Lists containing this project
README
# Blazor Extension for the MVVM CommunityToolkit
This project expands upon the [blazor-mvvm](https://github.com/IntelliTect-Samples/blazor-mvvm) repository by [Kelly Adams](https://github.com/adamskt), implementing full MVVM support via the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/). Enhancements include preventing cross-thread exceptions, adding extra base class types, MVVM-style navigation, and converting the project into a usable library.
## Table of Contents
- [Blazor Extension for the MVVM CommunityToolkit](#blazor-extension-for-the-mvvm-communitytoolkit)
- [Table of Contents](#table-of-contents)
- [Quick Start](#quick-start)
- [Installation](#installation)
- [.NET CLI](#net-cli)
- [NuGet Package Manager](#nuget-package-manager)
- [Configuration](#configuration)
- [Registering ViewModels in a Different Assembly](#registering-viewmodels-in-a-different-assembly)
- [Usage](#usage)
- [Create a `ViewModel` inheriting the `ViewModelBase` class](#create-a-viewmodel-inheriting-the-viewmodelbase-class)
- [Create your Page inheriting the `MvvmComponentBase` component](#create-your-page-inheriting-the-mvvmcomponentbasetviewmodel-component)
- [Give a ⭐](#give-a-)
- [Documentation](#documentation)
- [View Model](#view-model)
- [Lifecycle Methods](#lifecycle-methods)
- [Service Registration](#service-registration)
- [Registering ViewModels with Interfaces or Abstract Classes](#registering-viewmodels-with-interfaces-or-abstract-classes)
- [Registering Keyed ViewModels](#registering-keyed-viewmodels)
- [Parameter Resolution](#parameter-resolution)
- [MVVM Navigation](#mvvm-navigation)
- [Navigate by abstraction](#navigate-by-abstraction)
- [MVVM Validation](#mvvm-validation)
- [History](#history)
- [V2.0.0](#v200)
## Quick Start
### Installation
Add the [Blazing.Mvvm](https://www.nuget.org/packages/Blazing.Mvvm) NuGet package to your project.
Install the package via .NET CLI or the NuGet Package Manager.
#### .NET CLI
```bash
dotnet add package Blazing.Mvvm
```
#### NuGet Package Manager
```powershell
Install-Package Blazing.Mvvm
```
### Configuration
Configure the library in your `Program.cs` file. The `AddMvvm` method will add the required services for the library and automatically register ViewModels that inherit from the `ViewModelBase`, `RecipientViewModelBase`, or `ValidatorViewModelBase` class in the calling assembly.
```csharp
using Blazing.Mvvm;
builder.Services.AddMvvm(options =>
{
options.HostingModelType = BlazorHostingModelType.WebApp;
});
```
If you are using a different hosting model, set the `HostingModelType` property to the appropriate value. The available options are:
- `BlazorHostingModelType.Hybrid`
- `BlazorHostingModelType.Server`
- `BlazorHostingModelType.WebApp`
- `BlazorHostingModelType.WebAssembly`
- `BlazorHostingModelType.HybridMaui`
#### Registering ViewModels in a Different Assembly
If the ViewModels are in a different assembly, configure the library to scan that assembly for the ViewModels.
```csharp
using Blazing.Mvvm;
builder.Services.AddMvvm(options =>
{
options.RegisterViewModelsFromAssemblyContaining();
});
// OR
var vmAssembly = typeof(MyViewModel).Assembly;
builder.Services.AddMvvm(options =>
{
options.RegisterViewModelsFromAssembly(vmAssembly);
});
```
### Usage
#### Create a `ViewModel` inheriting the `ViewModelBase` class
```csharp
public partial class FetchDataViewModel : ViewModelBase
{
private static readonly string[] Summaries = [
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
[ObservableProperty]
private ObservableCollection _weatherForecasts = new();
public string Title => "Weather forecast";
public override void OnInitialized()
=> WeatherForecasts = new ObservableCollection(Get());
private IEnumerable Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
});
}
}
```
#### Create your Page inheriting the `MvvmComponentBase` component
> ***NOTE:*** If working with repositories, database services, etc, that require a scope, then use `MvvmOwningComponentBase` instead.
```xml
@page "/fetchdata"
@inherits MvvmOwningComponentBase
@ViewModel.Title
@ViewModel.Title
@if (!ViewModel.WeatherForecasts.Any())
{
Loading...
}
else
{
Date
Temp. (C)
Temp. (F)
Summary
@foreach (var forecast in ViewModel.WeatherForecasts)
{
@forecast.Date.ToShortDateString()
@forecast.TemperatureC
@forecast.TemperatureF
@forecast.Summary
}
}
```
## Give a ⭐
If you like or are using this project to learn or start your solution, please give it a star. Thanks!
Also, if you find this library useful, and you're feeling really generous, then please consider [buying me a coffee ☕](https://bmc.link/gragra33).
## Documentation
The Library supports the following hosting models:
- Blazor Server App
- Blazor WebAssembly App (WASM)
- Blazor Web App (.NET 8.0+)
- Blazor Hybrid - Wpf, WinForms, MAUI, and Avalonia (Windows only)
The library package includes:
- `MvvmComponentBase`, `MvvmOwningComponentBase` (Scoped service support), & `MvvmLayoutComponentBase` for quick and easy wiring up ViewModels.
- `ViewModelBase`, `RecipientViewModelBase`, & `ValidatorViewModelBase` wrappers for the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/).
- `MvvmNavigationManager` class, `MvvmNavLink`, and `MvvmKeyNavLink` component for MVVM-style navigation, no more hard-coded paths.
- Sample applications for getting started quickly with all hosting models.
There are two additional sample projects in separate GitHub repositories:
1. [Blazor MVVM Sample](https://github.com/gragra33/MvvmSampleBlazor) - takes Microsoft's [Xamarin Sample](https://github.com/CommunityToolkit/MVVM-Samples) project for the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/) and converts it to: Blazor Wasm & Blazor Hybrid for Wpf & Avalonia. Minimal changes were made.
2. [Dynamic Parent and Child](https://github.com/gragra33/Blazing.Mvvm.ParentChildSample) - demonstrates loose coupling of a parent component/page and an unknown number of child components using [Messenger](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger) for interactivity.
### View Model
The library offers several base classes that extend the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/) base classes:
- `ViewModelBase`: Inherits from the [`ObservableObject`](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observableobject) class.
- `RecipientViewModelBase`: Inherits from the [`ObservableRecipient`](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observablerecipient) class.
- `ValidatorViewModelBase`: Inherits from the [`ObservableValidator`](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observablevalidator) class and supports the `EditForm` component.
#### Lifecycle Methods
The `ViewModelBase`, `RecipientViewModelBase`, and `ValidatorViewModelBase` classes support the `ComponentBase` lifecycle methods, which are invoked when the corresponding `ComponentBase` method is called:
- `OnAfterRender`
- `OnAfterRenderAsync`
- `OnInitialized`
- `OnInitializedAsync`
- `OnParametersSet`
- `OnParametersSetAsync`
- `ShouldRender`
#### Service Registration
ViewModels are registered as `Transient` services by default. If you need to register a ViewModel with a different service lifetime (Scoped, Singleton, Transient), use the `ViewModelDefinition` attribute:
```csharp
[ViewModelDefinition(Lifetime = ServiceLifetime.Scoped)]
public partial class FetchDataViewModel : ViewModelBase
{
// ViewModel code
}
```
In the `View` component, inherit the `MvvmComponentBase` type and set the generic argument to the `ViewModel`:
```xml
@page "/fetchdata"
@inherits MvvmComponentBase
```
##### Registering ViewModels with Interfaces or Abstract Classes
To register the `ViewModel` with a specific interface or abstract class, use the `ViewModelDefinition` generic attribute:
```csharp
[ViewModelDefinition]
public partial class FetchDataViewModel : ViewModelBase, IFetchDataViewModel
{
// ViewModel code
}
```
In the `View` component, inherit the `MvvmComponentBase` type and set the generic argument to the interface or abstract class:
```xml
@page "/fetchdata"
@inherits MvvmComponentBase
```
##### Registering Keyed ViewModels
To register the `ViewModel` as a keyed service, use the `ViewModelDefinition` attribute (this also applies to generic variant) and set the `Key` property:
```csharp
[ViewModelDefinition(Key = "FetchDataViewModel")]
public partial class FetchDataViewModel : ViewModelBase
{
// ViewModel code
}
```
In the `View` component, use the `ViewModelKey` attribute to specify the key of the `ViewModel`:
```xml
@page "/fetchdata"
@attribute [ViewModelKey("FetchDataViewModel")]
@inherits MvvmComponentBase
```
#### Parameter Resolution
The library supports passing parameter values to the `ViewModel` which are defined in the `View`.
This feature is opt-in. To enable it, set the `ParameterResolutionMode` property to `ViewAndViewModel` in the `AddMvvm` method. This will resolve parameters in both the `View` component and the `ViewModel`.
```csharp
builder.Services.AddMvvm(options =>
{
options.ParameterResolutionMode = ParameterResolutionMode.ViewAndViewModel;
});
```
To resolve parameters in the `ViewModel` only, set the `ParameterResolutionMode` property value to `ViewModel`.
Properties in the `ViewModel` that should be set must be marked with the `ViewParameter` attribute.
```csharp
public partial class SampleViewModel : ViewModelBase
{
[ObservableProperty]
[property: ViewParameter]
private string _title;
[ViewParameter]
public int Count { get; set; }
[ViewParameter("Content")]
private string Body { get; set; }
}
```
In the `View` component, the parameters should be defined as properties with the `Parameter` attribute:
```xml
@inherits MvvmComponentBase
@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public int Count { get; set; }
[Parameter]
public string Content { get; set; }
}
```
### MVVM Navigation
No more magic strings! Strongly-typed navigation is now possible. If the page URI changes, you no longer need to search through your source code to make updates. It is auto-magically resolved at runtime for you!
When the `MvvmNavigationManager` is initialized by the IOC container as a Singleton, the class examines all assemblies and internally caches all ViewModels (classes and interfaces) along with their associated pages.
When navigation is required, a quick lookup is performed, and the Blazor `NavigationManager` is used to navigate to the correct page. Any relative URI or query string passed via the `NavigateTo` method call is also included.
> **Note:** The `MvvmNavigationManager` class is not a complete replacement for the Blazor `NavigationManager` class; it only adds support for MVVM.
**Modify the `NavMenu.razor` to use `MvvmNavLink`:**
```xml
```
> The `MvvmNavLink` component is based on the Blazor `NavLink` component and includes additional `TViewModel` and `RelativeUri` properties. Internally, it uses the `MvvmNavigationManager` for navigation.
**Navigate by ViewModel using the `MvvmNavigationManager` from code:**
Inject the `MvvmNavigationManager` class into your page or ViewModel, then use the `NavigateTo` method:
```csharp
mvvmNavigationManager.NavigateTo();
```
The `NavigateTo` method works the same as the standard Blazor `NavigationManager` and also supports passing a relative URL and/or query string.
#### Navigate by abstraction
If you prefer abstraction, you can also navigate by interface as shown below:
```csharp
mvvmNavigationManager.NavigateTo();
```
The same principle works with the `MvvmNavLink` component:
```xml
```
**Navigate by ViewModel Key using the `MvvmNavigationManager` from code:**
Inject the `MvvmNavigationManager` class into your page or ViewModel, then use the `NavigateTo` method:
```csharp
MvvmNavigationManager.NavigateTo("FetchDataViewModel");
```
The same principle works with the `MvvmKeyNavLink` component:
```xml
```
### MVVM Validation
The library provides an `MvvmObservableValidator` component that works with the `EditForm` component to enable validation using the `ObservableValidator` class from the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/) library.
The following example demonstrates how to use the `MvvmObservableValidator` component with the `EditForm` component to perform validation.
**First, define a class that inherits from the `ObservableValidator` class and contains properties with validation attributes:**
```csharp
public class ContactInfo : ObservableValidator
{
private string? _name;
[Required]
[StringLength(100, MinimumLength = 2, ErrorMessage = "The {0} field must have a length between {2} and {1}.")]
[RegularExpression(@"^[a-zA-Z\s'-]+$", ErrorMessage = "The {0} field contains invalid characters. Only letters, spaces, apostrophes, and hyphens are allowed.")]
public string? Name
{
get => _name;
set => SetProperty(ref _name, value, true);
}
private string? _email;
[Required]
[EmailAddress]
public string? Email
{
get => _email;
set => SetProperty(ref _email, value, true);
}
private string? _phoneNumber;
[Required]
[Phone]
[Display(Name = "Phone Number")]
public string? PhoneNumber
{
get => _phoneNumber;
set => SetProperty(ref _phoneNumber, value, true);
}
}
```
**Next, in the `ViewModel` component, define the property that will hold the object to be validated and the methods that will be called when the form is submitted:**
```csharp
public sealed partial class EditContactViewModel : ViewModelBase, IDisposable
{
private readonly ILogger _logger;
[ObservableProperty]
private ContactInfo _contact = new();
public EditContactViewModel(ILogger logger)
{
_logger = logger;
Contact.PropertyChanged += ContactOnPropertyChanged;
}
public void Dispose()
=> Contact.PropertyChanged -= ContactOnPropertyChanged;
[RelayCommand]
private void ClearForm()
=> Contact = new ContactInfo();
[RelayCommand]
private void Save()
=> _logger.LogInformation("Form is valid and submitted!");
private void ContactOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
=> NotifyStateChanged();
}
```
**Finally, in the `View` component, use the `EditForm` component with the `MvvmObservableValidator` component to enable validation:**
```xml
@page "/form"
@inherits MvvmComponentBase
Name:
Email:
Phone Number:
Save
Clear Form
```
## History
### V2.2.0 7 December, 2024
- Added support for `ObservableRecipient` being set to inactive when disposing the `MvvmComponentBase`, `MvvmOwningComponentBase`, `MvvmLayoutComponentBase`, and `RecipientViewModelBase`. [@gragra33](https://github.com/gragra33) & [@teunlielu](https://github.com/teunlielu)
### V2.1.1 4 December, 2024
- Version bump to fix a nuget release issue
### V2.1.0 3 December, 2024
- Added MAUI Blazor Hybrid App support + sample HybridMaui app. [@hakakou](https://github.com/hakakou)
### V2.0.0 30 November, 2024
This is a major release with breaking changes, migration notes can be found [here](docs/migration-notes/v1.4_to_v2.md).
- Added auto registration and discovery of view models. [@mishael-o](https://github.com/mishael-o)
- Added support for keyed view models. [@mishael-o](https://github.com/mishael-o)
- Added support for keyed view models to `MvvmNavLink`, `MvvmKeyNavLink` (new component), `MvvmNavigationManager`, `MvvmComponentBase`, `MvvmOwningComponentBase`, & `MvvmLayoutComponentBase`. [@gragra33](https://github.com/gragra33)
- Added a `MvvmObservableValidator` component which provides support for `ObservableValidator`. [@mishael-o](https://github.com/mishael-o)
- Added parameter resolution in the ViewModel. [@mishael-o](https://github.com/mishael-o)
- Added new `TestKeyedNavigation` samples for Keyed Navigation. [@gragra33](https://github.com/gragra33)
- Added & Updated tests for all changes made. [@mishael-o](https://github.com/mishael-o) & [@gragra33](https://github.com/gragra33)
- Added support for .NET 9. [@gragra33](https://github.com/gragra33)
- Dropped support for .NET 7. [@mishael-o](https://github.com/mishael-o)
- Documentation updates. [@mishael-o](https://github.com/mishael-o) & [@gragra33](https://github.com/gragra33)
**BREAKING CHANGES:**
- Renamed `BlazorHostingModel` to `BlazorHostingModelType` to avoid confusion
The full history can be found in the [Version Tracking](https://github.com/gragra33/Blazing.Mvvm/HISTORY.md) documentation.