< Summary

Class:ReactiveUI.Validation.Components.BasePropertyValidation`2
Assembly:ReactiveUI.Validation
File(s):D:\a\1\s\src\ReactiveUI.Validation\Components\BasePropertyValidation.cs
Covered lines:33
Uncovered lines:5
Coverable lines:38
Total lines:353
Line coverage:86.8% (33 of 38)
Covered branches:7
Total branches:12
Branch coverage:58.3% (7 of 12)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
.ctor(...)-100%100%
.ctor(...)-100%100%
.ctor(...)-100%100%
get_IsValidFunc()-100%100%
GetValidationChangeObservable()-100%100%
Dispose(...)-0%0%
Activate()-80%50%
GetMessage(...)-100%100%

File(s)

D:\a\1\s\src\ReactiveUI.Validation\Components\BasePropertyValidation.cs

#LineLine coverage
 1// Copyright (c) 2020 .NET Foundation and Contributors. All rights reserved.
 2// Licensed to the .NET Foundation under one or more agreements.
 3// The .NET Foundation licenses this file to you under the MIT license.
 4// See the LICENSE file in the project root for full license information.
 5
 6using System;
 7using System.Collections.Generic;
 8using System.Diagnostics.CodeAnalysis;
 9using System.Linq;
 10using System.Linq.Expressions;
 11using System.Reactive.Disposables;
 12using System.Reactive.Linq;
 13using System.Reactive.Subjects;
 14
 15using ReactiveUI.Validation.Collections;
 16using ReactiveUI.Validation.Comparators;
 17using ReactiveUI.Validation.Components.Abstractions;
 18using ReactiveUI.Validation.Extensions;
 19using ReactiveUI.Validation.States;
 20
 21namespace ReactiveUI.Validation.Components
 22{
 23    /// <inheritdoc cref="ReactiveObject" />
 24    /// <inheritdoc cref="IDisposable" />
 25    /// <inheritdoc cref="IPropertyValidationComponent{TViewModel}" />
 26    /// <summary>
 27    /// Base class for items which are used to build a <see cref="ReactiveUI.Validation.Contexts.ValidationContext" />.
 28    /// </summary>
 29    public abstract class BasePropertyValidation<TViewModel> : ReactiveObject, IDisposable, IPropertyValidationComponent
 30    {
 31        /// <summary>
 32        /// The current valid state.
 33        /// </summary>
 34        [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed by field _dis
 35        private readonly ReplaySubject<bool> _isValidSubject = new ReplaySubject<bool>(1);
 36
 37        /// <summary>
 38        /// The list of property names this validator is referencing.
 39        /// </summary>
 40        private readonly HashSet<string> _propertyNames = new HashSet<string>();
 41
 42        /// <summary>
 43        /// The items to be disposed.
 44        /// </summary>
 45        private CompositeDisposable _disposables = new CompositeDisposable();
 46
 47        /// <summary>
 48        /// The connected observable to kick off seeing <see cref="ValidationStatusChange" />.
 49        /// </summary>
 50        private IConnectableObservable<ValidationState>? _connectedChange;
 51
 52        private bool _isConnected;
 53
 54        /// <summary>
 55        /// Our current validity state.
 56        /// </summary>
 57        private bool _isValid;
 58
 59        private ValidationText? _text;
 60
 61        /// <summary>
 62        /// Initializes a new instance of the <see cref="BasePropertyValidation{TViewModel}"/> class.
 63        /// </summary>
 64        protected BasePropertyValidation()
 65        {
 66            // subscribe to the valid subject so we can assign the validity
 67            _disposables.Add(_isValidSubject.Subscribe(v => _isValid = v));
 68        }
 69
 70        /// <inheritdoc/>
 71        public int PropertyCount => _propertyNames.Count;
 72
 73        /// <inheritdoc/>
 74        public IEnumerable<string> Properties => _propertyNames.AsEnumerable();
 75
 76        /// <inheritdoc />
 77        public bool IsValid
 78        {
 79            get
 80            {
 81                Activate();
 82                return _isValid;
 83            }
 84        }
 85
 86        /// <summary>
 87        /// Gets the public mechanism indicating that the validation state has changed.
 88        /// </summary>
 89        public IObservable<ValidationState> ValidationStatusChange
 90        {
 91            get
 92            {
 93                Activate();
 94
 95                if (_connectedChange == null)
 96                {
 97                    throw new InvalidOperationException("ConnectedChange observable has not been initialized properly.")
 98                }
 99
 100                return _connectedChange;
 101            }
 102        }
 103
 104        /// <inheritdoc/>
 105        public ValidationText? Text
 106        {
 107            get
 108            {
 109                Activate();
 110                return _text;
 111            }
 112        }
 113
 114        /// <inheritdoc/>
 115        public void Dispose()
 116        {
 117            // Dispose of unmanaged resources.
 118            Dispose(true);
 119
 120            // Suppress finalization.
 121            GC.SuppressFinalize(this);
 122        }
 123
 124        /// <inheritdoc/>
 125        public bool ContainsProperty<TProp>(Expression<Func<TViewModel, TProp>> property, bool exclusively = false)
 126        {
 127            if (property is null)
 128            {
 129                throw new ArgumentNullException(nameof(property));
 130            }
 131
 132            var propertyName = property.Body.GetPropertyPath();
 133            return ContainsPropertyName(propertyName, exclusively);
 134        }
 135
 136        /// <inheritdoc/>
 137        public bool ContainsPropertyName(string propertyName, bool exclusively = false)
 138        {
 139            return exclusively
 140                ? _propertyNames.Contains(propertyName) && _propertyNames.Count == 1
 141                : _propertyNames.Contains(propertyName);
 142        }
 143
 144        /// <summary>
 145        /// Adds a property to the list of this which this validation is associated with.
 146        /// </summary>
 147        /// <typeparam name="TProp">Any type.</typeparam>
 148        /// <param name="property">ViewModel property.</param>
 149        protected void AddProperty<TProp>(Expression<Func<TViewModel, TProp>> property)
 150        {
 151            if (property is null)
 152            {
 153                throw new ArgumentNullException(nameof(property));
 154            }
 155
 156            var propertyName = property.Body.GetPropertyPath();
 157            _propertyNames.Add(propertyName);
 158        }
 159
 160        /// <summary>
 161        /// Get the validation change observable, implemented by concrete classes.
 162        /// </summary>
 163        /// <returns>Returns the <see cref="ValidationState"/> collection.</returns>
 164        protected abstract IObservable<ValidationState> GetValidationChangeObservable();
 165
 166        /// <summary>
 167        /// Disposes of the managed resources.
 168        /// </summary>
 169        /// <param name="disposing">If its getting called by the <see cref="BasePropertyValidation{TViewModel}.Dispose()
 170        protected virtual void Dispose(bool disposing)
 171        {
 172            if (disposing)
 173            {
 174                _disposables?.Dispose();
 175            }
 176        }
 177
 178        private void Activate()
 179        {
 180            if (_isConnected)
 181            {
 182                return;
 183            }
 184
 185            _connectedChange = GetValidationChangeObservable()
 186                .Do(state =>
 187                {
 188                    _isValid = state.IsValid;
 189                    _text = state.Text;
 190                })
 191                .Replay(1);
 192
 193            _disposables.Add(_connectedChange.Connect());
 194
 195            _isConnected = true;
 196        }
 197    }
 198
 199    /// <inheritdoc />
 200    /// <summary>
 201    /// Property validator for a single view model property.
 202    /// </summary>
 203    /// <typeparam name="TViewModel"></typeparam>
 204    /// <typeparam name="TViewModelProperty"></typeparam>
 205    [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Sa
 206    public sealed class BasePropertyValidation<TViewModel, TViewModelProperty> : BasePropertyValidation<TViewModel>
 207    {
 208        /// <summary>
 209        /// The message to be constructed.
 210        /// </summary>
 211        private readonly Func<TViewModelProperty, bool, ValidationText> _message;
 212
 213        private readonly IConnectableObservable<TViewModelProperty> _valueConnectedObservable;
 214
 215        /// <summary>
 216        /// The value calculated from the properties.
 217        /// </summary>
 218        [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed by field _dis
 58219        private readonly ReplaySubject<TViewModelProperty> _valueSubject = new ReplaySubject<TViewModelProperty>(1);
 220
 58221        private CompositeDisposable _disposables = new CompositeDisposable();
 222
 223        private bool _isConnected;
 224
 225        /// <summary>
 226        /// Initializes a new instance of the <see cref="BasePropertyValidation{TViewModel, TProperty1}"/> class.
 227        /// </summary>
 228        /// <param name="viewModel">ViewModel instance.</param>
 229        /// <param name="viewModelProperty">ViewModel property.</param>
 230        /// <param name="isValidFunc">Func to define if the viewModelProperty is valid or not.</param>
 231        /// <param name="message">Validation error message.</param>
 232        public BasePropertyValidation(
 233            TViewModel viewModel,
 234            Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
 235            Func<TViewModelProperty, bool> isValidFunc,
 236            string message)
 114237            : this(viewModel, viewModelProperty, isValidFunc, (p, v) => new ValidationText(v
 114238                ? string.Empty
 114239                : message))
 240        {
 42241        }
 242
 243        /// <summary>
 244        /// Initializes a new instance of the <see cref="BasePropertyValidation{TViewModel, TViewModelProperty}"/> class
 245        /// </summary>
 246        /// <param name="viewModel">ViewModel instance.</param>
 247        /// <param name="viewModelProperty">ViewModel property.</param>
 248        /// <param name="isValidFunc">Func to define if the viewModelProperty is valid or not.</param>
 249        /// <param name="message">Func to define the validation error message based on the viewModelProperty value.</par
 250        public BasePropertyValidation(
 251            TViewModel viewModel,
 252            Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
 253            Func<TViewModelProperty, bool> isValidFunc,
 254            Func<TViewModelProperty, string> message)
 42255            : this(viewModel, viewModelProperty, isValidFunc, (p, v) => new ValidationText(v
 42256                ? string.Empty
 42257                : message(p)))
 258        {
 14259        }
 260
 261        /// <summary>
 262        /// Initializes a new instance of the <see cref="BasePropertyValidation{TViewModel, TViewModelProperty}"/> class
 263        /// </summary>
 264        /// <param name="viewModel">ViewModel instance.</param>
 265        /// <param name="viewModelProperty">ViewModel property.</param>
 266        /// <param name="isValidFunc">Func to define if the viewModelProperty is valid or not.</param>
 267        /// <param name="messageFunc">Func to define the validation error message based on the viewModelProperty and isV
 268        public BasePropertyValidation(
 269            TViewModel viewModel,
 270            Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
 271            Func<TViewModelProperty, bool> isValidFunc,
 272            Func<TViewModelProperty, bool, string> messageFunc)
 2273            : this(viewModel, viewModelProperty, isValidFunc, (prop1, isValid) =>
 6274                new ValidationText(messageFunc(prop1, isValid)))
 275        {
 2276        }
 277
 278        /// <summary>
 279        /// Initializes a new instance of the <see cref="BasePropertyValidation{TViewModel, TViewModelProperty}"/> class
 280        /// Main constructor.
 281        /// </summary>
 282        /// <param name="viewModel">ViewModel instance.</param>
 283        /// <param name="viewModelProperty">ViewModel property.</param>
 284        /// <param name="isValidFunc">Func to define if the viewModelProperty is valid or not.</param>
 285        /// <param name="messageFunc">Func to define the validation error message based on the viewModelProperty and isV
 58286        private BasePropertyValidation(
 58287            TViewModel viewModel,
 58288            Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
 58289            Func<TViewModelProperty, bool> isValidFunc,
 58290            Func<TViewModelProperty, bool, ValidationText> messageFunc)
 291        {
 292            // Now, we have a function, which, in this case uses the value of the view Model Property...
 58293            IsValidFunc = isValidFunc;
 294
 295            // Record this property name
 58296            AddProperty(viewModelProperty);
 297
 298            // The function invoked
 58299            _message = messageFunc;
 300
 301            // Our connected observable
 162302            _valueConnectedObservable = viewModel.WhenAny(viewModelProperty, v => v.Value).DistinctUntilChanged()
 58303                .Multicast(_valueSubject);
 58304        }
 305
 306        /// <summary>
 307        /// Gets the mechanism to determine if the property(s) is valid or not.
 308        /// </summary>
 208309        private Func<TViewModelProperty, bool> IsValidFunc { get; }
 310
 311        /// <inheritdoc />
 312        /// <summary>
 313        /// Get the validation change observable.
 314        /// </summary>
 315        /// <returns></returns>
 316        protected override IObservable<ValidationState> GetValidationChangeObservable()
 317        {
 58318            Activate();
 319
 162320            return _valueSubject.Select(value => new ValidationState(IsValidFunc(value), GetMessage(value), this))
 58321                .DistinctUntilChanged(new ValidationStateComparer());
 322        }
 323
 324        /// <inheritdoc />
 325        protected override void Dispose(bool disposing)
 326        {
 0327            base.Dispose(disposing);
 328
 0329            if (disposing)
 330            {
 0331                _disposables?.Dispose();
 332            }
 0333        }
 334
 335        private void Activate()
 336        {
 58337            if (_isConnected)
 338            {
 0339                return;
 340            }
 341
 58342            _disposables.Add(_valueConnectedObservable.Connect());
 343
 58344            _isConnected = true;
 58345        }
 346
 347        private ValidationText GetMessage(TViewModelProperty value)
 348        {
 349            // Need something subtle to deal with validity having not actual message
 104350            return _message(value, IsValidFunc(value));
 351        }
 352    }
 353}