< Summary

Class:ReactiveUI.Validation.ValidationBindings.ValidationBinding
Assembly:ReactiveUI.Validation
File(s):D:\a\1\s\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs
Covered lines:66
Uncovered lines:79
Coverable lines:145
Total lines:412
Line coverage:45.5% (66 of 145)
Covered branches:48
Total branches:100
Branch coverage:48% (48 of 100)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
ForProperty(...)-87.5%85%
ForProperty(...)-0%0%
ForValidationHelperProperty(...)-84.21%78.57%
ForValidationHelperProperty(...)-0%0%
ForViewModel(...)-0%0%
ForViewModel(...)-84.62%83.33%
Dispose()-0%100%
BindToView(...)-87.5%100%
Dispose(...)-0%0%

File(s)

D:\a\1\s\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.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.Linq;
 9using System.Linq.Expressions;
 10using System.Reactive;
 11using System.Reactive.Disposables;
 12using System.Reactive.Linq;
 13using ReactiveUI.Validation.Abstractions;
 14using ReactiveUI.Validation.Extensions;
 15using ReactiveUI.Validation.Formatters;
 16using ReactiveUI.Validation.Formatters.Abstractions;
 17using ReactiveUI.Validation.Helpers;
 18using ReactiveUI.Validation.States;
 19using ReactiveUI.Validation.ValidationBindings.Abstractions;
 20using Splat;
 21
 22namespace ReactiveUI.Validation.ValidationBindings
 23{
 24    /// <inheritdoc />
 25    public sealed class ValidationBinding : IValidationBinding
 26    {
 2627        private readonly CompositeDisposable _disposables = new CompositeDisposable();
 28
 2629        private ValidationBinding(IObservable<Unit> validationObservable)
 30        {
 2631            _disposables.Add(validationObservable.Subscribe());
 2632        }
 33
 34        /// <summary>
 35        /// Creates a binding between a ViewModel property and a view property.
 36        /// </summary>
 37        /// <typeparam name="TView">ViewFor of ViewModel type.</typeparam>
 38        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
 39        /// <typeparam name="TViewModelProperty">ViewModel property type.</typeparam>
 40        /// <typeparam name="TViewProperty">View property type.</typeparam>
 41        /// <param name="view">View instance.</param>
 42        /// <param name="viewModelProperty">ViewModel property.</param>
 43        /// <param name="viewProperty">View property.</param>
 44        /// <param name="formatter">Validation formatter. Defaults to the <see cref="SingleLineFormatter"/>.</param>
 45        /// <param name="strict">Indicates if the ViewModel property to find is unique.</param>
 46        /// <returns>Returns a validation component.</returns>
 47        public static IValidationBinding ForProperty<TView, TViewModel, TViewModelProperty, TViewProperty>(
 48            TView view,
 49            Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
 50            Expression<Func<TView, TViewProperty>> viewProperty,
 51            IValidationTextFormatter<string>? formatter = null,
 52            bool strict = true)
 53            where TView : IViewFor<TViewModel>
 54            where TViewModel : ReactiveObject, IValidatableViewModel
 55        {
 1656            if (view is null)
 57            {
 058                throw new ArgumentNullException(nameof(view));
 59            }
 60
 1661            if (viewModelProperty is null)
 62            {
 063                throw new ArgumentNullException(nameof(viewModelProperty));
 64            }
 65
 1666            if (viewProperty is null)
 67            {
 068                throw new ArgumentNullException(nameof(viewProperty));
 69            }
 70
 1671            formatter ??= SingleLineFormatter.Default;
 72
 1673            var vcObs = view
 1674                .WhenAnyValue(v => v.ViewModel)
 3275                .Where(vm => vm != null)
 1676                .Select(
 3277                    viewModel => viewModel!
 3278                        .ValidationContext
 3279                        .ResolveFor(viewModelProperty, strict)
 5280                        .Select(x => x.ValidationStatusChange)
 3281                        .CombineLatest())
 1682                .Switch()
 1683                .Select(
 3684                    states => states
 6085                        .Select(state => formatter.Format(state.Text))
 6086                        .FirstOrDefault(msg => !string.IsNullOrEmpty(msg)) ?? string.Empty);
 87
 1688            var updateObs = BindToView(vcObs, view, viewProperty)
 3689                .Select(_ => Unit.Default);
 90
 1691            return new ValidationBinding(updateObs);
 92        }
 93
 94        /// <summary>
 95        /// Creates a binding from a specified ViewModel property to a provided action.
 96        /// </summary>
 97        /// <typeparam name="TView">ViewFor of ViewModel type.</typeparam>
 98        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
 99        /// <typeparam name="TViewModelProperty">ViewModel property type.</typeparam>
 100        /// <typeparam name="TOut">Action return type.</typeparam>
 101        /// <param name="view">View instance.</param>
 102        /// <param name="viewModelProperty">ViewModel property.</param>
 103        /// <param name="action">Action to be executed.</param>
 104        /// <param name="formatter">Validation formatter.</param>
 105        /// <param name="strict">Indicates if the ViewModel property to find is unique.</param>
 106        /// <returns>Returns a validation component.</returns>
 107        public static IValidationBinding ForProperty<TView, TViewModel, TViewModelProperty, TOut>(
 108            TView view,
 109            Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
 110            Action<IList<ValidationState>, IList<TOut>> action,
 111            IValidationTextFormatter<TOut> formatter,
 112            bool strict = true)
 113            where TView : IViewFor<TViewModel>
 114            where TViewModel : ReactiveObject, IValidatableViewModel
 115        {
 0116            if (view is null)
 117            {
 0118                throw new ArgumentNullException(nameof(view));
 119            }
 120
 0121            if (viewModelProperty is null)
 122            {
 0123                throw new ArgumentNullException(nameof(viewModelProperty));
 124            }
 125
 0126            if (action is null)
 127            {
 0128                throw new ArgumentNullException(nameof(action));
 129            }
 130
 0131            if (formatter == null)
 132            {
 0133                throw new ArgumentNullException(nameof(formatter));
 134            }
 135
 0136            var vcObs = view
 0137                .WhenAnyValue(v => v.ViewModel)
 0138                .Where(vm => vm != null)
 0139                .Select(
 0140                    viewModel => viewModel!
 0141                        .ValidationContext
 0142                        .ResolveFor(viewModelProperty, strict)
 0143                        .Select(x => x.ValidationStatusChange)
 0144                        .CombineLatest())
 0145                .Switch()
 0146                .Select(states => new
 0147                {
 0148                    ValidationChange = states,
 0149                    Formatted = states
 0150                        .Select(state => formatter.Format(state.Text))
 0151                        .ToList()
 0152                })
 0153                .Do(r => action(r.ValidationChange, r.Formatted))
 0154                .Select(_ => Unit.Default);
 155
 0156            return new ValidationBinding(vcObs);
 157        }
 158
 159        /// <summary>
 160        /// Creates a binding between a <see cref="ValidationHelper" /> and a specified View property.
 161        /// </summary>
 162        /// <typeparam name="TView">ViewFor of ViewModel type.</typeparam>
 163        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
 164        /// <typeparam name="TViewProperty">View property type.</typeparam>
 165        /// <param name="view">View instance.</param>
 166        /// <param name="viewModelHelperProperty">ViewModel's ValidationHelper property.</param>
 167        /// <param name="viewProperty">View property to bind the validation message.</param>
 168        /// <param name="formatter">Validation formatter.</param>
 169        /// <returns>Returns a validation component.</returns>
 170        public static IValidationBinding ForValidationHelperProperty<TView, TViewModel, TViewProperty>(
 171            TView view,
 172            Expression<Func<TViewModel?, ValidationHelper>> viewModelHelperProperty,
 173            Expression<Func<TView, TViewProperty>> viewProperty,
 174            IValidationTextFormatter<string>? formatter = null)
 175            where TView : IViewFor<TViewModel>
 176            where TViewModel : ReactiveObject, IValidatableViewModel
 177        {
 6178            if (view is null)
 179            {
 0180                throw new ArgumentNullException(nameof(view));
 181            }
 182
 6183            if (viewModelHelperProperty is null)
 184            {
 0185                throw new ArgumentNullException(nameof(viewModelHelperProperty));
 186            }
 187
 6188            if (viewProperty is null)
 189            {
 0190                throw new ArgumentNullException(nameof(viewProperty));
 191            }
 192
 6193            formatter ??= SingleLineFormatter.Default;
 194
 6195            var vcObs = view
 6196                .WhenAnyValue(v => v.ViewModel)
 12197                .Where(vm => vm != null)
 6198                .Select(
 12199                    viewModel => viewModel
 12200                        .WhenAnyValue(viewModelHelperProperty)
 18201                        .SelectMany(vy => vy.ValidationChanged))
 6202                .Switch()
 20203                .Select(vc => formatter.Format(vc.Text));
 204
 6205            var updateObs = BindToView(vcObs, view, viewProperty)
 20206                .Select(_ => Unit.Default);
 207
 6208            return new ValidationBinding(updateObs);
 209        }
 210
 211        /// <summary>
 212        /// Creates a binding from a <see cref="ValidationHelper" /> to a specified action.
 213        /// </summary>
 214        /// <typeparam name="TView">ViewFor of ViewModel type.</typeparam>
 215        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
 216        /// <typeparam name="TOut">Action return type.</typeparam>
 217        /// <param name="view">View instance.</param>
 218        /// <param name="viewModelHelperProperty">ViewModel's ValidationHelper property.</param>
 219        /// <param name="action">Action to be executed.</param>
 220        /// <param name="formatter">Validation formatter.</param>
 221        /// <returns>Returns a validation component.</returns>
 222        public static IValidationBinding ForValidationHelperProperty<TView, TViewModel, TOut>(
 223            TView view,
 224            Expression<Func<TViewModel?, ValidationHelper>> viewModelHelperProperty,
 225            Action<ValidationState, TOut> action,
 226            IValidationTextFormatter<TOut> formatter)
 227            where TView : IViewFor<TViewModel>
 228            where TViewModel : ReactiveObject, IValidatableViewModel
 229        {
 0230            if (view is null)
 231            {
 0232                throw new ArgumentNullException(nameof(view));
 233            }
 234
 0235            if (viewModelHelperProperty is null)
 236            {
 0237                throw new ArgumentNullException(nameof(viewModelHelperProperty));
 238            }
 239
 0240            if (action is null)
 241            {
 0242                throw new ArgumentNullException(nameof(action));
 243            }
 244
 0245            if (formatter is null)
 246            {
 0247                throw new ArgumentNullException(nameof(formatter));
 248            }
 249
 0250            var vcObs = view
 0251                .WhenAnyValue(v => v.ViewModel)
 0252                .Where(vm => vm != null)
 0253                .Select(
 0254                    viewModel => viewModel
 0255                        .WhenAnyValue(viewModelHelperProperty)
 0256                        .SelectMany(vy => vy.ValidationChanged))
 0257                .Switch()
 0258                .Select(vc => new { ValidationChange = vc, Formatted = formatter.Format(vc.Text) });
 259
 0260            var updateObs = vcObs
 0261                .Do(r => action(r.ValidationChange, r.Formatted))
 0262                .Select(_ => Unit.Default);
 263
 0264            return new ValidationBinding(updateObs);
 265        }
 266
 267        /// <summary>
 268        /// Creates a binding between a ViewModel and a specified action.
 269        /// </summary>
 270        /// <typeparam name="TView">ViewFor of ViewModel type.</typeparam>
 271        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
 272        /// <typeparam name="TOut">Action return type.</typeparam>
 273        /// <param name="view">View instance.</param>
 274        /// <param name="action">Action to be executed.</param>
 275        /// <param name="formatter">Validation formatter.</param>
 276        /// <returns>Returns a validation component.</returns>
 277        public static IValidationBinding ForViewModel<TView, TViewModel, TOut>(
 278            TView view,
 279            Action<TOut> action,
 280            IValidationTextFormatter<TOut> formatter)
 281            where TView : IViewFor<TViewModel>
 282            where TViewModel : ReactiveObject, IValidatableViewModel
 283        {
 0284            if (view is null)
 285            {
 0286                throw new ArgumentNullException(nameof(view));
 287            }
 288
 0289            if (action is null)
 290            {
 0291                throw new ArgumentNullException(nameof(action));
 292            }
 293
 0294            if (formatter is null)
 295            {
 0296                throw new ArgumentNullException(nameof(formatter));
 297            }
 298
 0299            var vcObs = view
 0300                .WhenAnyValue(v => v.ViewModel)
 0301                .Where(vm => vm != null)
 0302                .Select(vm => vm!.ValidationContext.Text)
 0303                .Select(formatter.Format);
 304
 0305            var updateObs = vcObs
 0306                .Do(action)
 0307                .Select(_ => Unit.Default);
 308
 0309            return new ValidationBinding(updateObs);
 310        }
 311
 312        /// <summary>
 313        /// Creates a binding between a ViewModel and a View property.
 314        /// </summary>
 315        /// <typeparam name="TView">ViewFor of ViewModel type.</typeparam>
 316        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
 317        /// <typeparam name="TViewProperty">View property type.</typeparam>
 318        /// <param name="view">View instance.</param>
 319        /// <param name="viewProperty">View property to bind the validation message.</param>
 320        /// <param name="formatter">Validation formatter.</param>
 321        /// <returns>Returns a validation component.</returns>
 322        public static IValidationBinding ForViewModel<TView, TViewModel, TViewProperty>(
 323            TView view,
 324            Expression<Func<TView, TViewProperty>> viewProperty,
 325            IValidationTextFormatter<string>? formatter = null)
 326            where TView : IViewFor<TViewModel>
 327            where TViewModel : ReactiveObject, IValidatableViewModel
 328        {
 4329            if (view is null)
 330            {
 0331                throw new ArgumentNullException(nameof(view));
 332            }
 333
 4334            if (viewProperty is null)
 335            {
 0336                throw new ArgumentNullException(nameof(view));
 337            }
 338
 4339            formatter ??= SingleLineFormatter.Default;
 340
 4341            var vcObs = view
 4342                .WhenAnyValue(v => v.ViewModel)
 8343                .Where(vm => vm != null)
 8344                .SelectMany(vm => vm!.ValidationContext.ValidationStatusChange)
 8345                .Select(vc => formatter.Format(vc.Text));
 346
 4347            var updateObs = BindToView(vcObs, view, viewProperty)
 8348                .Select(_ => Unit.Default);
 349
 4350            return new ValidationBinding(updateObs);
 351        }
 352
 353        /// <inheritdoc/>
 354        public void Dispose()
 355        {
 356            // Dispose of unmanaged resources.
 0357            Dispose(true);
 0358        }
 359
 360        /// <summary>
 361        /// Creates a binding to a View property.
 362        /// </summary>
 363        /// <typeparam name="TView">ViewFor of ViewModel type.</typeparam>
 364        /// <typeparam name="TViewProperty">ViewModel type.</typeparam>
 365        /// <typeparam name="TTarget">Target type.</typeparam>
 366        /// <param name="valueChange">Observable value change.</param>
 367        /// <param name="target">Target instance.</param>
 368        /// <param name="viewProperty">View property.</param>
 369        /// <returns>Returns a validation component.</returns>
 370        private static IObservable<string> BindToView<TView, TViewProperty, TTarget>(
 371            IObservable<string> valueChange,
 372            TTarget target,
 373            Expression<Func<TView, TViewProperty>> viewProperty)
 374            where TTarget : notnull
 375        {
 26376            var viewExpression = Reflection.Rewrite(viewProperty.Body);
 377
 26378            var setter = Reflection.GetValueSetterOrThrow(viewExpression.GetMemberInfo())!;
 379
 26380            if (viewExpression.GetParent().NodeType == ExpressionType.Parameter)
 381            {
 24382                return valueChange
 24383                   .Do(
 60384                       x => setter(target, x, viewExpression.GetArgumentsArray()),
 0385                       ex => LogHost.Default.Error(ex, $"{viewExpression} Binding received an Exception!"));
 386            }
 387
 2388            var bindInfo = valueChange.CombineLatest(
 4389                target.WhenAnyDynamic(viewExpression.GetParent(), x => x.Value),
 4390                (val, host) => new { val, host });
 391
 2392            return bindInfo
 4393                .Where(x => x.host != null)
 2394                .Do(
 4395                    x => setter(x.host, x.val, viewExpression.GetArgumentsArray()),
 0396                    ex => LogHost.Default.Error(ex, $"{viewExpression} Binding received an Exception!"))
 4397                .Select(v => v.val);
 398        }
 399
 400        /// <summary>
 401        /// Disposes of the managed resources.
 402        /// </summary>
 403        /// <param name="disposing">If its getting called by the <see cref="Dispose()"/> method.</param>
 404        private void Dispose(bool disposing)
 405        {
 0406            if (disposing)
 407            {
 0408                _disposables.Dispose();
 409            }
 0410        }
 411    }
 412}