gbastecki.BlazorMvvm 1.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package gbastecki.BlazorMvvm --version 1.1.0
                    
NuGet\Install-Package gbastecki.BlazorMvvm -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="gbastecki.BlazorMvvm" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="gbastecki.BlazorMvvm" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="gbastecki.BlazorMvvm" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add gbastecki.BlazorMvvm --version 1.1.0
                    
#r "nuget: gbastecki.BlazorMvvm, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package gbastecki.BlazorMvvm@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=gbastecki.BlazorMvvm&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=gbastecki.BlazorMvvm&version=1.1.0
                    
Install as a Cake Tool

BlazorMvvm

https://github.com/github/docs/actions/workflows/main.yml GitHub NuGet version NuGet downloads

Use the MVVM pattern for Blazor with a simple and lightweight library.

Quick start

This library is distributed via NuGet.

Check Live Demo.

Usage

BlazorMvvm provides a lightweight set of base classes and components to implement the Model-View-ViewModel (MVVM) pattern in Blazor applications.

This guide outlines the core components and their usage.

ViewModel

Viewmodels encapsulate the application's presentation logic and state. In BlazorMvvm, your viewmodels must inherit from the BlazorViewModel base class.

This base class implements IBlazorViewModel, which is essential for notifying the UI when a property's value has changed.

Example: HomeViewModel.cs

using BlazorMvvm;

namespace YourNamespace;

public class HomeViewModel : BlazorViewModel
{
    // --- Option 1: Manual Property Notification ---
    
    private int _counter;
    public int Counter
    {
        get => _counter;
        set
        {
            // Manual equality check
            if (_counter == value) return;
            
            _counter = value;
            
            // Manually raise the OnPropertyChanged event
            // This will trigger the UI to refresh
            base.OnPropertyChanged(); 
        }
    }

    // --- Option 2: Using the Set<T> Helper ---
    
    private int _counter2;
    public int Counter2
    {
        get => _counter2;
        
        // The Set() helper method simplifies this pattern:
        // 1. It performs an equality check.
        // 2. If the value is new, it updates the backing field.
        // 3. It raises the OnPropertyChanged event.
        set => Set(ref _counter2, value);
    }

    // --- Command example ---
    
    private void IncrementCounter()
    {
        Counter++;
    }
    private IBlazorCommand _incrementCounterCommand;
    public IBlazorCommand IncrementCounterCommand => _incrementCounterCommand ??= new BlazorCommand(IncrementCounter);
}

Example: HomeViewModel.cs with Source Generation

using BlazorMvvm;

namespace YourNamespace;

public partial class HomeViewModel : BlazorViewModel //class must be partial
{
    [BlazorObservableProperty]
    private int _counter;
    // Generates:
    // public int Counter
    // {
    //     get => _counter;
    //     set => Set(ref _counter, value);
    // }

    [BlazorObservableProperty]
    private int _counter2;
    // Generates:
    // public int Counter2
    // {
    //     get => _counter2;
    //     set => Set(ref _counter2, value);
    // }

    [BlazorCommand]
    private void IncrementCounter()
    {
        Counter++;
    }
    // Generates:
    // private BlazorMvvm.IBlazorCommand _incrementCounterCommand;
    // public BlazorMvvm.IBlazorCommand IncrementCounterCommand => _incrementCounterCommand ??= new BlazorMvvm.BlazorCommand(IncrementCounter);
}

ComponentBase

To bind a view (Blazor Component) to a viewmodel, your components should inherit from BlazorMvvmComponentBase<T>, where T is the type of your viewmodel.

The final step is to connect your viewmodel instance to the component by calling SetDataContext() in the component's OnInitialized lifecycle method. This subscribes the component to the viewmodel's PropertyChanged events, automatically triggering StateHasChanged() to re-render the component when a property is updated.

Example: Home.razor

@using BlazorMvvm
@inherits BlazorMvvmComponentBase<HomeViewModel>

Example: Home.razor.cs

using BlazorMvvm;

namespace YourNamespace;

public partial class Home : BlazorMvvmComponentBase<HomeViewModel>
{
    // Instantiate the ViewModel
    HomeViewModel ViewModel = new();

    protected override void OnInitialized()
    {
        // Set the DataContext to link the View and ViewModel.
        // This is the essential step for enabling data binding.
        SetDataContext(ViewModel);
        
        base.OnInitialized();
    }
}

ObservableComponent

By default, when a viewmodel property changes, the entire component bound via SetDataContext is re-rendered. For complex UIs, this can be inefficient.

The ObservableComponent allows you to define fine-grained "observable" fragments within your component. These fragments can be bound to a viewmodel and will only re-render when specific properties change, isolating the UI update.

Usage
  • Full Update: Pass a ViewModel instance. The ObservableComponent's child content will re-render for any property change on that viewmodel.

  • Selective Update: Pass a ViewModel and a PropertyNames array. The child content will only re-render when one of the specified properties raises its OnPropertyChanged event.

Example: Home.razor

@using BlazorMvvm
@inherits BlazorMvvmComponentBase<HomeViewModel>


<ObservableComponent ViewModel="ObservablePartViewModel">
    <div>ObservableComponent current counter: @ObservablePartViewModel.Counter</div>
</ObservableComponent>


<ObservableComponent ViewModel="SharedObservableViewModel" PropertyNames="[nameof(SharedObservableViewModel.Counter1)]">
    <div>SharedObservableViewModel current counter 1: @SharedObservableViewModel.Counter1</div>
</ObservableComponent>


<ObservableComponent ViewModel="SharedObservableViewModel" PropertyNames="[nameof(SharedObservableViewModel.Counter2), nameof(SharedObservableViewModel.Counter3)]">
    <div>SharedObservableViewModel current counter 2: @SharedObservableViewModel.Counter2</div>
    <div>SharedObservableViewModel current counter 3: @SharedObservableViewModel.Counter3</div>
</ObservableComponent>

Example: Home.razor.cs

using BlazorMvvm;
using Microsoft.AspNetCore.Components;

namespace YourNamespace;

public class ObservablePartViewModel : BlazorViewModel { /* ... */ }
public class SharedObservableViewModel : BlazorViewModel { /* ... */ }

public partial class Home : BlazorMvvmComponentBase<HomeViewModel>
{
    // Main ViewModel for the component
    HomeViewModel ViewModel = new();
    
    // ViewModels for the observable fragments
    ObservablePartViewModel ObservablePartViewModel = new();
    SharedObservableViewModel SharedObservableViewModel = new();
    
    protected override void OnInitialized()
    {
        // The main viewmodel is set as the primary DataContext
        SetDataContext(ViewModel);
        base.OnInitialized();
    }
}

Commands

BlazorMvvm provides Command implementations that allow you to bind UI actions (like @onclick) to methods on your viewmodel, while also managing execution state (e.g., disabling a button while an async task is running).

Available Implementations
  1. Parameterless:

    • BlazorCommand(Action execute, Func<bool>? canExecute = null)

    • BlazorAsyncCommand(Func<Task> execute, Func<Task<bool>>? canExecute = null, bool allowConcurrentExecutions = false)

  2. Generic (Type-Safe Parameter):

    • BlazorRelayCommand<T>(Action<T> execute, Func<T, bool>? canExecute = null)

    • BlazorAsyncRelayCommand<T>(Func<T, Task> execute, Func<T, Task<bool>>? canExecute = null, bool allowConcurrentExecutions = false)

  3. Object-based Parameter:

    • BlazorRelayCommand(Action<object[]?> execute, Func<object[]?, bool>? canExecute = null)

    • BlazorAsyncRelayCommand(Func<object[]?, Task> execute, Func<object[]?, Task<bool>>? canExecute = null, bool allowConcurrentExecutions = false)

Key Features
  • canExecute: An optional delegate that determines if the command is allowed to run.

  • IsExecuting (Async only): A bool property that is true while the execute task is running.

  • allowConcurrentExecutions (Async only): If false (the default), prevents the command from executing if it IsExecuting.

  • OnIsExecutingChanged (Async only): An event raised when IsExecuting changes. You must subscribe to this and call OnPropertyChanged() to notify the UI to update.

Example: ButtonExampleViewModel.cs

This example demonstrates an async command that disables a button for 5 seconds. It implements IDisposable to safely unsubscribe from the event handler.

using BlazorMvvm;
using System;
using System.Threading.Tasks;

namespace YourNamespace;

public class ButtonExampleViewModel : BlazorViewModel, IDisposable
{
    public IBlazorAsyncCommand DisableButtonCommand { get; }

    public ButtonExampleViewModel()
    {
        // Initialize the command, passing the method to execute
        DisableButtonCommand = new BlazorAsyncCommand(DisableButton);
        
        // Subscribe to the event to update the UI
        DisableButtonCommand.OnIsExecutingChanged += DisableButtonCommand_OnIsExecutingChanged;
    }

    private async Task DisableButton()
    {
        // Simulate a long-running operation
        await Task.Delay(5000);
    }

    private void DisableButtonCommand_OnIsExecutingChanged(bool isExecuting)
    {
        // Notify the UI that the command's state has changed
        // This allows the button's 'disabled' attribute to update
        base.OnPropertyChanged(nameof(DisableButtonCommand));
    }

    // Implement IDisposable to clean up event subscriptions
    public void Dispose()
    {
        DisableButtonCommand.OnIsExecutingChanged -= DisableButtonCommand_OnIsExecutingChanged;
    }
}

Example: .razor Component

This component hosts the ButtonExampleViewModel inside an ObservableComponent to ensure the button state updates correctly.

@using BlazorMvvm
@inherits BlazorMvvmComponentBase<HomeViewModel>

<ObservableComponent ViewModel="ButtonExampleViewModel">
    <button 
        @onclick="ButtonExampleViewModel.DisableButtonCommand.Execute" 
        disabled="@ButtonExampleViewModel.DisableButtonCommand.IsExecuting">
        
        Disable button for 5 seconds
    </button>
</ObservableComponent>

@code {
    ButtonExampleViewModel ButtonExampleViewModel = new();

    protected override void OnDispose()
    {
        ButtonExampleViewModel.Dispose();
        base.OnDispose();
    }
}

ViewModelFactory

The BlazorMvvmViewModelFactory is responsible for resolving and providing instances of ViewModels with support for different lifetimes.

Features
  • Automatic Dependency Injection: ViewModels can be automatically registered and injected.
  • Lazy Loading support: Works seamlessly with lazy-loaded assemblies.
  • AOT & Trimming compatible: Uses Source Generators and Module Initializers to avoid runtime reflection, making it fully compatible with AOT and Trimming.
  • Flexible lifetimes: Support for Transient, Scoped, and Singleton ViewModels.
  • Constructor Selection: Ability to specify which constructor to use when multiple constructors are present.
  • Service Injection: Supports constructor injection for services registered in the DI container.
  • Integration with BlazorMvvmComponentBase: Automatically resolves and injects ViewModels into components.
Setup

To use automatic dependency injection for registered ViewModels, in your main Program.cs, add the following line to register the BlazorMvvm ViewModelFactory:

builder.Services.UseBlazorMvvmViewModelFactory();
Registering ViewModels

To register a ViewModel with the BlazorMvvmViewModelFactory, decorate the ViewModel class with the [BlazorMvvmViewModel] attribute, specifying the desired lifetime.

[BlazorMvvmViewModel(ViewModelLifetime.Singleton)]
public class HomeViewModel : BlazorViewModel { ... }
ViewModelFactory parameters injection

You can also register services that your ViewModels depend on in the DI container as usual, to make them available for constructor injection.

builder.Services.AddSingleton<IService, ServiceImplementation>();

Then, you can declare constructor parameters in your ViewModel, and they will be automatically injected when the ViewModel is resolved.

[BlazorMvvmViewModel(ViewModelLifetime.Singleton)]
public class HomeViewModel : BlazorViewModel 
{
    private readonly IService _service;
    public HomeViewModel(IService service) 
    {
        _service = service;
    }
}
ViewModelFactory constructor selection

If your ViewModel has multiple constructors, you can specify which constructor should be used by decorating it with the [BlazorMvvmViewModelFactoryConstructor] attribute.

[BlazorMvvmViewModel(ViewModelLifetime.Singleton)]
public class HomeViewModel : BlazorViewModel 
{
    private readonly IService _service;
    public HomeViewModel() 
    {
    }
    // This constructor will be used by the ViewModelFactory
    [BlazorMvvmViewModelFactoryConstructor]
    public HomeViewModel(IService service) 
    {
        _service = service;
    }
}
ViewModel lifetimes initialized via ViewModelFactory

You can control the lifetime of your ViewModels using the attribute:

  • [BlazorMvvmViewModel(ViewModelLifetime.Transient)]: Created every time it's requested (Default).
  • [BlazorMvvmViewModel(ViewModelLifetime.Scoped)]: Created once per scope.
  • [BlazorMvvmViewModel(ViewModelLifetime.Singleton)]: Created once per application.
Using ViewModelFactory in components

When using the BlazorMvvmViewModelFactory, you can retrieve ViewModel instances via dependency injection.

There is no need to call SetDataContext() manually, as the base class will do it for you.

Example: Home.razor.cs with ViewModelFactory registration

using BlazorMvvm;

namespace YourNamespace;

public partial class Home : BlazorMvvmComponentBase<HomeViewModel>
{
    // ViewModel will be resolved and injected automatically if registered with [BlazorMvvmViewModelAttribute].
    // No need to instantiate it manually.
    // It can be accessed via BaseViewModel property inherited from BlazorMvvmComponentBase<T>.
    // OnInitialized will call SetDataContext automatically.
    // Just make sure to call base.OnInitialized() if overriding OnInitialized.
    protected override void OnInitialized()
    {
        base.OnInitialized();
    }
}

Source generation for properties and commands

Features
  • Automatic property generation: Convert fields into observable properties with [BlazorObservableProperty].
  • Commands generation: Generate Commands implementations from methods with [BlazorCommand].
  • Async support: Support for both Task and ValueTask in commands.
  • Flexible CanExecute: Support for synchronous and asynchronous CanExecute methods.
  • Parameter support: Handle parameterless methods, single parameters, and multiple parameters (using Tuples).
  • Concurrency control: Configure concurrent execution behavior for async commands.
Observable Properties

Decorate a private field with [BlazorObservableProperty] to generate a public property with change notification.

ViewModel classes must be declared as partial to use this feature.

The generator assumes the field is named either lowerCamel, _lowerCamel or m_lowerCamel, and it will transform that to be UpperCamel.

public partial class CounterViewModel : BlazorViewModel
{
    [BlazorObservableProperty]
    private int _count;

    // Generates:
    // public int Count
    // {
    //     get => _count;
    //     set => Set(ref _count, value);
    // }
}
Commands

Decorate a method with [BlazorCommand] to generate a proper Command.

Synchronous Command
[BlazorCommand]
private void Increment()
{
    Count++;
}

// Generates: public IBlazorCommand IncrementCommand { get; }
Asynchronous Command

Supports both Task and ValueTask.

[BlazorCommand]
private async Task LoadDataAsync()
{
    await Task.Delay(1000);
    // ...
}

// Generates: public IBlazorAsyncCommand LoadDataAsyncCommand { get; }
Command with Parameter
[BlazorCommand]
private void UpdateMessage(string message)
{
    Message = message;
}

// Generates: public IBlazorRelayCommand<string> UpdateMessageCommand { get; }
Command with Multiple Parameters

Methods with multiple parameters are wrapped using a Tuple.

[BlazorCommand]
private void Add(int a, int b)
{
    Count = a + b;
}

// Generates: public IBlazorRelayCommand<(int, int)> AddCommand { get; }
// Usage in View: <button @onclick="() => AddCommand.Execute((5, 10))"></button>
CanExecute Logic

You can specify a method to control command execution using the CanExecute parameter (via constructor argument or property).

Synchronous CanExecute
[BlazorCommand(CanExecute = nameof(CanIncrement))]
private void Increment() => Count++;

private bool CanIncrement() => Count < 10;
Asynchronous CanExecute

The source generator automatically handles wrapping synchronous predicates for async commands, or you can use async predicates.

[BlazorCommand(nameof(CanDoAsync))]
private async Task DoAsync() { ... }

// Can be synchronous bool
// private bool CanDoAsync() => true;

// Or asynchronous
private async Task<bool> CanDoAsync() 
{
    // ...
    return true;
}
Concurrency Control

For async commands, you can control whether multiple executions are allowed simultaneously.

// Prevent concurrent executions (default is false)
[BlazorCommand(AllowConcurrentExecutions = false)]
private async Task SubmitAsync()
{
    // ...
}

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 1,233 12/25/2025
1.1.2 802 12/2/2025
1.1.1 703 12/1/2025
1.1.0 409 11/30/2025
1.0.3 254 11/22/2025
1.0.2 330 11/11/2025
1.0.1 247 10/30/2025
1.0.0 283 2/2/2025