NuExt.Minimal.Mvvm.SourceGenerator 0.8.0

Prefix Reserved
dotnet add package NuExt.Minimal.Mvvm.SourceGenerator --version 0.8.0
                    
NuGet\Install-Package NuExt.Minimal.Mvvm.SourceGenerator -Version 0.8.0
                    
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="NuExt.Minimal.Mvvm.SourceGenerator" Version="0.8.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NuExt.Minimal.Mvvm.SourceGenerator" Version="0.8.0" />
                    
Directory.Packages.props
<PackageReference Include="NuExt.Minimal.Mvvm.SourceGenerator">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
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 NuExt.Minimal.Mvvm.SourceGenerator --version 0.8.0
                    
#r "nuget: NuExt.Minimal.Mvvm.SourceGenerator, 0.8.0"
                    
#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 NuExt.Minimal.Mvvm.SourceGenerator@0.8.0
                    
#: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=NuExt.Minimal.Mvvm.SourceGenerator&version=0.8.0
                    
Install as a Cake Addin
#tool nuget:?package=NuExt.Minimal.Mvvm.SourceGenerator&version=0.8.0
                    
Install as a Cake Tool

NuExt.Minimal.Mvvm.SourceGenerator

NuExt.Minimal.Mvvm.SourceGenerator is a Roslyn source generator for the lightweight MVVM framework NuExt.Minimal.Mvvm. It emits boilerplate for properties, commands, validation, and localization at compile time, so you can focus on app logic.

NuGet Ask DeepWiki Build License Downloads

Requirements

  • Consumer target frameworks: .NET Standard 2.0/2.1, .NET 8/9/10, and .NET Framework 4.6.2+
  • Language: C# 12+
  • Core package: NuExt.Minimal.Mvvm or a compatible UI integration package

Features

  • Properties and commands

    • Generates observable properties from backing fields and partial properties.
    • Generates command properties from synchronous and asynchronous methods.
    • Emits cached event arguments for allocation-free notification paths.
    • Initializes generated backing fields for non-nullable partial string properties with string.Empty.
  • Validation (INotifyDataErrorInfo)

    • Uses thread-safe, copy-on-write error storage.
    • Supports property-level and entity-level validation scopes.
    • Provides synchronous and asynchronous validation helpers.
  • WPF command requery

    • [UseCommandManager] connects generated command properties to CommandManager.RequerySuggested.
  • Localization

    • [Localize] generates localization properties from JSON files provided through AdditionalFiles.
  • Generated member customization

    • [AlsoNotify] raises notifications for related properties.
    • [CustomAttribute] applies attributes to generated members.

Attributes

  • [Notify]: Generates a property from a field or partial property, or a command property from a method.
    • Options: PropertyName, CallbackName, PreferCallbackWithParameter, Getter, Setter.
  • [AlsoNotify]: Notifies additional properties when the annotated property changes.
  • [CustomAttribute]: Applies an attribute to a generated member.
  • [UseCommandManager]: Enables automatic CanExecute reevaluation for the generated command property in WPF.
  • [NotifyDataErrorInfo]: Generates validation infrastructure for INotifyDataErrorInfo.
  • [Localize]: Generates localization members from a JSON file provided through MSBuild AdditionalFiles.

Install

dotnet add package NuExt.Minimal.Mvvm.SourceGenerator
# and one of:
dotnet add package NuExt.Minimal.Mvvm
# or
dotnet add package NuExt.Minimal.Mvvm.Wpf
# or
dotnet add package NuExt.Minimal.Mvvm.MahApps.Metro

Quick Start

using Minimal.Mvvm;
using System.Threading.Tasks;

public partial class PersonModel : BindableBase
{
    [Notify, AlsoNotify(nameof(FullName))]
    private string? _name;

    [Notify, AlsoNotify(nameof(FullName))]
    private string? _surname;

    public string FullName => $"{_surname} {_name}";

    public PersonModel()
    {
        ShowInfoCommand = new AsyncCommand<string?>(ShowInfoAsync);
    }

    [Notify("ShowInfoCommand"), UseCommandManager]
    [CustomAttribute("System.Text.Json.Serialization.JsonIgnore")]
    private async Task ShowInfoAsync(string? text)
    {
        await Task.Delay(100);
    }
}

[Notify] generates the ShowInfoCommand property. The command instance is created explicitly in the constructor, keeping command configuration deterministic and visible.

What you get (conceptually):

  • Name/Surname properties with PropertyChanged and FullName notifications.
  • IAsyncCommand<string?>? ShowInfoCommand property with WPF requery wiring (via [UseCommandManager]).
  • Cached PropertyChangedEventArgs etc. for allocation-free notifications.

Validation semantics (concise)

  • Scopes: null/"" = entity‑level; "PropertyName" = property‑level.
  • Clearing:
    • ClearErrors(null) or ClearErrors("") → clears entity‑level only.
    • ClearErrors(nameof(Property)) → clears that property only.
    • ClearAllErrors() → full reset (raises ErrorsChanged per affected scope + updates HasErrors).
  • Updates: SetError, SetErrors (merge), ReplaceErrors (replace), RemoveError.
    • Copy‑on‑write lists + CAS; early‑return on no‑op (no redundant ErrorsChanged).

Threading: notifications are marshaled to UI via a lazily captured SynchronizationContext.

Async validation helpers (SetValidationTask / CancelValidationTask)

When a class is annotated with [NotifyDataErrorInfo], the generator exposes helpers to manage asynchronous per‑property validation tasks:

  • SetValidationTask(Task task, CancellationTokenSource cts, string propertyName)
    Associates the task and its CancellationTokenSource with propertyName, cancelling and disposing the previous token source for that scope before replacing the tracked task.
  • CancelValidationTask(string propertyName = null)
    Cancels and disposes the tracked CancellationTokenSource for propertyName; with null, cancels all tracked validation operations.
  • CancelAllValidationTasks()
    Convenience wrapper to cancel every tracked task (use in teardown).
  • HasErrorsFor(scope) / SetError / SetErrors / ReplaceErrors / RemoveError
    Same semantics as in the synchronous flow (entity = null/"").

Usage pattern (per property):

[NotifyDataErrorInfo]
public partial class LoginViewModel : ViewModelBase
{
    private readonly CancellationTokenSource _cts = new();

    private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(UserName))
        {
            // Cancel previous async validation for this property
            CancelValidationTask(nameof(UserName));

            // Run sync validation first (one notification, via ReplaceErrors)
            ValidateUserNameSync();

            // Start async validation with a linked CTS
            var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
            var task = ValidateUserNameAsync(UserName, cts.Token);

            // Observe faults to avoid UnobservedTaskException (no UI marshal)
            task.ContinueWith(
                t => { _ = t.Exception; },
                CancellationToken.None,
                TaskContinuationOptions.OnlyOnFaulted | 
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default);

            // Publish/track the task for this property
            SetValidationTask(task, cts, nameof(UserName));
        }
    }

    private async Task ValidateUserNameAsync(string userName, CancellationToken ct)
    {
        try
        {
            // Debounce
            await Task.Delay(250, ct);

            // If sync errors still exist — skip async
            if (HasErrorsFor(nameof(UserName)))
                return;

            // Ensure value is still current
            if (!string.Equals(UserName, userName, StringComparison.Ordinal))
                return;

            // Do the actual async check (no UI context capture)
            var locked = await _auth.IsUserLockedAsync(userName, ct).ConfigureAwait(false);

            if (locked) SetError("This user is locked.", nameof(UserName));
            else        ClearErrors(nameof(UserName));
        }
        catch (OperationCanceledException) { /* ignore */ }
    }

    // Teardown: cancel all pending validations
    protected override async Task UninitializeAsyncCore(CancellationToken ct)
    {
        CancelAllValidationTasks();
        await base.UninitializeAsyncCore(ct);
    }
}
Notes
  • Always cancel the previous task for the same property before starting a new one: CancelValidationTask(nameof(Property)).
  • Prefer a linked CancellationTokenSource tied to the VM lifetime.
  • Mark faults as observed (via ContinueWith(OnlyOnFaulted|ExecuteSynchronously)) or catch inside the async validator.
  • Use ConfigureAwait(false) inside validators; the generator marshals notifications to the UI via a lazily captured SynchronizationContext.
  • Keep CanExecute/UI logic driven by property‑level errors (e.g., HasErrorsFor(nameof(UserName))), while entity‑level banners are cleared at the start of a new attempt (ClearErrors("")).

WPF notes (displaying errors)

  • Field‑level errors bind with Validation.HasError and delayed index access to (Validation.Errors)[0] (avoid warnings).
  • Entity‑level errors can be shown by a hidden binding host (bind to VM with ValidatesOnNotifyDataErrors=True, read its Validation.Errors).

See the WpfAppSample in the repository for a minimal, production‑style pattern (entity banner + field messages).

UseCommandManagerAttribute

When applied to a command field or method, enables automatic CanExecute reevaluation for the generated command property by subscribing to the WPF CommandManager.RequerySuggested event. This attribute is used together with [Notify] for commands that should react to global UI state changes.

using Minimal.Mvvm;

public partial class MyViewModel : ViewModelBase
{
    [Notify, UseCommandManager]
    private IRelayCommand? _saveCommand;

    public MyViewModel()
    {
        SaveCommand = new RelayCommand(Save, CanSave);
    }

    private void Save()
    {
    }

    private bool CanSave()
    {
        return true;
    }
}

Localization (AdditionalFiles)

Example csproj snippet for [Localize("local.en.json")]:

<ItemGroup>
  <AdditionalFiles Include="Resources\local.en.json" />
</ItemGroup>

The generator will create a static class with string properties populated from that JSON. At runtime, localization can also be loaded from the specified file (see samples).

Ecosystem

Contributing

Issues and PRs are welcome. Keep changes minimal and performance-conscious.

License

MIT. See LICENSE.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

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
0.8.0 0 10/23/2026
0.7.4 104 10/2/2026
0.7.3 174 6/29/2026
0.7.2 156 3/16/2026
0.7.1 143 2/23/2026
0.7.0 145 2/13/2026
0.6.0 144 1/11/2026
0.5.2 336 12/15/2025
0.5.1 271 12/14/2025
0.4.1 484 12/10/2025
0.4.0 211 12/5/2025
0.3.2 199 1/26/2025
0.3.1 191 1/22/2025
0.3.0 186 1/21/2025
Loading failed