Lifted.MVVM
8.0.0
dotnet add package Lifted.MVVM --version 8.0.0
NuGet\Install-Package Lifted.MVVM -Version 8.0.0
<PackageReference Include="Lifted.MVVM" Version="8.0.0" />
<PackageVersion Include="Lifted.MVVM" Version="8.0.0" />
<PackageReference Include="Lifted.MVVM" />
paket add Lifted.MVVM --version 8.0.0
#r "nuget: Lifted.MVVM, 8.0.0"
#:package Lifted.MVVM@8.0.0
#addin nuget:?package=Lifted.MVVM&version=8.0.0
#tool nuget:?package=Lifted.MVVM&version=8.0.0
Lifted.MVVM
A lightweight .NET 8 Blazor MVVM framework that simplifies building Blazor applications using the Model-View-ViewModel pattern. Built on top of the CommunityToolkit.Mvvm, Lifted.MVVM provides ready-to-use base classes and components that eliminate boilerplate code and handle common MVVM patterns automatically.
🚀 Features
- Zero-Configuration MVVM: Automatic property change notifications and UI updates
- Multiple ViewModel Base Classes: Choose the right base for your needs (basic, validation, messaging)
- Blazor Component Integration: Seamless integration with Blazor's component lifecycle
- Built-in Validation Support: Easy form validation with visual feedback
- Messaging Support: Inter-component communication using the Messenger pattern
- Dependency Injection Ready: Full support for DI with automatic ViewModel injection
- Async-First Design: Built-in async initialization support
📦 Installation
Install via NuGet Package Manager:
dotnet add package Lifted.MVVM
Or via Package Manager Console:
Install-Package Lifted.MVVM
🎯 Quick Start
1. Create a ViewModel
Choose one of three base classes depending on your needs:
using Lifted.MVVM.ComponentModel.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
public partial class CounterViewModel : LiftedVM
{
[ObservableProperty]
private int _count;
[RelayCommand]
private void IncrementCount()
{
Count++;
}
public override async Task Loaded()
{
// Initialize your ViewModel here
Count = 0;
await base.Loaded();
}
}
2. Register the ViewModel
In your Program.cs:
builder.Services.AddScoped<CounterViewModel>();
3. Create a Blazor Component
@page "/counter"
@inherits LiftedComponentBase<CounterViewModel>
<h1>Counter</h1>
<p>Current count: @VM.Count</p>
<button @onclick="VM.IncrementCountCommand.Execute">Click me</button>
That's it! The component automatically re-renders when Count changes.
📚 Core Components
ViewModel Base Classes
LiftedVM
The basic ViewModel base class for most scenarios.
public partial class MyViewModel : LiftedVM
{
[ObservableProperty]
private string _message = "Hello, World!";
public override async Task Loaded()
{
// Called when the component initializes
await Task.CompletedTask;
}
}
Features:
- Inherits from
ObservableObject(CommunityToolkit.Mvvm) - Automatic property change notifications
OnInitializedAsync()lifecycle methodLoaded()command for initialization logicNotifyStateChanged()to trigger full UI refresh
LiftedValidatorVM
For ViewModels that require validation support.
using System.ComponentModel.DataAnnotations;
public partial class LoginViewModel : LiftedValidatorVM
{
[ObservableProperty]
[Required(ErrorMessage = "Username is required")]
[MinLength(3, ErrorMessage = "Username must be at least 3 characters")]
private string _username = string.Empty;
[ObservableProperty]
[Required(ErrorMessage = "Password is required")]
private string _password = string.Empty;
[RelayCommand]
private async Task Login()
{
ValidateAllProperties();
if (!HasErrors)
{
// Perform login
await Task.CompletedTask;
}
}
}
Features:
- Inherits from
ObservableValidator(CommunityToolkit.Mvvm) - Built-in validation using Data Annotations
HasErrorspropertyGetErrors()method- Works with
LiftedValidationSummarycomponent
LiftedRecipientVM and LiftedRecipientVM<TMessage>
For ViewModels that need to send/receive messages between components.
// Define a message
public class UserLoggedInMessage
{
public string Username { get; set; } = string.Empty;
}
// ViewModel that receives messages
public partial class HeaderViewModel : LiftedRecipientVM<UserLoggedInMessage>
{
[ObservableProperty]
private string _currentUser = "Guest";
public override void Receive(UserLoggedInMessage message)
{
CurrentUser = message.Username;
}
public override async Task Loaded()
{
// Register to receive messages
IsActive = true;
await base.Loaded();
}
}
// ViewModel that sends messages
public partial class LoginViewModel : LiftedRecipientVM
{
[RelayCommand]
private async Task Login(string username)
{
// Send message to other ViewModels
Messenger.Send(new UserLoggedInMessage { Username = username });
await Task.CompletedTask;
}
}
Features:
- Inherits from
ObservableRecipient(CommunityToolkit.Mvvm) - Built-in messenger support
- Type-safe message handling
- Automatic message subscription/unsubscription
Blazor Component Base Classes
LiftedComponentBase<TViewModel>
The standard base class for Blazor pages and components.
@page "/mypage"
@inherits LiftedComponentBase<MyViewModel>
<h1>@VM.Title</h1>
<p>@VM.Description</p>
Features:
- Automatic ViewModel injection via DI
- Automatic UI updates on property changes
- Calls
VM.OnInitializedAsync()during component initialization - Access ViewModel via
VMproperty
LiftedLayoutComponentBase<TViewModel>
For layout components that need a ViewModel.
@inherits LiftedLayoutComponentBase<LayoutViewModel>
<div class="page">
<header>
<h1>@VM.AppTitle</h1>
</header>
<main>
@Body
</main>
</div>
Features:
- Same as
LiftedComponentBasebut for layouts - Inherits from
LayoutComponentBase
LiftedParameterBoundComponentBase<TViewModel>
For child components that receive their ViewModel as a parameter instead of via DI.
@* Parent Component *@
<ChildComponent VM="@childViewModel" />
@* Child Component *@
@inherits LiftedParameterBoundComponentBase<ChildViewModel>
<div>
<p>@VM.Message</p>
</div>
@code {
// VM is received as a [Parameter] instead of [Inject]
}
Features:
- ViewModel passed as
[Parameter]instead of injected - Useful for reusable child components
- Same automatic UI update behavior
Validation Component
LiftedValidationSummary
Displays validation errors from a LiftedValidatorVM.
@page "/register"
@inherits LiftedComponentBase<RegisterViewModel>
<EditForm Model="@VM" OnValidSubmit="@VM.RegisterCommand.Execute">
<LiftedValidationSummary Context="@VM" />
<InputText @bind-Value="VM.Username" />
<InputText @bind-Value="VM.Email" type="email" />
<InputText @bind-Value="VM.Password" type="password" />
<button type="submit">Register</button>
</EditForm>
Features:
- Automatically displays validation errors
- Updates in real-time as errors change
- Customizable CSS classes (
validation-errors,validation-message)
💡 Usage Examples
Example 1: Layout with ViewModel
LayoutViewModel.cs
using Lifted.MVVM.ComponentModel.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
public partial class MainLayoutViewModel : LiftedVM
{
[ObservableProperty]
private string _appTitle = "My Application";
[ObservableProperty]
private string _currentUser = "Guest";
public override async Task Loaded()
{
// Initialize layout data
await Task.CompletedTask;
}
}
MainLayout.razor
@inherits LiftedLayoutComponentBase<MainLayoutViewModel>
<div>
<header>
<h1>@VM.AppTitle</h1>
<span>User: @VM.CurrentUser</span>
</header>
<main>
@Body
</main>
<footer>
<p>© 2024 @VM.AppTitle</p>
</footer>
</div>
Program.cs
builder.Services.AddScoped<MainLayoutViewModel>();
Example 2: Page with ViewModel
ProductsViewModel.cs
using Lifted.MVVM.ComponentModel.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class ProductsViewModel : LiftedVM
{
[ObservableProperty]
private List<Product> _products = new();
[ObservableProperty]
private string _searchTerm = string.Empty;
[Inject]
public IProductService ProductService { get; set; } = default!;
public override async Task Loaded()
{
Products = await ProductService.GetAllAsync();
}
[RelayCommand]
private async Task Search()
{
Products = await ProductService.SearchAsync(SearchTerm);
}
}
Products.razor
@page "/products"
@inherits LiftedComponentBase<ProductsViewModel>
<h2>Products</h2>
<input @bind="VM.SearchTerm" placeholder="Search..." />
<button @onclick="VM.SearchCommand.Execute">Search</button>
<ul>
@foreach (var product in VM.Products)
{
<li>@product.Name - $@product.Price</li>
}
</ul>
Program.cs
builder.Services.AddScoped<ProductsViewModel>();
builder.Services.AddScoped<IProductService, ProductService>();
Example 3: Injected Component
AlertViewModel.cs
using Lifted.MVVM.ComponentModel.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class AlertViewModel : LiftedVM
{
[ObservableProperty]
private string _message = string.Empty;
[ObservableProperty]
private bool _isVisible = false;
[RelayCommand]
public void Show(string message)
{
Message = message;
IsVisible = true;
}
[RelayCommand]
public void Close()
{
IsVisible = false;
}
}
AlertComponent.razor
@inherits LiftedComponentBase<AlertViewModel>
@if (VM.IsVisible)
{
<div class="alert">
<p>@VM.Message</p>
<button @onclick="VM.CloseCommand.Execute">Close</button>
</div>
}
Usage in a Page
@page "/home"
@inject AlertViewModel AlertVM
<h1>Home Page</h1>
<button @onclick="() => AlertVM.ShowCommand.Execute("Hello!")">Show Alert</button>
<AlertComponent />
Program.cs
builder.Services.AddScoped<AlertViewModel>();
Example 4: Parameter-Bound Component
CardViewModel.cs
using Lifted.MVVM.ComponentModel.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class CardViewModel : LiftedVM
{
[ObservableProperty]
private string _title = string.Empty;
[ObservableProperty]
private string _content = string.Empty;
[ObservableProperty]
private bool _isExpanded = false;
[RelayCommand]
private void ToggleExpand()
{
IsExpanded = !IsExpanded;
}
}
Card.razor
@inherits LiftedParameterBoundComponentBase<CardViewModel>
<div class="card">
<h3 @onclick="VM.ToggleExpandCommand.Execute">@VM.Title</h3>
@if (VM.IsExpanded)
{
<p>@VM.Content</p>
}
</div>
Usage in a Page
@page "/dashboard"
<h1>Dashboard</h1>
@foreach (var cardVM in cardViewModels)
{
<Card VM="@cardVM" />
}
@code {
private List<CardViewModel> cardViewModels = new()
{
new() { Title = "Card 1", Content = "Content 1" },
new() { Title = "Card 2", Content = "Content 2" },
new() { Title = "Card 3", Content = "Content 3" }
};
}
🎨 Advanced Usage
Custom Initialization Logic
Override the Loaded() method to perform initialization:
public partial class ProductListViewModel : LiftedVM
{
[ObservableProperty]
private List<Product> _products = new();
[Inject]
public IProductService ProductService { get; set; } = default!;
public override async Task Loaded()
{
Products = await ProductService.GetAllProductsAsync();
await base.Loaded();
}
}
Manual State Refresh
Force a complete UI refresh when needed:
public partial class DashboardViewModel : LiftedVM
{
private async Task RefreshAll()
{
// Update multiple properties
await LoadData();
// Notify UI to refresh everything
NotifyStateChanged();
}
}
Combining Validation and Messaging
public partial class CheckoutViewModel : LiftedValidatorVM
{
[Inject]
public IMessenger Messenger { get; set; } = default!;
[ObservableProperty]
[Required]
private string _shippingAddress = string.Empty;
[RelayCommand]
private async Task CompleteCheckout()
{
ValidateAllProperties();
if (!HasErrors)
{
// Process checkout
await ProcessOrder();
// Notify other components
Messenger.Send(new OrderCompletedMessage());
}
}
}
🔧 Configuration
Registering ViewModels
Register your ViewModels in Program.cs:
// Scoped (recommended for most scenarios)
builder.Services.AddScoped<MyViewModel>();
// Transient (new instance each time)
builder.Services.AddTransient<MyViewModel>();
// Singleton (shared across the app)
builder.Services.AddSingleton<MyViewModel>();
Using with Messenger
Register the messenger service:
builder.Services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default);
📖 Best Practices
Choose the Right Base Class
- Use
LiftedVMfor basic scenarios - Use
LiftedValidatorVMwhen you need validation - Use
LiftedRecipientVMwhen you need messaging
- Use
Lifecycle Management
- Use
Loaded()for async initialization - Don't perform heavy operations in constructors
- Clean up resources in
Dispose()if needed
- Use
Property Changes
- Use
[ObservableProperty]for automatic notifications - Use
NotifyStateChanged()sparingly for full refreshes
- Use
Validation
- Call
ValidateAllProperties()before submitting forms - Check
HasErrorsbefore proceeding with operations
- Call
Messaging
- Set
IsActive = trueinLoaded()for recipients - Use specific message types for type safety
- Remember to unregister when needed (handled automatically)
- Set
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Built on CommunityToolkit.Mvvm
- Inspired by various MVVM frameworks in the .NET ecosystem
📞 Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with ❤️ by CodeLifterIO
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 was computed. 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 was computed. 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. |
-
net8.0
- CommunityToolkit.Mvvm (>= 8.2.2)
- Microsoft.AspNetCore.Components.Web (>= 8.0.4)
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 | |
|---|---|---|---|
| 8.0.0 | 343 | 12/7/2025 | |
| 8.0.0-pre0.0.2 | 183 | 3/29/2024 | |
| 8.0.0-pre0.0.1 | 185 | 3/5/2024 | |
| 8.0.0-pre0.0.0 | 162 | 2/26/2024 |