Darp.CommunityToolkit.MvvmValidator 0.1.1

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

Darp.CommunityToolkit.MvvmValidator

NuGet Downloads License .NET

DataAnnotations validation for CommunityToolkit.Mvvm view models that works with trimming and Native AOT. A source generator writes the validation code, so nothing is discovered with reflection at runtime.

dotnet add package Darp.CommunityToolkit.MvvmValidator

Requirements:

  • .NET 10
  • View models and their containing types are partial. A code fix adds missing modifiers.
  • Attributes that only target properties, like [Compare], need [property: ...] on [ObservableProperty] fields.

Usage

Derive from ValidatingObservableObject instead of ObservableValidator and make the class partial:

using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
using Darp.MvvmValidator;

public partial class SignUpViewModel : ValidatingObservableObject
{
    public SignUpViewModel() => ValidateAllProperties();

    [ObservableProperty]
    [Required]
    [StringLength(20, MinimumLength = 3)]
    [Display(Name = "User name")]
    public partial string? UserName { get; set; }

    [ObservableProperty]
    [Required]
    public partial string? Password { get; set; }

    [ObservableProperty]
    [Compare(nameof(Password))]
    public partial string? ConfirmPassword { get; set; }

    [CustomValidation(typeof(SignUpViewModel), nameof(ValidateInvite))]
    public string? InviteCode
    {
        get;
        set => SetProperty(ref field, value);
    }

    public static ValidationResult? ValidateInvite(string? code, ValidationContext context) =>
        code is null or "WELCOME" ? ValidationResult.Success : new ValidationResult("Unknown invite code");
}

<details> <summary>Generated code</summary>

// <auto-generated/>
#nullable enable
partial class SignUpViewModel
{
    private static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute __dmv_rule0_0 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
    private static readonly global::System.ComponentModel.DataAnnotations.StringLengthAttribute __dmv_rule0_1 = new global::System.ComponentModel.DataAnnotations.StringLengthAttribute(((int)(20))) { MinimumLength = ((int)(3)) };
    private static readonly global::System.ComponentModel.DataAnnotations.DisplayAttribute __dmv_display0 = new global::System.ComponentModel.DataAnnotations.DisplayAttribute() { Name = "User name" };
    private static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute __dmv_rule1_0 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();

    /// <inheritdoc />
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    protected override bool TryValidatePropertyCore(string propertyName, global::System.Collections.Generic.ICollection<global::System.ComponentModel.DataAnnotations.ValidationResult> errors)
    {
        switch (propertyName)
        {
            case "UserName":
                __dmv_validate0(this.UserName, errors);
                return true;
            case "Password":
                __dmv_validate1(this.Password, errors);
                return true;
            case "ConfirmPassword":
                __dmv_validate2(this.ConfirmPassword, errors);
                return true;
            case "InviteCode":
                __dmv_validate3(this.InviteCode, errors);
                return true;
            default:
                return base.TryValidatePropertyCore(propertyName, errors);
        }
    }

    /// <inheritdoc />
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    protected override void ValidateAllPropertiesCore()
    {
        base.ValidateAllPropertiesCore();
        ValidateProperty("UserName");
        ValidateProperty("Password");
        ValidateProperty("ConfirmPassword");
        ValidateProperty("InviteCode");
    }

    /// <inheritdoc />
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    protected override void ValidateDependentPropertiesCore(string propertyName)
    {
        switch (propertyName)
        {
            case "Password":
                RevalidateIfValidated("ConfirmPassword");
                break;
        }
        base.ValidateDependentPropertiesCore(propertyName);
    }

    private void __dmv_validate0(string? value, global::System.Collections.Generic.ICollection<global::System.ComponentModel.DataAnnotations.ValidationResult> errors)
    {
        global::System.ComponentModel.DataAnnotations.ValidationContext context = CreateValidationContext("UserName", __dmv_display0.GetName());
        global::System.ComponentModel.DataAnnotations.ValidationResult? result;
        result = __dmv_rule0_0.GetValidationResult(value, context);
        if (result is not null)
        {
            errors.Add(result);
            return;
        }
        result = __dmv_rule0_1.GetValidationResult(value, context);
        if (result is not null)
        {
            errors.Add(result);
        }
    }

    private void __dmv_validate1(string? value, global::System.Collections.Generic.ICollection<global::System.ComponentModel.DataAnnotations.ValidationResult> errors)
    {
        global::System.ComponentModel.DataAnnotations.ValidationContext context = CreateValidationContext("Password", null);
        global::System.ComponentModel.DataAnnotations.ValidationResult? result;
        result = __dmv_rule1_0.GetValidationResult(value, context);
        if (result is not null)
        {
            errors.Add(result);
            return;
        }
    }

    private void __dmv_validate2(string? value, global::System.Collections.Generic.ICollection<global::System.ComponentModel.DataAnnotations.ValidationResult> errors)
    {
        global::System.ComponentModel.DataAnnotations.ValidationContext context = CreateValidationContext("ConfirmPassword", null);
        global::System.ComponentModel.DataAnnotations.ValidationResult? result;
        result = ValidateCompare(value, this.Password, "Password", null, context);
        if (result is not null)
        {
            errors.Add(result);
        }
    }

    private void __dmv_validate3(string? value, global::System.Collections.Generic.ICollection<global::System.ComponentModel.DataAnnotations.ValidationResult> errors)
    {
        global::System.ComponentModel.DataAnnotations.ValidationContext context = CreateValidationContext("InviteCode", null);
        global::System.ComponentModel.DataAnnotations.ValidationResult? result;
        result = CompleteCustomValidationResult(global::SignUpViewModel.ValidateInvite(value, context), null, context);
        if (result is not null)
        {
            errors.Add(result);
        }
    }
}

</details>

Behavior

  • Properties with validation attributes are validated whenever they change, before PropertyChanged reaches subscribers. [NotifyDataErrorInfo] and SetProperty(..., validate: true) are not needed.
  • Results match Validator.TryValidateProperty, including [CustomValidation], [Compare] and resource-based messages.
  • When a [Compare] target changes, the comparing property is validated again, unless it is still untouched.
  • ValidateAllProperties and ClearErrors work as in ObservableValidator. SetErrors adds results of asynchronous or server-side validation.

Diagnostics

The package turns off the toolkit's MVVMTK0006, which only accepts ObservableValidator, and reports these instead:

Id Reported when
DMV0001 A validated type or a containing type is not partial, or is file-local. A code fix adds partial
DMV0002 A [CustomValidation] method is missing, not public static, or has the wrong signature
DMV0003 An [ObservableProperty] member has validation attributes, but its type is no validating view model
DMV0004 A [Compare] target is not a readable, accessible property
DMV0005 An attribute or property cannot be expressed in generated code, e.g. a ref struct or dynamic property

[Compare], [MinLength], [MaxLength] and [Length] make the trim analyzer and the trimmer warn where they are applied. The package suppresses these warnings when the generated code avoids the reflection they warn about. For properties with hand-written accessors or a field initializer, the warnings stay, because suppressing them would also hide warnings in that code.

Development

  • Format with CSharpier: dotnet tool restore, then dotnet csharpier format .
  • Runtime tests also run as a Native AOT executable in CI, which proves the generated code is trim safe.
  • Generator tests snapshot the generated code with Verify.
  • Releases come from Conventional Commits via release-please. Every pull request publishes a preview package to https://nuget.pkg.github.com/rosslight/index.json.
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 (2)

Showing the top 2 NuGet packages that depend on Darp.CommunityToolkit.MvvmValidator:

Package Downloads
Darp.Utils.Dialog

A very simple dialog service targeting avalonia

Darp.Utils.Dialog.FluentAvalonia

Implementation of Darp.Utils.Dialog compatible with Avalonia

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.1 53 11/9/2026
0.1.0 55 11/9/2026