< Summary

Information
Class: MyNet.Observable.Behaviors.ValidationBehavior<T>
Assembly: MyNet.Observable
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Behaviors/ValidationBehavior.cs
Tag: 323_28699572109
Line coverage
75%
Covered lines: 63
Uncovered lines: 20
Coverable lines: 83
Total lines: 245
Line coverage: 75.9%
Branch coverage
59%
Covered branches: 31
Total branches: 52
Branch coverage: 59.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_HasErrors()100%11100%
get_Errors()100%22100%
GetErrors(...)0%4260%
Validate()50%4475%
ValidateProperty(...)50%4477.77%
ResetValidation()0%4260%
OnPropertyChanged(...)62.5%9875%
CreateValidationContext(...)100%22100%
ApplyValidationResult(...)90%101093.33%
ApplyPropertyValidationResult(...)100%66100%
RaiseValidationStateChanged()100%11100%
RaiseErrorsChanged(...)50%22100%
DisposeManagedResources()100%11100%
GetDependents(...)100%22100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Behaviors/ValidationBehavior.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="ValidationBehavior.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections;
 9using System.Collections.Generic;
 10using System.ComponentModel;
 11using System.Diagnostics.CodeAnalysis;
 12using System.Linq;
 13using FluentValidation;
 14using FluentValidation.Internal;
 15using FluentValidation.Results;
 16using MyNet.Metadata;
 17using MyNet.Observable.Behaviors.Metadata.Features;
 18
 19namespace MyNet.Observable.Behaviors;
 20
 21/// <summary>
 22/// Provides validation support for an ObservableObject.
 23/// </summary>
 8124public sealed class ValidationBehavior<T>(T owner, IValidator validator) : SuspendableBehavior<T>(owner), IPropertyChang
 25    where T : ObservableObject
 26{
 8127    private readonly Dictionary<string, List<string>> _errors = [];
 28
 29    #region INotifyDataErrorInfo
 30
 31    /// <inheritdoc/>
 3932    public bool HasErrors => _errors.Count > 0;
 33
 34    /// <summary>
 35    /// Gets all validation errors for the object. This property returns an enumerable collection of all validation erro
 36    /// </summary>
 37    [SuppressMessage("Naming", "CA1721:Property names should not match get methods", Justification = "Conforms to INotif
 1838    public IReadOnlyCollection<string> Errors => [.. _errors.Values.SelectMany(x => x).Distinct()];
 39
 40    /// <inheritdoc />
 41    public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
 42
 43    /// <inheritdoc />
 44    public IEnumerable GetErrors(string? propertyName)
 045        => string.IsNullOrWhiteSpace(propertyName)
 046            ? _errors.SelectMany(x => x.Value)
 047            : (IEnumerable)(_errors.TryGetValue(propertyName, out var list)
 048                ? list
 049                : []);
 50
 51    #endregion
 52
 53    #region Validation
 54
 55    /// <inheritdoc/>
 56    public bool Validate()
 57    {
 3058        if (IsDisposed)
 059            return false;
 60
 3061        if (IsSuspended)
 062            return true;
 63
 3064        var context = CreateValidationContext();
 65
 3066        var result = validator.Validate(context);
 67
 3068        ApplyValidationResult(result);
 69
 3070        return !HasErrors;
 71    }
 72
 73    /// <inheritdoc/>
 74    public void ValidateProperty(string propertyName)
 75    {
 30976        if (IsDisposed)
 077            return;
 78
 30979        if (IsSuspended)
 080            return;
 81
 30982        var selector = new MemberNameValidatorSelector([propertyName]);
 30983        var context = CreateValidationContext(selector);
 30984        var result = validator.Validate(context);
 85
 30986        ApplyPropertyValidationResult(propertyName, result);
 30987    }
 88
 89    /// <summary>
 90    /// Resets the validation state of the object by clearing all validation errors. This method clears the internal dic
 91    /// </summary>
 92    public void ResetValidation()
 93    {
 094        if (_errors.Count == 0)
 095            return;
 96
 097        var properties = _errors.Keys.ToArray();
 98
 099        _errors.Clear();
 100
 0101        RaiseValidationStateChanged();
 102
 0103        foreach (var property in properties)
 104        {
 0105            ErrorsChanged?.Invoke(Owner, new(property));
 106        }
 0107    }
 108
 109    #endregion
 110
 111    #region Property behavior
 112
 113    /// <inheritdoc />
 114    public void OnPropertyChanged(PropertyMutationContext context)
 115    {
 279116        if (IsDisposed || IsSuspended)
 0117            return;
 118
 279119        if (string.IsNullOrWhiteSpace(context.PropertyName))
 0120            return;
 121
 279122        ValidateProperty(context.PropertyName);
 123
 570124        foreach (var dependent in GetDependents(context.PropertyName))
 125        {
 6126            ValidateProperty(dependent);
 127        }
 279128    }
 129
 130    #endregion
 131
 132    #region Internal validation
 133
 134    /// <summary>
 135    /// Creates a validation context for the owner object. This method creates a validation context that is used when pe
 136    /// </summary>
 137    /// <param name="selector">The validator selector to use, or null to use the default selector.</param>
 138    /// <returns>The validation context for the owner object.</returns>
 339139    private ValidationContext<T> CreateValidationContext(IValidatorSelector? selector = null) => new(Owner, new(), selec
 140
 141    /// <summary>
 142    /// Applies the validation result for the entire object. This method takes the validation result for the entire obje
 143    /// </summary>
 144    /// <param name="result">The validation result for the entire object.</param>
 145    private void ApplyValidationResult(ValidationResult result)
 146    {
 30147        var previousProperties = _errors.Keys.ToArray();
 148
 30149        _errors.Clear();
 150
 96151        foreach (var group in result.Errors.GroupBy(x => x.PropertyName))
 152        {
 18153            _errors[group.Key] =
 18154            [
 18155                .. group
 18156                    .Select(x => x.ErrorMessage)
 18157                    .Distinct()
 18158            ];
 159
 18160            RaiseErrorsChanged(group.Key);
 161        }
 162
 78163        foreach (var property in previousProperties)
 164        {
 9165            if (!_errors.ContainsKey(property))
 0166                RaiseErrorsChanged(property);
 167        }
 168
 30169        RaiseValidationStateChanged();
 30170    }
 171
 172    /// <summary>
 173    /// Applies the validation result for a specific property. This method takes the validation result for a specific pr
 174    /// </summary>
 175    /// <param name="propertyName">The name of the property for which the validation result is being applied.</param>
 176    /// <param name="result">The validation result for the specified property.</param>
 177    private void ApplyPropertyValidationResult(string propertyName, ValidationResult result)
 178    {
 309179        var errors =
 309180            result.Errors
 309181                .Where(x => x.PropertyName == propertyName)
 309182                .Select(x => x.ErrorMessage)
 309183                .Distinct()
 309184                .ToList();
 185
 309186        if (errors.Count == 0)
 187        {
 288188            if (_errors.Remove(propertyName))
 189            {
 6190                RaiseValidationStateChanged();
 6191                RaiseErrorsChanged(propertyName);
 192            }
 193
 288194            return;
 195        }
 196
 21197        _errors[propertyName] = errors;
 198
 21199        RaiseValidationStateChanged();
 21200        RaiseErrorsChanged(propertyName);
 21201    }
 202
 203    #endregion
 204
 205    #region Notifications
 206
 207    /// <summary>
 208    /// Raises notifications for changes in the validation state. This method is called whenever there is a change in th
 209    /// </summary>
 210    private void RaiseValidationStateChanged()
 211    {
 57212        Owner.NotifyPropertyChanged(nameof(HasErrors));
 57213        Owner.NotifyPropertyChanged(nameof(Errors));
 57214    }
 215
 216    /// <summary>
 217    /// Raises the ErrorsChanged event for a specific property. This method is called whenever the validation errors for
 218    /// </summary>
 219    /// <param name="propertyName">The name of the property whose validation errors have changed.</param>
 45220    private void RaiseErrorsChanged(string propertyName) => ErrorsChanged?.Invoke(Owner, new(propertyName));
 221
 222    #endregion
 223
 224    /// <inheritdoc/>
 225    protected override void DisposeManagedResources()
 226    {
 3227        _errors.Clear();
 228
 3229        base.DisposeManagedResources();
 3230    }
 231
 232    #region Helpers
 233
 234    /// <summary>
 235    /// Gets the dependent properties for a given property. This method retrieves the dependent properties for a specifi
 236    /// </summary>
 237    /// <param name="propertyName">The name of the property whose dependent properties are to be retrieved.</param>
 238    /// <returns>An array of property names that are dependent on the specified property.</returns>
 279239    private string[] GetDependents(string propertyName) => MetadataRegistry.Get(Owner.GetType()).GetProperty(propertyNam
 279240        ? [.. feature.Dependents]
 279241        : [];
 242
 243    #endregion
 244}
 245