MVVMFluent.ValidationExtensions
0.1.2
dotnet add package MVVMFluent.ValidationExtensions --version 0.1.2
NuGet\Install-Package MVVMFluent.ValidationExtensions -Version 0.1.2
<PackageReference Include="MVVMFluent.ValidationExtensions" Version="0.1.2" />
<PackageVersion Include="MVVMFluent.ValidationExtensions" Version="0.1.2" />
<PackageReference Include="MVVMFluent.ValidationExtensions" />
paket add MVVMFluent.ValidationExtensions --version 0.1.2
#r "nuget: MVVMFluent.ValidationExtensions, 0.1.2"
#:package MVVMFluent.ValidationExtensions@0.1.2
#addin nuget:?package=MVVMFluent.ValidationExtensions&version=0.1.2
#tool nuget:?package=MVVMFluent.ValidationExtensions&version=0.1.2
MVVMFluent
MVVMFluent is a lightweight .NET library that helps you build MVVM view models with fluent property setters, declarative command builders, and optional validation support. The library ships as a compiled NuGet package (targeting netstandard2.0) with a clean interface-based API that separates implementation details from the public contract.
Features
- Fluent property setters — Compose
Changing,Changed, andNotifybehaviors with a fluent API before committing values back to the backing store. - Interface-based design — Work with
IFluentSetter<T>andIValidationFluentSetter<T>interfaces instead of concrete implementations, making your code more testable and maintainable. - Command builders — Generate
IFluentCommandandIFluentCommand<T>instances directly from your view model, keeping command wiring andCanExecutelogic close to the properties that depend on them. - Async command support — Use
IAsyncFluentCommand/IAsyncFluentCommand<T>to handle cancellable asynchronous work, expose an auto-wiredCancelCommand, and surface progress updates throughINotifyPropertyChanged. - Validation pipeline — Opt-in to
ValidationViewModelBaseto compose validation rules (such asHasValueor customValidatecallbacks) that keep theErrorscollection andHasErrorsflag in sync with your UI. - Extended validation helpers — Reference
MVVMFluent.ValidationExtensionsfor ready-to-use rules likeIsEmail,IsUrl,HasLengthBetween, and date or range guards. - Roslyn analyzer — Automatically included analyzer that enforces proper usage of
.Set()at the end of fluent setter chains to prevent subtle bugs. - Deterministic cleanup — View model, command, and builder implementations implement
IDisposablewhere appropriate to avoid self-referencing leaks when commands are re-evaluated or builders are cached.
Installation
Install the package from NuGet just like any other binary dependency:
dotnet add package MVVMFluent
The package embeds the project README and license so IDE package managers surface the latest documentation. A Roslyn analyzer is automatically included to help enforce proper usage patterns.
Architecture
The library follows a clean interface-based architecture:
- Interfaces (
MVVMFluent.Interfaces) - Public contracts for fluent setters, commands, and validation - Commands (
MVVMFluent.Commands) - Internal implementations ofFluentCommandandAsyncFluentCommand - Builders (
MVVMFluent.Builders) - Internal implementations of fluent setter builders - Validation (
MVVMFluent.Validation) - Validation-specific builders and rules
When you use When() or Do() methods, they return interfaces (IFluentSetter<T>, IFluentCommand, etc.) rather than concrete types, allowing for better testability and encapsulation.
Usage
Fluent property setters
ViewModelBase provides the When helper to build rich property setters without repetitive boilerplate. The method returns an IFluentSetter<T> interface:
public class MyViewModel : ViewModelBase
{
public string? Name
{
get => Get<string?>();
set => When(value)
.Changing(newValue => Console.WriteLine($"Changing to {newValue}"))
.Changed(newValue => Console.WriteLine($"Changed to {newValue}"))
.Notify(SaveCommand)
.Notify(nameof(FullName))
.Set();
}
public string FullName => $"{Name} Surname";
}
The builder caches itself per-property, so subsequent setter invocations reuse the configured pipeline while updating the pending value.
Commands
Create commands directly from your view model via the Do helpers. Commands are cached per property and automatically expose RaiseCanExecuteChanged. All command methods return interfaces (IFluentCommand, IFluentCommand<T>, IAsyncFluentCommand, or IAsyncFluentCommand<T>):
public class MyViewModel : ViewModelBase
{
public IFluentCommand SaveCommand => Do(Save)
.If(() => !string.IsNullOrEmpty(Name));
private void Save()
{
// Persist data
}
}
For parameterized scenarios, use IFluentCommand<T> by calling Do<T>:
public IFluentCommand<string> HelpCommand => Do<string>(ShowDialog);
private void ShowDialog(string? input)
{
MessageBox.Show(input);
}
Asynchronous commands
IAsyncFluentCommand wraps cancellable asynchronous work and keeps UI bindings informed about execution state:
public class LoaderViewModel : ViewModelBase
{
public IAsyncFluentCommand LoadCommand => Do(async token =>
{
for (var i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(100, token);
LoadCommand.ReportProgress(i + 1, 10);
}
})
.Handle(ex => Console.WriteLine($"Load failed: {ex.Message}"));
public IFluentCommand CancelLoadCommand => LoadCommand.CancelCommand;
}
Bindings can observe the IsRunning, Progress, and CancelCommand members exposed by the async command.
Validation
Derive from ValidationViewModelBase to wire validation rules directly into your setters. The When method returns an IValidationFluentSetter<T> interface. HasValue() optionally accepts a custom error message so you can surface friendly text when the bound property is empty:
public class ContactViewModel : ValidationViewModelBase
{
public string? Email
{
get => Get<string?>();
set => When(value)
.HasValue("Email is required")
.Validate(address => address?.Contains('@') == true, "Email must contain '@'")
.Notify(SaveCommand)
.Set();
}
public bool CanSave => !HasErrors;
public IFluentCommand SaveCommand => Do(Save).If(() => CanSave);
private void Save()
{
// Save contact information
}
}
Validation errors propagate to the Errors collection, enabling XAML data binding to display aggregated messages.
Extended validation extensions
Install the optional MVVMFluent.ValidationExtensions package to get a catalog of reusable validation helpers that extend IValidationFluentSetter<T>:
dotnet add package MVVMFluent.ValidationExtensions
The extensions provide expressive guards for common scenarios with sensible default messages and optional overrides:
public class RegistrationViewModel : ValidationViewModelBase
{
public string? Email
{
get => Get<string?>();
set => When(value)
.HasValue("Email is required")
.IsEmail()
.HasLengthBetween(5, 100)
.Notify(RegisterCommand)
.Set();
}
public int? Age
{
get => Get<int?>();
set => When(value)
.IsMinimumAge(18, "You must be at least 18 years old.")
.IsAgeBetween(18, 120)
.Notify(RegisterCommand)
.Set();
}
public DateTime? AppointmentDate
{
get => Get<DateTime?>();
set => When(value)
.IsDateInFuture("Please choose a future date.")
.IsDateBetween(DateTime.Today, DateTime.Today.AddYears(1))
.Set();
}
public IFluentCommand RegisterCommand => Do(Register).IfValid(nameof(Email), nameof(Age));
private void Register()
{
// Perform registration
}
}
Additional helpers cover URL validation (IsUrl), numeric comparisons (IsGreaterThan, IsInRange), and pattern-based checks (MatchesPattern).
Command validation helpers
CommandValidationExtensions provides IfValid helpers for both synchronous and asynchronous commands so they only execute when
specified properties are error-free:
public class ContactViewModel : ValidationViewModelBase
{
public string? Email
{
get => Get<string?>();
set => When(value)
.HasValue("Email is required")
.Validate(address => address?.Contains('@') == true, "Email must contain '@'")
.Notify(SaveCommand)
.Set();
}
public IFluentCommand SaveCommand => Do(Save).IfValid(nameof(Email));
private void Save()
{
// Save contact information
}
}
The extensions enforce that the owning view model derives from ValidationViewModelBase and throw meaningful exceptions when the
validated properties are missing or contain errors.
Roslyn Analyzer
MVVMFluent includes a Roslyn analyzer that enforces proper usage of fluent property setters. The analyzer ensures that every property setter using When(value) ends with a call to .Set(), which is crucial for committing values to the backing field and triggering property change notifications.
MVVMFLUENT001: Fluent setter must end with Set()
Severity: Error
What it does: Detects property setters that use When(value) but don't end with .Set().
Why it matters: Without .Set(), the value isn't committed to the backing field, and property change notifications aren't triggered, leading to subtle bugs where the UI doesn't update or validation doesn't run.
Example of violation:
public string Name
{
get => Get<string>();
set => When(value).Validate(); // ? Error: Missing Set()!
}
Correct usage:
public string Name
{
get => Get<string>();
set => When(value).Validate().Set(); // ? Correct
}
Code fix: The analyzer includes an automatic code fix. Simply press Ctrl+. (or Cmd+. on Mac) when the error appears and select "Add .Set() to complete fluent setter" to automatically append .Set() to your fluent chain.
The analyzer works with both ViewModelBase and ValidationViewModelBase, and supports both expression-bodied and block-bodied setters.
API Design
MVVMFluent follows an interface-based design principle:
- View Models work with interfaces:
IFluentSetter<T>,IValidationFluentSetter<T>,IFluentCommand,IAsyncFluentCommand - Implementations are internal to the library in specific namespaces:
MVVMFluent.Commands- Command implementationsMVVMFluent.Builders- Builder implementationsMVVMFluent.Validation- Validation-specific implementations
- Extensions operate on interfaces, making them composable and testable
This separation ensures your view models depend on stable contracts rather than implementation details.
Contributing
Contributions are welcome! Feel free to open issues or pull requests to improve this library.
License
MVVMFluent is available under the MIT License. See LICENSE.txt for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- MVVMFluent (>= 0.1.2)
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 |
|---|