Nabs.Launchpad.Core.ViewModels
9.0.146
Prefix Reserved
dotnet add package Nabs.Launchpad.Core.ViewModels --version 9.0.146
NuGet\Install-Package Nabs.Launchpad.Core.ViewModels -Version 9.0.146
<PackageReference Include="Nabs.Launchpad.Core.ViewModels" Version="9.0.146" />
<PackageVersion Include="Nabs.Launchpad.Core.ViewModels" Version="9.0.146" />
<PackageReference Include="Nabs.Launchpad.Core.ViewModels" />
paket add Nabs.Launchpad.Core.ViewModels --version 9.0.146
#r "nuget: Nabs.Launchpad.Core.ViewModels, 9.0.146"
#:package Nabs.Launchpad.Core.ViewModels@9.0.146
#addin nuget:?package=Nabs.Launchpad.Core.ViewModels&version=9.0.146
#tool nuget:?package=Nabs.Launchpad.Core.ViewModels&version=9.0.146
Nabs Launchpad Core ViewModels Library
This library contains the core ViewModels for the Nabs Launchpad application. It provides a robust MVVM foundation for Blazor applications with built-in validation, change tracking, command patterns, and master-detail scenarios.
Overview
The Core ViewModels library implements the Model-View-ViewModel (MVVM) pattern using the CommunityToolkit.Mvvm framework. It is designed to provide a clean separation of concerns between the UI and business logic, enabling easier testing, maintenance, and code reuse across different presentation layers.
Key Features
- MVVM Pattern: Built on CommunityToolkit.Mvvm with observable objects and relay commands
- Validation Framework: Integrated FluentValidation with real-time validation feedback
- Change Tracking: Automatic dirty state detection and nested object monitoring
- Master-Detail Pattern: Pre-built ViewModels for common master-detail scenarios
- Search Functionality: Abstract search ViewModels with debouncing and result management
- Localization Support: Full integration with .NET localization services
- Command Pattern: Async relay commands with proper can-execute logic
Architecture
Base Classes
BaseItemViewModel<TItem>
The foundation class for all item-based ViewModels providing:
- Change Tracking: Automatic detection of modifications through deep comparison
- Validation: Integration with FluentValidation for real-time validation
- Nested Object Support: Property change notifications for complex object graphs
- Loading States: Built-in busy and ready state management
- Events: Extensible event system for custom behavior
MasterDetailViewModel<TListItem, TItem>
Abstract base class for master-detail scenarios featuring:
- List Management: Observable collection of master list items
- Selection Handling: Automatic detail loading on selection changes
- CRUD Operations: Built-in commands for create, read, update, delete operations
- State Coordination: Synchronized state between master list and detail views
SearchViewModel<TSearchResultListItem, TSearchResultItem>
Base class for search scenarios with:
- Debounced Search: Configurable delay to prevent excessive API calls
- Result Management: Observable collections for search results
- Selection Handling: Automatic detail loading for selected search results
- Async Operations: Full async/await support for search operations
Dependencies
- CommunityToolkit.Mvvm: Core MVVM functionality and source generators
- FluentValidation: Comprehensive validation framework
- Ardalis.Result: Result pattern implementation for operation outcomes
- Nabs.Launchpad.Core.Dtos: Shared data transfer objects
- Nabs.Launchpad.Ui.Shell: Common UI services and utilities
Usage Examples
Basic Item ViewModel
public class PersonViewModel : BaseItemViewModel<PersonDto>
{
public PersonViewModel(IStringLocalizer<PersonViewModel> localizer)
: base(localizer)
{
Validation.Validator = new PersonValidator();
}
}
public class PersonValidator : AbstractValidator<PersonDto>
{
public PersonValidator()
{
RuleFor(x => x.FirstName).NotEmpty().MaximumLength(50);
RuleFor(x => x.LastName).NotEmpty().MaximumLength(50);
RuleFor(x => x.Email).EmailAddress().When(x => !string.IsNullOrEmpty(x.Email));
}
}
Master-Detail Implementation
public class PersonMasterDetailViewModel : MasterDetailViewModel<PersonListItemDto, PersonDto>
{
private readonly IPersonService _personService;
public PersonMasterDetailViewModel(
IPersonService personService,
IStringLocalizer<PersonMasterDetailViewModel> localizer)
: base(localizer)
{
_personService = personService;
Title = "Person Management";
MasterTitle = "People";
DetailsTitle = "Person Details";
}
protected override async Task GetListItemsAsync()
{
var result = await _personService.GetPersonListAsync();
if (result.IsSuccess)
{
ListItems = new ObservableCollection<PersonListItemDto>(result.Value);
}
}
protected override async Task<Result<PersonDto>> GetItemAsync(PersonListItemDto masterListItem)
{
return await _personService.GetPersonAsync(masterListItem.Id);
}
protected override async Task<Result<PersonDto>> NewItemAsync()
{
return Result.Success(new PersonDto());
}
protected override async Task<Result<PersonDto>> SaveItemAsync()
{
if (DetailViewModel.CurrentItem?.Id == null)
{
return await _personService.CreatePersonAsync(DetailViewModel.CurrentItem);
}
return await _personService.UpdatePersonAsync(DetailViewModel.CurrentItem);
}
protected override async Task<Result> DeleteItemAsync(PersonDto item)
{
return await _personService.DeletePersonAsync(item.Id);
}
}
Search ViewModel Implementation
public class PersonSearchViewModel : SearchViewModel<PersonListItemDto, PersonDto>
{
private readonly IPersonService _personService;
public PersonSearchViewModel(IPersonService personService)
{
_personService = personService;
DebounceDelay = 300; // Customize debounce delay
}
public override async Task<PersonListItemDto[]> SearchAsync(
string searchText,
CancellationToken cancellationToken = default)
{
var result = await _personService.SearchPersonsAsync(searchText, cancellationToken);
return result.IsSuccess ? result.Value : Array.Empty<PersonListItemDto>();
}
public override async Task<PersonDto> GetSearchResultItemAsync(
PersonListItemDto? searchResultListItem = null,
CancellationToken cancellationToken = default)
{
if (searchResultListItem == null) return new PersonDto();
var result = await _personService.GetPersonAsync(searchResultListItem.Id, cancellationToken);
return result.IsSuccess ? result.Value : new PersonDto();
}
protected override async Task AfterSearchResultItemSet(PersonDto? searchResultItem)
{
// Custom logic after search result is selected
await Task.CompletedTask;
}
}
Validation Integration
The library provides seamless integration with FluentValidation:
Real-time Validation
- Validation occurs automatically on property changes
- Validation results are exposed through the
Validation.ValidationResult
property - UI can bind to validation errors for immediate feedback
Custom Validation Configuration
public class ValidationConfiguration
{
public IValidator? Validator { get; set; }
public ValidationResult? ValidationResult { get; set; }
public bool IsValid => ValidationResult?.IsValid ?? true;
public bool HasErrors => !IsValid;
}
Change Tracking
Automatic Dirty Detection
- Compares current item state with initial state using deep comparison
- Tracks changes in nested objects and collections
- Exposes
IsDirty
property for UI feedback
Event Handling
public class ItemViewModelEvents
{
public Action<object?, PropertyChangedEventArgs>? CurrentItemPropertyChangedHandler { get; set; }
public Action? UpdateCommandsChangedHandler { get; set; }
public Action<Dictionary<string, Func<Array>>>? ConfigureSelectors { get; set; }
}
Command Pattern
Built-in Commands
Master-detail ViewModels include standard commands:
RefreshListItemsCommand
: Reload the master listNewItemCommand
: Create a new itemSaveItemCommand
: Save current changesCancelItemCommand
: Discard changesDeleteItemCommand
: Delete current item
Command State Management
Commands automatically update their CanExecute
state based on:
- Busy state of the ViewModel
- Validation state of the current item
- Dirty state for save/cancel operations
Integration with Blazor
Blazor Component Binding
@inject PersonMasterDetailViewModel ViewModel
<div class="master-detail-container">
<div class="master-panel">
<button @onclick="ViewModel.RefreshListItemsCommand.ExecuteAsync"
disabled="@(!ViewModel.RefreshListItemsCommand.CanExecute(null))">
Refresh
</button>
@foreach (var item in ViewModel.ListItems)
{
<div class="list-item @(ViewModel.SelectedListItem == item ? "selected" : "")"
@onclick="() => ViewModel.SelectedListItem = item">
@item.DisplayName
</div>
}
</div>
<div class="detail-panel">
@if (ViewModel.DetailViewModel.IsReady)
{
<EditForm Model="ViewModel.DetailViewModel.CurrentItem">
<FluentValidationValidator />
<ValidationSummary />
<InputText @bind-Value="ViewModel.DetailViewModel.CurrentItem.FirstName" />
<ValidationMessage For="() => ViewModel.DetailViewModel.CurrentItem.FirstName" />
<button type="button"
@onclick="ViewModel.SaveItemCommand.ExecuteAsync"
disabled="@(!ViewModel.SaveItemCommand.CanExecute(null))">
Save
</button>
</EditForm>
}
</div>
</div>
Service Registration
// Program.cs or service configuration
builder.Services.AddScoped<PersonMasterDetailViewModel>();
builder.Services.AddScoped<PersonSearchViewModel>();
Testing
The library is designed for testability with:
- Dependency injection for all external dependencies
- Abstract base classes that can be easily mocked
- Event-driven architecture for testing state changes
- Comprehensive unit test coverage in
Launchpad.Core.ViewModels.UnitTests
Example Unit Test
[Fact]
public async Task SaveCommand_WithValidItem_SavesSuccessfully()
{
// Arrange
var mockService = new Mock<IPersonService>();
var mockLocalizer = new Mock<IStringLocalizer<PersonMasterDetailViewModel>>();
var viewModel = new PersonMasterDetailViewModel(mockService.Object, mockLocalizer.Object);
mockService.Setup(x => x.CreatePersonAsync(It.IsAny<PersonDto>()))
.ReturnsAsync(Result.Success(new PersonDto()));
// Act
await viewModel.InitializeAsync();
viewModel.NewItemCommand.Execute(null);
viewModel.DetailViewModel.CurrentItem.FirstName = "John";
await viewModel.SaveItemCommand.ExecuteAsync(null);
// Assert
mockService.Verify(x => x.CreatePersonAsync(It.IsAny<PersonDto>()), Times.Once);
}
Best Practices
- Validation: Always implement FluentValidation validators for your DTOs
- Async Operations: Use async/await patterns consistently for all service calls
- Error Handling: Leverage Ardalis.Result for consistent error handling
- Localization: Use IStringLocalizer for all user-facing strings
- Resource Management: ViewModels automatically handle subscription cleanup
- State Management: Let the base classes handle state coordination
- Command Logic: Override virtual methods rather than replacing commands
Integration with Launchpad
This library integrates with other Nabs Launchpad components:
- Core.Dtos: Provides the data transfer objects used by ViewModels
- Ui.Shell: Common UI services like loading indicators and utilities
- Core.Context: Database context for data persistence
- Core.ServiceDefaults: Service configuration and dependency injection
Contributing
This library follows the Nabs Launchpad coding standards:
- Use C# 13 features and latest language constructs
- Follow nullable reference types conventions (
is null
,is not null
) - Implement proper async/await patterns
- Use file-scoped namespaces and modern C# syntax
- Include comprehensive unit tests with AAA pattern
- Apply code formatting defined in
.editorconfig
License
Copyright � Net Advantage Business Solutions
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | 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 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. |
-
net9.0
- Ardalis.Result (>= 10.1.0)
- Ardalis.Result.FluentValidation (>= 10.1.0)
- CommunityToolkit.Mvvm (>= 8.4.0)
- FluentValidation (>= 12.0.0)
- Nabs.Launchpad.Core.Dtos (>= 9.0.146)
- Nabs.Launchpad.Ui.Shell (>= 9.0.146)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Nabs.Launchpad.Core.ViewModels:
Package | Downloads |
---|---|
Nabs.Launchpad.Ui.Shell.Blazor.Sf
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.