devoft.ClientModel 3.0.2

dotnet add package devoft.ClientModel --version 3.0.2
                    
NuGet\Install-Package devoft.ClientModel -Version 3.0.2
                    
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="devoft.ClientModel" Version="3.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="devoft.ClientModel" Version="3.0.2" />
                    
Directory.Packages.props
<PackageReference Include="devoft.ClientModel" />
                    
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 devoft.ClientModel --version 3.0.2
                    
#r "nuget: devoft.ClientModel, 3.0.2"
                    
#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 devoft.ClientModel@3.0.2
                    
#: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=devoft.ClientModel&version=3.0.2
                    
Install as a Cake Addin
#tool nuget:?package=devoft.ClientModel&version=3.0.2
                    
Install as a Cake Tool

devoft.ClientModel

MVVM library for .NET 10 focused on ViewModel implementation with registered properties, fluent validation, value coercion, change notification, commands, and scope-based undo/redo support.

Features

  • Generic ViewModelBase<TViewModel> base class.
  • Property backing storage through GetValue and SetValue.
  • Observable collections through GetCollection.
  • Fluent property configuration with RegisterProperty and RegisterCollectionProperty.
  • Validation results with Error, Warning, and Information states.
  • Chained coercion before assigning values.
  • Property dependency propagation through DependOn.
  • Dynamically registered commands with RegisterCommand.
  • Scope-based editing with BeginScope, Undo(), and Redo().
  • Integration with INotifyPropertyChanged and INotifyDataErrorInfo.

Installation

dotnet add package devoft.ClientModel

Requirements

  • .NET 10
  • devoft.Core as a transitive package dependency

Basic usage

using System;
using devoft.ClientModel;
using devoft.ClientModel.Validation;

public sealed class ContactEditorViewModel : ViewModelBase<ContactEditorViewModel>
{
    static ContactEditorViewModel()
    {
        RegisterProperty(x => x.FirstName)
            .Validate((vm, value, results) => results.Error<NotNull>(value), notifyChangeOnValidationError: true, when: ValidationErrorBehavior.BeforeCoerce)
            .Validate((vm, value, results) => results.Error(string.IsNullOrWhiteSpace(value), "First name is required"), notifyChangeOnValidationError: true)
            .Coerce(value => value.Trim())
            .Coerce(value => char.ToUpper(value[0]) + value[1..].ToLower());

        RegisterProperty(x => x.LastName)
            .Validate((vm, value, results) => results.Error(value != null && string.IsNullOrWhiteSpace(value), "Last name cannot contain only whitespace"), notifyChangeOnValidationError: true)
            .Coerce(value => value?.Trim());

        RegisterProperty(x => x.FullName)
            .DependOn(x => x.FirstName)
            .DependOn(x => x.LastName);
    }

    public string FirstName
    {
        get => GetValue<string>();
        set => SetValue(value);
    }

    public string LastName
    {
        get => GetValue<string>();
        set => SetValue(value);
    }

    public string FullName => $"{FirstName} {LastName}";
}

Validation

Each property keeps a ValidationResult collection that can be accessed through GetValidationResults(string propertyName).

var vm = new ContactEditorViewModel();
vm.FirstName = "";

var results = vm.GetValidationResults(nameof(ContactEditorViewModel.FirstName));
var hasErrors = vm.HasErrors;

Available validation kinds:

  • ValidationKind.Error
  • ValidationKind.Warning
  • ValidationKind.Information
  • ValidationKind.Success

Included validators:

  • NotNull
  • NotEmpty
  • ValidEmail
  • ValidUrl
  • ValidRange
  • AreDistinct

Collections

For collection properties, use GetCollection<T>() and RegisterCollectionProperty(...).

using System;
using System.Collections.ObjectModel;
using devoft.ClientModel;
using devoft.ClientModel.Validation;

public sealed class GroupViewModel : ViewModelBase<GroupViewModel>
{
    static GroupViewModel()
    {
        RegisterCollectionProperty(x => x.Members)
            .Validate((vm, items) => items.Count > 3, ValidationKind.Error, "No more than 3 members are allowed")
            .OnChanged(items => Console.WriteLine($"Members: {items.Count}"))
            .EnableRecording();
    }

    public ObservableCollection<string> Members
        => GetCollection<ObservableCollection<string>>();
}

Commands

Commands can be registered by name and are exposed through the dynamic Commands property.

public sealed class SaveViewModel : ViewModelBase<SaveViewModel>
{
    public SaveViewModel()
    {
        RegisterCommand(
            "Save",
            execute: _ => Saved = true,
            canExecuteCondition: (_, _) => !HasErrors);
    }

    public bool Saved
    {
        get => GetValue<bool>();
        set => SetValue(value);
    }
}

You can also execute a command explicitly with ExecuteCommand("Save", null) and subscribe to CommandExecuted.

Scope-based undo and redo

Properties or collections must enable recording with EnableRecording() to participate in undo/redo.

public sealed class PersonViewModel : ViewModelBase<PersonViewModel>
{
    static PersonViewModel()
    {
        RegisterProperty(x => x.Name).EnableRecording();
    }

    public string Name
    {
        get => GetValue<string>();
        set => SetValue(value);
    }
}

var vm = new PersonViewModel();

await vm.BeginScope(_ =>
{
    vm.Name = "Alice";
}).StartAsync();

await vm.BeginScope(_ =>
{
    vm.Name = "Bob";
}).StartAsync();

await vm.Undo();
await vm.Redo();

Async initialization

ViewModelBase<TViewModel> exposes InitializeAsync(IDispatcher dispatcher = null) for initialization once the UI is ready.

UI integration

The library implements:

  • INotifyPropertyChanged
  • INotifyDataErrorInfo
  • automatic command invalidation when properties change

This makes it suitable for WPF, WinUI, UWP, Xamarin, and other XAML-based MVVM environments.

Main API

  • ViewModelBase<TViewModel>
  • RegisterProperty(...)
  • RegisterCollectionProperty(...)
  • GetValue<T>()
  • SetValue<T>(...)
  • GetCollection<T>()
  • GetValidationResults(...)
  • ClearValidationResults()
  • RegisterCommand(...)
  • ExecuteCommand(...)
  • BeginScope(...)
  • Undo()
  • Redo()

Reference project

The repository includes tests in devoft.ClientModel.Test that also serve as real usage examples for validation, dependencies, collections, and undo/redo.

License

MIT

Product Compatible and additional computed target framework versions.
.NET 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
3.0.2 105 5/21/2026
2.1.2 820 6/11/2020
2.1.1 623 6/10/2020
2.1.0 599 6/9/2020
1.1.0 790 7/1/2019
1.0.19 1,038 9/21/2018
1.0.18 963 9/10/2018