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
                    
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="Nabs.Launchpad.Core.ViewModels" Version="9.0.146" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nabs.Launchpad.Core.ViewModels" Version="9.0.146" />
                    
Directory.Packages.props
<PackageReference Include="Nabs.Launchpad.Core.ViewModels" />
                    
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 Nabs.Launchpad.Core.ViewModels --version 9.0.146
                    
#r "nuget: Nabs.Launchpad.Core.ViewModels, 9.0.146"
                    
#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 Nabs.Launchpad.Core.ViewModels@9.0.146
                    
#: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=Nabs.Launchpad.Core.ViewModels&version=9.0.146
                    
Install as a Cake Addin
#tool nuget:?package=Nabs.Launchpad.Core.ViewModels&version=9.0.146
                    
Install as a Cake Tool

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 list
  • NewItemCommand: Create a new item
  • SaveItemCommand: Save current changes
  • CancelItemCommand: Discard changes
  • DeleteItemCommand: 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

  1. Validation: Always implement FluentValidation validators for your DTOs
  2. Async Operations: Use async/await patterns consistently for all service calls
  3. Error Handling: Leverage Ardalis.Result for consistent error handling
  4. Localization: Use IStringLocalizer for all user-facing strings
  5. Resource Management: ViewModels automatically handle subscription cleanup
  6. State Management: Let the base classes handle state coordination
  7. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
9.0.146 57 8/15/2025
9.0.145 126 8/11/2025
9.0.144 129 8/8/2025
9.0.137 115 7/29/2025
9.0.136 114 7/29/2025
9.0.135 95 7/28/2025
9.0.134 144 7/9/2025
9.0.133 141 7/9/2025
9.0.132 143 7/9/2025
9.0.131 152 7/9/2025
9.0.130 141 7/7/2025