devoft.ClientModel
3.0.2
dotnet add package devoft.ClientModel --version 3.0.2
NuGet\Install-Package devoft.ClientModel -Version 3.0.2
<PackageReference Include="devoft.ClientModel" Version="3.0.2" />
<PackageVersion Include="devoft.ClientModel" Version="3.0.2" />
<PackageReference Include="devoft.ClientModel" />
paket add devoft.ClientModel --version 3.0.2
#r "nuget: devoft.ClientModel, 3.0.2"
#:package devoft.ClientModel@3.0.2
#addin nuget:?package=devoft.ClientModel&version=3.0.2
#tool nuget:?package=devoft.ClientModel&version=3.0.2
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
GetValueandSetValue. - Observable collections through
GetCollection. - Fluent property configuration with
RegisterPropertyandRegisterCollectionProperty. - Validation results with
Error,Warning, andInformationstates. - Chained coercion before assigning values.
- Property dependency propagation through
DependOn. - Dynamically registered commands with
RegisterCommand. - Scope-based editing with
BeginScope,Undo(), andRedo(). - Integration with
INotifyPropertyChangedandINotifyDataErrorInfo.
Installation
dotnet add package devoft.ClientModel
Requirements
- .NET 10
devoft.Coreas 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.ErrorValidationKind.WarningValidationKind.InformationValidationKind.Success
Included validators:
NotNullNotEmptyValidEmailValidUrlValidRangeAreDistinct
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:
INotifyPropertyChangedINotifyDataErrorInfo- 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 | Versions 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. |
-
net10.0
- devoft.Core (>= 3.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.