An open API service indexing awesome lists of open source software.

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.

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.