Updated to NetStandard and improved code.

This commit is contained in:
alexmartinezm 2019-02-23 21:42:05 +01:00
Родитель 33535cf0cc
Коммит 7e9278e0e4
21 изменённых файлов: 2241 добавлений и 8 удалений

31
.gitignore поставляемый
Просмотреть файл

@ -4,6 +4,7 @@
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
@ -19,6 +20,8 @@
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
@ -52,7 +55,6 @@ BenchmarkDotNet.Artifacts/
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
@ -60,7 +62,7 @@ StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*_h.h
*.ilk
*.meta
*.obj
@ -77,6 +79,7 @@ StyleCopReport.xml
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
@ -208,7 +211,7 @@ _pkginfo.txt
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
!?*.[Cc]ache/
# Others
ClientBin/
@ -221,13 +224,15 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true
**/wwwroot/lib/
# RIA/Silverlight projects
Generated_Code/
@ -252,6 +257,7 @@ ServiceFabricBackup/
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
@ -291,8 +297,8 @@ paket-files/
.idea/
*.sln.iml
# CodeRush
.cr/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
@ -317,7 +323,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
@ -326,5 +332,14 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/

Просмотреть файл

@ -0,0 +1,15 @@
using ReactiveUI.Validation.Contexts;
namespace ReactiveUI.Validation.Abstractions
{
/// <summary>
/// Interface used by view models to indicate they have a validation context.
/// </summary>
public interface ISupportsValidation
{
/// <summary>
/// Get the validation context
/// </summary>
ValidationContext ValidationContext { get; }
}
}

Просмотреть файл

@ -0,0 +1,61 @@
using System.Collections;
using System.Collections.Generic;
namespace ReactiveUI.Validation.Collections
{
/// <summary>
/// Container for validation text
/// </summary>
public class ValidationText : IEnumerable<string>
{
private readonly List<string> _texts = new List<string>();
public ValidationText()
{
}
public ValidationText(string text)
{
_texts.Add(text);
}
public ValidationText(IEnumerable<ValidationText> validationTexts)
{
foreach (var vt in validationTexts) _texts.AddRange(vt._texts);
}
public string this[int index] => _texts[index];
public int Count => _texts.Count;
public IEnumerator<string> GetEnumerator()
{
return _texts.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(string text)
{
_texts.Add(text);
}
public void Clear()
{
_texts.Clear();
}
/// <summary>
/// Convert representation to a single line using a specified separator
/// </summary>
/// <param name="separator"></param>
/// <returns></returns>
public string ToSingleLine(string separator = ",")
{
return string.Join(separator, _texts);
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using ReactiveUI.Validation.States;
namespace ReactiveUI.Validation.Comparators
{
/// <inheritdoc />
/// <summary>
/// Utility class used to compare <see cref="T:ReactiveUI.Validation.States.ValidationState" /> instances.
/// </summary>
public class ValidationStateComparer : EqualityComparer<ValidationState>
{
public override bool Equals(ValidationState x, ValidationState y)
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
return x.IsValid == y.IsValid && x.Text.ToSingleLine() == y.Text.ToSingleLine() &&
x.Component == y.Component;
}
public override int GetHashCode(ValidationState obj)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
return obj.GetHashCode();
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
using System;
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.States;
namespace ReactiveUI.Validation.Components.Abstractions
{
/// <summary>
/// Core interface which all validation components must implement.
/// </summary>
public interface IValidationComponent
{
/// <summary>
/// Get the Current,optional validation message
/// </summary>
ValidationText Text { get; }
/// <summary>
/// Get the current validation state
/// </summary>
bool IsValid { get; }
/// <summary>
/// Get the observable for validation state changes.
/// </summary>
IObservable<ValidationState> ValidationStatusChange { get; }
}
}

Просмотреть файл

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.Comparators;
using ReactiveUI.Validation.Components.Abstractions;
using ReactiveUI.Validation.States;
namespace ReactiveUI.Validation.Components
{
/// <inheritdoc cref="ReactiveObject" />
/// <inheritdoc cref="IDisposable" />
/// <inheritdoc cref="IValidationComponent" />
/// <summary>
/// Base class for items which are used to build a <see cref="T:ReactiveUI.Validation.Contexts.ValidationContext" />
/// </summary>
public abstract class BasePropertyValidation<TViewModel> : ReactiveObject, IDisposable, IValidationComponent
{
/// <summary>
/// The items to be disposed.
/// </summary>
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// The current valid state
/// </summary>
private readonly ReplaySubject<bool> _isValidSubject = new ReplaySubject<bool>(1);
/// <summary>
/// The list of property names this validator
/// </summary>
private readonly HashSet<string> _propertyNames = new HashSet<string>();
/// <summary>
/// The connected observable to kick off seeing <see cref="ValidationStatusChange" />
/// </summary>
private IConnectableObservable<ValidationState> _connectedChange;
private bool _isConnected;
/// <summary>
/// Our current validity state
/// </summary>
private bool _isValid;
private ValidationText _text;
protected BasePropertyValidation()
{
// subscribe to the valid subject so we can assign the validity
_disposables.Add(_isValidSubject.Subscribe(v => _isValid = v));
}
/// <summary>
/// Get the total number of properties referenced.
/// </summary>
public int PropertyCount => _propertyNames.Count;
public virtual void Dispose()
{
_disposables?.Dispose();
}
public bool IsValid
{
get
{
Activate();
return _isValid;
}
}
/// <inheritdoc />
/// <summary>
/// The public mechanism indicating that the validation state has changed.
/// </summary>
public IObservable<ValidationState> ValidationStatusChange
{
get
{
Activate();
return _connectedChange;
}
}
public ValidationText Text
{
get
{
Activate();
return _text;
}
}
/// <summary>
/// Determine if a property name is actually contained within this
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="property"></param>
/// <param name="exclusively"></param>
/// <returns></returns>
public bool ContainsProperty<TProp>(Expression<Func<TViewModel, TProp>> property, bool exclusively = false)
{
var propertyName = property.Body.ToString();
return exclusively
? _propertyNames.Contains(propertyName) && _propertyNames.Count == 1
: _propertyNames.Contains(propertyName);
}
/// <summary>
/// Add a property to the list of this which this validation is associated with.
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="property"></param>
protected void AddProperty<TProp>(Expression<Func<TViewModel, TProp>> property)
{
var propertyName = property.Body.ToString();
_propertyNames.Add(propertyName);
}
/// <summary>
/// Get the validation change observable, implemented by concrete classes.
/// </summary>
/// <returns></returns>
protected abstract IObservable<ValidationState> GetValidationChangeObservable();
private void Activate()
{
if (_isConnected)
return;
_connectedChange = GetValidationChangeObservable()
.Do(state =>
{
_isValid = state.IsValid;
_text = state.Text;
})
.Replay(1);
_disposables.Add(_connectedChange.Connect());
_isConnected = true;
}
}
/// <inheritdoc />
/// <summary>
/// Property validator for a single view model property.
/// </summary>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TProperty1"></typeparam>
public sealed class BasePropertyValidation<TViewModel, TProperty1> : BasePropertyValidation<TViewModel>
{
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// The message to be constructed.
/// </summary>
private readonly Func<TProperty1, bool, ValidationText> _message;
private readonly IConnectableObservable<TProperty1> _valueConnectedObservable;
/// <summary>
/// The value calculated from the properties.
/// </summary>
private readonly ReplaySubject<TProperty1> _valueSubject = new ReplaySubject<TProperty1>(1);
private bool _isConnected;
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> viewModelProperty,
Func<TProperty1, bool> isValidFunc,
string message) : this(viewModel,
viewModelProperty, isValidFunc, (p, v) => new ValidationText(v ? string.Empty : message))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> viewModelProperty,
Func<TProperty1, bool> isValidFunc,
Func<TProperty1, string> message) :
this(viewModel, viewModelProperty, isValidFunc,
(p, v) => new ValidationText(v ? string.Empty : message(p)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> viewModelProperty,
Func<TProperty1, bool> isValidFunc,
Func<TProperty1, bool, string> messageFunc) : this(viewModel, viewModelProperty,
isValidFunc, (prop1, isValid) => new ValidationText(messageFunc(prop1, isValid)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> viewModelProperty,
Func<TProperty1, bool> isValidFunc,
Func<TProperty1, bool, ValidationText> messageFunc)
{
// Now, we have a function, which, in this case uses the value of the view Model Property...
IsValidFunc = isValidFunc;
// Record this property name
AddProperty(viewModelProperty);
// The function invoked
_message = messageFunc;
// Our connected observable
_valueConnectedObservable = viewModel.WhenAny(viewModelProperty, v => v.Value).DistinctUntilChanged()
.Multicast(_valueSubject);
}
/// <summary>
/// The mechanism to determine if the property(s) is valid or not
/// </summary>
private Func<TProperty1, bool> IsValidFunc { get; }
/// <inheritdoc />
/// <summary>
/// Get the validation change observable.
/// </summary>
/// <returns></returns>
protected override IObservable<ValidationState> GetValidationChangeObservable()
{
Activate();
return _valueSubject.Select(value => new ValidationState(IsValidFunc(value), GetMessage(value), this))
.DistinctUntilChanged(new ValidationStateComparer());
}
private void Activate()
{
if (_isConnected)
return;
_disposables.Add(_valueConnectedObservable.Connect());
_isConnected = true;
}
private ValidationText GetMessage(TProperty1 value)
{
// Need something subtle to deal with validity having not actual message
return _message(value, IsValidFunc(value));
}
}
}

Просмотреть файл

@ -0,0 +1,113 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.Components.Abstractions;
using ReactiveUI.Validation.States;
namespace ReactiveUI.Validation.Components
{
/// <inheritdoc cref="ReactiveObject" />
/// <inheritdoc cref="IValidationComponent" />
/// <inheritdoc cref="IDisposable" />
/// <summary>
/// More generic observable for determination of validity
/// </summary>
/// <remarks>
/// We probably need a more 'complex' one, where the params of the validation block are
/// passed through ?
/// Also, what about access to the view model to output the error message ?
/// </remarks>
public class ModelObservableValidation<TViewModel> : ReactiveObject, IValidationComponent, IDisposable
{
private readonly CompositeDisposable _disposables = new CompositeDisposable();
private readonly ReplaySubject<ValidationState> _lastValidationStateSubject =
new ReplaySubject<ValidationState>(1);
// the underlying connected observable for the validation change which is published
private readonly IConnectableObservable<ValidationState> _validityConnectedObservable;
private bool _isActive;
private bool _isValid;
private ValidationText _text;
public ModelObservableValidation(TViewModel model,
Func<TViewModel, IObservable<bool>> validityObservable,
Func<TViewModel, bool, string> messageFunc) : this(model, validityObservable,
(vm, state) => new ValidationText(messageFunc(vm, state)))
{
}
public ModelObservableValidation(TViewModel model,
Func<TViewModel, IObservable<bool>> validityObservable,
Func<TViewModel, bool, ValidationText> messageFunc)
{
_disposables.Add(_lastValidationStateSubject.Do(s =>
{
_isValid = s.IsValid;
_text = s.Text;
}).Subscribe());
_validityConnectedObservable = Observable.Defer(() => validityObservable(model))
.Select(v => new ValidationState(v, messageFunc(model, v), this))
.Multicast(_lastValidationStateSubject);
}
public void Dispose()
{
_disposables?.Dispose();
}
/// <inheritdoc />
/// <summary>
/// Get the current validation text
/// </summary>
public ValidationText Text
{
get
{
Activate();
return _text;
}
}
/// <inheritdoc />
/// <summary>
/// Get the current state
/// </summary>
public bool IsValid
{
get
{
Activate();
return _isValid;
}
}
/// <inheritdoc />
/// <summary>
/// Get the observable for <see cref="T:ReactiveUI.Validation.States.ValidationState" /> changes
/// </summary>
public IObservable<ValidationState> ValidationStatusChange
{
get
{
Activate();
return _validityConnectedObservable;
}
}
private void Activate()
{
if (_isActive)
return;
_isActive = true;
_disposables.Add(_validityConnectedObservable.Connect());
}
}
}

Просмотреть файл

@ -0,0 +1,171 @@
using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ReactiveUI.Legacy;
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.Components.Abstractions;
using ReactiveUI.Validation.States;
namespace ReactiveUI.Validation.Contexts
{
/// <inheritdoc cref="ReactiveObject" />
/// <inheritdoc cref="IDisposable" />
/// <inheritdoc cref="IValidationComponent" />
/// <summary>
/// The overall context for a view model under which validation takes place.
/// </summary>
/// <remarks>
/// Contains all of the <see cref="T:ReactiveUI.Validation.Components.Contracts.IValidationComponent" /> instances
/// applicable to the view model.
/// </remarks>
public class ValidationContext : ReactiveObject, IDisposable, IValidationComponent
{
/// <summary>
/// What needs to be disposed off
/// </summary>
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// Backing field for the current validation state
/// </summary>
private readonly ObservableAsPropertyHelper<bool> _isValid;
private readonly IConnectableObservable<bool> _validationConnectable;
/// <summary>
/// The list of current validations
/// </summary>
private readonly ReactiveList<IValidationComponent> _validations = new ReactiveList<IValidationComponent>();
private readonly ReplaySubject<ValidationState> _validationStatusChange = new ReplaySubject<ValidationState>(1);
/// <summary>
/// Backing field for the validation summary
/// </summary>
private readonly ObservableAsPropertyHelper<ValidationText> _validationText;
/// <summary>
/// Subject for validality of the context.
/// </summary>
private readonly ReplaySubject<bool> _validSubject = new ReplaySubject<bool>(1);
private bool _isActive;
/// <inheritdoc />
/// <summary>
/// Create the context
/// </summary>
public ValidationContext()
{
// publish the current validation state
_disposables.Add(_validSubject.StartWith(true).ToProperty(this, m => m.IsValid, out _isValid));
// when a change occurs in the validation state, publish the updated validation text
_disposables.Add(_validSubject.StartWith(true).Select(v => BuildText())
.ToProperty(this, m => m.Text, out _validationText, new ValidationText()));
//publish the current validation state
_disposables.Add(_validSubject.Select(v => new ValidationState(IsValid, BuildText(), this))
.Do(vc => _validationStatusChange.OnNext(vc)).Subscribe());
// observe the defined validations and whenever there is a change publish the current validation state.
_validationConnectable = _validations.CountChanged.StartWith(0)
.Select(_ =>
_validations
.Select(v => v.ValidationStatusChange).Merge()
.Select(o => Unit.Default).StartWith(Unit.Default))
.Switch()
.Select(_ => GetIsValid()).Multicast(_validSubject);
}
/// <summary>
/// An observable for the Valid state
/// </summary>
public IObservable<bool> Valid
{
get
{
Activate();
return _validSubject.AsObservable();
}
}
/// <summary>
/// Get the list of validations
/// </summary>
public IReadOnlyReactiveList<IValidationComponent> Validations => _validations;
public void Dispose()
{
_disposables?.Dispose();
}
/// <summary>
/// Get whether currently valid or not
/// </summary>
public bool IsValid
{
get
{
Activate();
return _isValid.Value;
}
}
public IObservable<ValidationState> ValidationStatusChange
{
get
{
Activate();
return _validationStatusChange.AsObservable();
}
}
/// <inheritdoc />
/// <summary>
/// Get the current validation summary.
/// </summary>
public ValidationText Text
{
get
{
Activate();
return _validationText.Value;
}
}
private void Activate()
{
if (_isActive)
return;
_isActive = true;
_disposables.Add(_validationConnectable.Connect());
}
public void Add(IValidationComponent validation)
{
_validations.Add(validation);
}
public bool GetIsValid()
{
var isValid = _validations.Count == 0 || _validations.All(v => v.IsValid);
return isValid;
}
/// <summary>
/// Build a list of the validation text for each invalid component
/// </summary>
/// <returns></returns>
private ValidationText BuildText()
{
return new ValidationText(_validations.Where(p => !p.IsValid).Select(p => p.Text));
}
}
}

Просмотреть файл

@ -0,0 +1,111 @@
using System;
using System.Linq.Expressions;
using ReactiveUI.Validation.Abstractions;
using ReactiveUI.Validation.Components;
using ReactiveUI.Validation.Helpers;
namespace ReactiveUI.Validation.Extensions
{
public static class SupportsValidationExtensions
{
/// <summary>
/// Validation rule for a property of a view model
/// </summary>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TViewModelProp"></typeparam>
/// <param name="viewModel"></param>
/// <param name="viewModelProperty"></param>
/// <param name="viewPropertyValid"></param>
/// <param name="message"></param>
/// <returns></returns>
public static ValidationHelper ValidationRule<TViewModel, TViewModelProp>(this TViewModel viewModel,
Expression<Func<TViewModel,
TViewModelProp>>
viewModelProperty,
Func<TViewModelProp, bool>
viewPropertyValid,
string message)
where TViewModel : ReactiveObject, ISupportsValidation
{
// we need to associate the view model property
// with something that can be easily looked up and bound to
var propValidation = new BasePropertyValidation<TViewModel, TViewModelProp>(viewModel, viewModelProperty,
viewPropertyValid, message);
viewModel.ValidationContext.Add(propValidation);
var validationHelper = new ValidationHelper(propValidation);
return validationHelper;
}
/// <summary>
/// Setup a validation rule for a specified view model property
/// </summary>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TViewModelProp"></typeparam>
/// <param name="viewModel"></param>
/// <param name="viewModelProperty"></param>
/// <param name="viewPropertyValid"></param>
/// <param name="message"></param>
/// <returns></returns>
public static ValidationHelper ValidationRule<TViewModel, TViewModelProp>(this TViewModel viewModel,
Expression<Func<TViewModel,
TViewModelProp>>
viewModelProperty,
Func<TViewModelProp, bool>
viewPropertyValid,
Func<TViewModelProp, string> message)
where TViewModel : ReactiveObject, ISupportsValidation
{
// we need to associate the view model property
// with something that can be easily looked up and bound to
var propValidation = new BasePropertyValidation<TViewModel, TViewModelProp>(viewModel, viewModelProperty,
viewPropertyValid, message);
viewModel.ValidationContext.Add(propValidation);
return new ValidationHelper(propValidation);
}
/// <summary>
/// Setup a rule with a general observable indicating validity.
/// </summary>
/// <typeparam name="TViewModel"></typeparam>
/// <param name="viewModel"></param>
/// <param name="validationObservableFunc"></param>
/// <param name="messageFunc"></param>
/// <returns></returns>
/// <remarks>
/// It should be noted that the observable should provide an initial value, otherwise that can result in an
/// inconsistent performance.
/// </remarks>
public static ValidationHelper ValidationRule<TViewModel>(this TViewModel viewModel,
Func<TViewModel, IObservable<bool>>
validationObservableFunc,
Func<TViewModel, bool, string> messageFunc)
where TViewModel : ReactiveObject, ISupportsValidation
{
var validation =
new ModelObservableValidation<TViewModel>(viewModel, validationObservableFunc, messageFunc);
viewModel.ValidationContext.Add(validation);
return new ValidationHelper(validation);
}
/// <summary>
/// Get an observable for the validity of the view model.
/// </summary>
/// <typeparam name="TViewModel"></typeparam>
/// <param name="viewModel"></param>
/// <returns></returns>
public static IObservable<bool> IsValid<TViewModel>(this TViewModel viewModel)
where TViewModel : ReactiveObject, ISupportsValidation
{
return viewModel.ValidationContext.Valid;
}
}
}

Просмотреть файл

@ -0,0 +1,63 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using ReactiveUI.Validation.Components;
using ReactiveUI.Validation.Contexts;
using ReactiveUI.Validation.TemplateGenerators;
namespace ReactiveUI.Validation.Extensions
{
public static class ValidationContextExtensions
{
/// <summary>
/// Resolve the property valuation for a specified property
/// </summary>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TProperty1"></typeparam>
/// <returns></returns>
public static BasePropertyValidation<TViewModel, TProperty1> ResolveFor<TViewModel, TProperty1>(
this ValidationContext context,
Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
bool strict = true)
{
var instance = context.Validations.Where(p => p is BasePropertyValidation<TViewModel, TProperty1>)
.Cast<BasePropertyValidation<TViewModel, TProperty1>>()
.FirstOrDefault(v => v.ContainsProperty(viewModelProperty1, strict));
return instance;
}
public static BasePropertyValidation<TViewModel, TProperty1, TProperty2> ResolveFor<TViewModel, TProperty1,
TProperty2>(this ValidationContext context,
Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
Expression<Func<TViewModel, TProperty1>> viewModelProperty2,
bool strict = true)
{
var instance = context
.Validations.Where(p => p is BasePropertyValidation<TViewModel, TProperty1, TProperty2>)
.Cast<BasePropertyValidation<TViewModel, TProperty1, TProperty2>>().FirstOrDefault(v =>
v.ContainsProperty(viewModelProperty1) && v.ContainsProperty(viewModelProperty2) &&
v.PropertyCount == 2);
return instance;
}
public static BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3> ResolveFor<TViewModel,
TProperty1, TProperty2, TProperty3>(this ValidationContext context,
Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
Expression<Func<TViewModel, TProperty1>> viewModelProperty2,
Expression<Func<TViewModel, TProperty1>> viewModelProperty3,
bool strict = true)
{
var instance = context
.Validations
.Where(p => p is BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3>)
.Cast<BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3>>()
.FirstOrDefault(v =>
v.ContainsProperty(viewModelProperty1) && v.ContainsProperty(viewModelProperty2) &&
v.ContainsProperty(viewModelProperty3) && v.PropertyCount == 3);
return instance;
}
}
}

Просмотреть файл

@ -0,0 +1,105 @@
using System;
using System.Linq.Expressions;
using System.Reactive.Linq;
using ReactiveUI.Validation.Abstractions;
using ReactiveUI.Validation.Helpers;
using ReactiveUI.Validation.ValidationBindings;
namespace ReactiveUI.Validation.Extensions
{
public static class ViewForExtensions
{
/// <summary>
/// Bind the specified view property validation to the view property.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TViewModelProperty1"></typeparam>
/// <typeparam name="TViewProperty"></typeparam>
/// <param name="view"></param>
/// <param name="viewModel"></param>
/// <param name="viewModelProperty1"></param>
/// <param name="viewProperty"></param>
/// <returns></returns>
public static IDisposable BindValidation
<TView, TViewModel, TViewModelProperty1, TViewProperty>(this TView view,
TViewModel viewModel,
Expression<Func<TViewModel, TViewModelProperty1>>
viewModelProperty1,
Expression<Func<TView, TViewProperty>> viewProperty)
where TViewModel : ReactiveObject, ISupportsValidation
where TView : IViewFor<TViewModel>
{
return ValidationBinding.ForProperty(view, viewModelProperty1, viewProperty);
}
/// <summary>
/// Bind the overall validation of a view model to a specified view property.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TViewProperty"></typeparam>
/// <param name="view"></param>
/// <param name="viewModel"></param>
/// <param name="viewProperty"></param>
/// <returns></returns>
public static IDisposable BindValidation
<TView, TViewModel, TViewProperty>(this TView view,
TViewModel viewModel,
Expression<Func<TView, TViewProperty>> viewProperty)
where TViewModel : ReactiveObject, ISupportsValidation
where TView : IViewFor<TViewModel>
{
return ValidationBinding.ForViewModel<TView, TViewModel, TViewProperty>(view, viewProperty);
}
/// <summary>
/// Bind a <see cref="ValidationHelper" /> from a view model to a specified view property.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TViewProperty"></typeparam>
/// <param name="view"></param>
/// <param name="viewModel"></param>
/// <param name="viewModelHelperProperty"></param>
/// <param name="viewProperty"></param>
/// <returns></returns>
public static IDisposable BindValidation
<TView, TViewModel, TViewProperty>(this TView view,
TViewModel viewModel,
Expression<Func<TViewModel, ValidationHelper>> viewModelHelperProperty,
Expression<Func<TView, TViewProperty>> viewProperty)
where TViewModel : ReactiveObject, ISupportsValidation
where TView : IViewFor<TViewModel>
{
return ValidationBinding.ForValidationHelperProperty(view, viewModelHelperProperty, viewProperty);
}
public static IDisposable BindToDirect
<TTarget, TValue>(IObservable<TValue> This, TTarget target, Expression viewExpression)
{
var setter = Reflection.GetValueSetterOrThrow(viewExpression.GetMemberInfo());
if (viewExpression.GetParent().NodeType == ExpressionType.Parameter)
return This.Subscribe(
x => setter(target, x, viewExpression.GetArgumentsArray()),
ex =>
{
//this.Log().ErrorException(String.Format("{0} Binding received an Exception!", viewExpression), ex);
});
var bindInfo = This.CombineLatest(target.WhenAnyDynamic(viewExpression.GetParent(), x => x.Value),
(val, host) => new {val, host});
return bindInfo
.Where(x => x.host != null)
.Subscribe(
x => setter(x.host, x.val, viewExpression.GetArgumentsArray()),
ex =>
{
//this.Log().ErrorException(String.Format("{0} Binding received an Exception!", viewExpression), ex);
});
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
using ReactiveUI.Validation.Collections;
namespace ReactiveUI.Validation.Formatters.Abstractions
{
/// <summary>
/// Specification for a <see cref="ValidationText" /> formatter.
/// </summary>
/// <typeparam name="TOut"></typeparam>
public interface IValidationTextFormatter<out TOut>
{
TOut Format(ValidationText validationText);
}
}

Просмотреть файл

@ -0,0 +1,31 @@
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.Formatters.Abstractions;
namespace ReactiveUI.Validation.Formatters
{
/// <inheritdoc />
/// <summary>
/// Helper class to generate a single formatted line for a
/// <see cref="T:ReactiveUI.Validation.Collections.ValidationText" />
/// </summary>
public class SingleLineFormatter : IValidationTextFormatter<string>
{
private readonly string _separator;
/// <summary>
/// Create an instance with an optional, custom separator.
/// </summary>
/// <param name="separator"></param>
public SingleLineFormatter(string separator = null)
{
_separator = separator;
}
public static SingleLineFormatter Default { get; } = new SingleLineFormatter();
public string Format(ValidationText validationText)
{
return validationText != null ? validationText.ToSingleLine(_separator) : string.Empty;
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.Components.Abstractions;
using ReactiveUI.Validation.States;
namespace ReactiveUI.Validation.Helpers
{
/// <inheritdoc cref="ReactiveObject" />
/// <inheritdoc cref="IDisposable" />
/// <summary>
/// Encapsulation of a validation with bindable properties.
/// </summary>
public class ValidationHelper : ReactiveObject, IDisposable
{
private readonly CompositeDisposable _disposables = new CompositeDisposable();
private readonly IValidationComponent _validation;
// how do we get this to be reactive though? we need to publish
// validation object
private ObservableAsPropertyHelper<bool> _isValid;
private ObservableAsPropertyHelper<ValidationText> _message;
public ValidationHelper(IValidationComponent validation)
{
_validation = validation;
Setup();
}
public bool IsValid => _isValid.Value;
public ValidationText Message => _message.Value;
public IObservable<ValidationState> ValidationChanged => _validation.ValidationStatusChange;
public void Dispose()
{
_disposables?.Dispose();
}
private void Setup()
{
_disposables.Add(_validation.ValidationStatusChange.Select(v => v.IsValid)
.ToProperty(this, vm => vm.IsValid, out _isValid));
_disposables.Add(_validation.ValidationStatusChange.Select(v => v.Text)
.ToProperty(this, vm => vm.Message, out _message));
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Class1.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Collections\" />
<Folder Include="Components\" />
<Folder Include="Contexts\" />
<Folder Include="Abstractions\" />
<Folder Include="Comparators\" />
<Folder Include="Extensions\" />
<Folder Include="Formatters\" />
<Folder Include="Helpers\" />
<Folder Include="States\" />
<Folder Include="TemplateGenerators\" />
<Folder Include="ValidationBindings\" />
<Folder Include="Components\Abstractions\" />
<Folder Include="Formatters\Abstractions\" />
<Folder Include="ValidationBindings\Abstractions\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ReactiveUI" Version="9.11.1" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,17 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Validation", "ReactiveUI.Validation.csproj", "{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,51 @@
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.Components.Abstractions;
namespace ReactiveUI.Validation.States
{
/// <summary>
/// Represents the validation state of a validation component.
/// </summary>
public sealed class ValidationState
{
/// <inheritdoc />
/// <summary>
/// Create an instance.
/// </summary>
/// <param name="isValid"></param>
/// <param name="text"></param>
/// <param name="component"></param>
public ValidationState(bool isValid, string text, IValidationComponent component) : this(isValid,
new ValidationText(text), component)
{
}
/// <summary>
/// Create an instance.
/// </summary>
/// <param name="isValid"></param>
/// <param name="text"></param>
/// <param name="component"></param>
public ValidationState(bool isValid, ValidationText text, IValidationComponent component)
{
IsValid = isValid;
Text = text;
Component = component;
}
/// <summary>
/// The associated component
/// </summary>
public IValidationComponent Component { get; }
/// <summary>
/// Get whether the validation is currently valid or not.
/// </summary>
public bool IsValid { get; }
/// <summary>
/// Get the validation text.
/// </summary>
public ValidationText Text { get; }
}
}

Просмотреть файл

@ -0,0 +1,646 @@
using System;
using System.Linq.Expressions;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.Comparators;
using ReactiveUI.Validation.Components;
using ReactiveUI.Validation.States;
namespace ReactiveUI.Validation.TemplateGenerators
{
public sealed class BasePropertyValidation<TViewModel, TProperty1, TProperty2> : BasePropertyValidation<TViewModel>
{
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// Function to determine if valid or not.
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2>, bool> _isValidFunc;
/// <summary>
/// The validation message factory
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2>, bool, ValidationText> _message;
/// <summary>
/// The connected observable to see updates in properties being validated
/// </summary>
private readonly IConnectableObservable<Tuple<TProperty1, TProperty2>> _valueConnectedObservable;
/// <summary>
/// Represents the current value.
/// </summary>
private readonly Subject<Tuple<TProperty1, TProperty2>> _valueSubject =
new Subject<Tuple<TProperty1, TProperty2>>();
/// <summary>
/// Are we connected
/// </summary>
private bool _connected;
/// <summary>
/// The last calculated value of the properties.
/// </summary>
private Tuple<TProperty1, TProperty2> _lastValue;
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Func<Tuple<TProperty1, TProperty2>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2>, string> message) :
this(viewModel, property1, property2, isValidFunc,
(p, v) => new ValidationText(v ? string.Empty : message(p)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Func<Tuple<TProperty1, TProperty2>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2>, bool, string> messageFunc) :
this(viewModel, property1, property2, isValidFunc,
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Func<Tuple<TProperty1, TProperty2>, bool> validSelector,
Func<Tuple<TProperty1, TProperty2>, bool, ValidationText> message)
{
_message = message;
_isValidFunc = validSelector;
// add the properties used to our list
AddProperty(property1);
AddProperty(property2);
// always record the last value seen
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
// setup a connected observable to see when values change and cast that to our value subject
_valueConnectedObservable = viewModel.WhenAnyValue(property1, property2).DistinctUntilChanged()
.Multicast(_valueSubject);
}
protected override IObservable<ValidationState> GetValidationChangeObservable()
{
Activate();
return _valueSubject.Select(value =>
{
var isValid = _isValidFunc(value);
return new ValidationState(isValid, GetMessage(value, isValid), this);
}).DistinctUntilChanged(new ValidationStateComparer());
}
protected ValidationText GetMessage(Tuple<TProperty1, TProperty2> @params, bool isValid)
{
return _message(@params, isValid);
}
/// <summary>
/// Activate the connection to ensure we start seeing validations.
/// </summary>
private void Activate()
{
if (!_connected)
{
_disposables.Add(_valueConnectedObservable.Connect());
_connected = true;
}
}
public override void Dispose()
{
_disposables.Dispose();
base.Dispose();
}
}
public sealed class
BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3> : BasePropertyValidation<TViewModel>
{
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// Function to determine if valid or not.
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3>, bool> _isValidFunc;
/// <summary>
/// The validation message factory
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3>, bool, ValidationText> _message;
/// <summary>
/// The connected observable to see updates in properties being validated
/// </summary>
private readonly IConnectableObservable<Tuple<TProperty1, TProperty2, TProperty3>> _valueConnectedObservable;
/// <summary>
/// Represents the current value.
/// </summary>
private readonly Subject<Tuple<TProperty1, TProperty2, TProperty3>> _valueSubject =
new Subject<Tuple<TProperty1, TProperty2, TProperty3>>();
/// <summary>
/// Are we connected
/// </summary>
private bool _connected;
/// <summary>
/// The last calculated value of the properties.
/// </summary>
private Tuple<TProperty1, TProperty2, TProperty3> _lastValue;
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2, TProperty3>, string> message) :
this(viewModel, property1, property2, property3, isValidFunc,
(p, v) => new ValidationText(v ? string.Empty : message(p)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool, string> messageFunc) :
this(viewModel, property1, property2, property3, isValidFunc,
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool> validSelector,
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool, ValidationText> message)
{
_message = message;
_isValidFunc = validSelector;
// add the properties used to our list
AddProperty(property1);
AddProperty(property2);
AddProperty(property3);
// always record the last value seen
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
// setup a connected observable to see when values change and cast that to our value subject
_valueConnectedObservable = viewModel.WhenAnyValue(property1, property2, property3).DistinctUntilChanged()
.Multicast(_valueSubject);
}
protected override IObservable<ValidationState> GetValidationChangeObservable()
{
Activate();
return _valueSubject.Select(value =>
{
var isValid = _isValidFunc(value);
return new ValidationState(isValid, GetMessage(value, isValid), this);
}).DistinctUntilChanged(new ValidationStateComparer());
}
protected ValidationText GetMessage(Tuple<TProperty1, TProperty2, TProperty3> @params, bool isValid)
{
return _message(@params, isValid);
}
/// <summary>
/// Activate the connection to ensure we start seeing validations.
/// </summary>
private void Activate()
{
if (!_connected)
{
_disposables.Add(_valueConnectedObservable.Connect());
_connected = true;
}
}
public override void Dispose()
{
_disposables.Dispose();
base.Dispose();
}
}
public sealed class
BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3,
TProperty4> : BasePropertyValidation<TViewModel>
{
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// Function to determine if valid or not.
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool> _isValidFunc;
/// <summary>
/// The validation message factory
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool, ValidationText> _message;
/// <summary>
/// The connected observable to see updates in properties being validated
/// </summary>
private readonly IConnectableObservable<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>>
_valueConnectedObservable;
/// <summary>
/// Represents the current value.
/// </summary>
private readonly Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>> _valueSubject =
new Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>>();
/// <summary>
/// Are we connected
/// </summary>
private bool _connected;
/// <summary>
/// The last calculated value of the properties.
/// </summary>
private Tuple<TProperty1, TProperty2, TProperty3, TProperty4> _lastValue;
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Expression<Func<TViewModel, TProperty4>> property4,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, string> message) :
this(viewModel, property1, property2, property3, property4, isValidFunc,
(p, v) => new ValidationText(v ? string.Empty : message(p)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Expression<Func<TViewModel, TProperty4>> property4,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool, string> messageFunc) :
this(viewModel, property1, property2, property3, property4, isValidFunc,
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Expression<Func<TViewModel, TProperty4>> property4,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool> validSelector,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool, ValidationText> message)
{
_message = message;
_isValidFunc = validSelector;
// add the properties used to our list
AddProperty(property1);
AddProperty(property2);
AddProperty(property3);
AddProperty(property4);
// always record the last value seen
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
// setup a connected observable to see when values change and cast that to our value subject
_valueConnectedObservable = viewModel.WhenAnyValue(property1, property2, property3, property4)
.DistinctUntilChanged().Multicast(_valueSubject);
}
protected override IObservable<ValidationState> GetValidationChangeObservable()
{
Activate();
return _valueSubject.Select(value =>
{
var isValid = _isValidFunc(value);
return new ValidationState(isValid, GetMessage(value, isValid), this);
}).DistinctUntilChanged(new ValidationStateComparer());
}
protected ValidationText GetMessage(Tuple<TProperty1, TProperty2, TProperty3, TProperty4> @params, bool isValid)
{
return _message(@params, isValid);
}
/// <summary>
/// Activate the connection to ensure we start seeing validations.
/// </summary>
private void Activate()
{
if (!_connected)
{
_disposables.Add(_valueConnectedObservable.Connect());
_connected = true;
}
}
public override void Dispose()
{
_disposables.Dispose();
base.Dispose();
}
}
public sealed class
BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3, TProperty4,
TProperty5> : BasePropertyValidation<TViewModel>
{
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// Function to determine if valid or not.
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool> _isValidFunc;
/// <summary>
/// The validation message factory
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool, ValidationText>
_message;
/// <summary>
/// The connected observable to see updates in properties being validated
/// </summary>
private readonly IConnectableObservable<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>>
_valueConnectedObservable;
/// <summary>
/// Represents the current value.
/// </summary>
private readonly Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>> _valueSubject =
new Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>>();
/// <summary>
/// Are we connected
/// </summary>
private bool _connected;
/// <summary>
/// The last calculated value of the properties.
/// </summary>
private Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5> _lastValue;
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Expression<Func<TViewModel, TProperty4>> property4,
Expression<Func<TViewModel, TProperty5>> property5,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, string> message) :
this(viewModel, property1, property2, property3, property4, property5, isValidFunc,
(p, v) => new ValidationText(v ? string.Empty : message(p)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Expression<Func<TViewModel, TProperty4>> property4,
Expression<Func<TViewModel, TProperty5>> property5,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool, string> messageFunc) :
this(viewModel, property1, property2, property3, property4, property5, isValidFunc,
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Expression<Func<TViewModel, TProperty4>> property4,
Expression<Func<TViewModel, TProperty5>> property5,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool> validSelector,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool, ValidationText> message)
{
_message = message;
_isValidFunc = validSelector;
// add the properties used to our list
AddProperty(property1);
AddProperty(property2);
AddProperty(property3);
AddProperty(property4);
AddProperty(property5);
// always record the last value seen
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
// setup a connected observable to see when values change and cast that to our value subject
_valueConnectedObservable = viewModel.WhenAnyValue(property1, property2, property3, property4, property5)
.DistinctUntilChanged().Multicast(_valueSubject);
}
protected override IObservable<ValidationState> GetValidationChangeObservable()
{
Activate();
return _valueSubject.Select(value =>
{
var isValid = _isValidFunc(value);
return new ValidationState(isValid, GetMessage(value, isValid), this);
}).DistinctUntilChanged(new ValidationStateComparer());
}
protected ValidationText GetMessage(Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5> @params,
bool isValid)
{
return _message(@params, isValid);
}
/// <summary>
/// Activate the connection to ensure we start seeing validations.
/// </summary>
private void Activate()
{
if (!_connected)
{
_disposables.Add(_valueConnectedObservable.Connect());
_connected = true;
}
}
public override void Dispose()
{
_disposables.Dispose();
base.Dispose();
}
}
public sealed class BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3, TProperty4, TProperty5,
TProperty6> : BasePropertyValidation<TViewModel>
{
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// Function to determine if valid or not.
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool>
_isValidFunc;
/// <summary>
/// The validation message factory
/// </summary>
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool,
ValidationText> _message;
/// <summary>
/// The connected observable to see updates in properties being validated
/// </summary>
private readonly
IConnectableObservable<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>>
_valueConnectedObservable;
/// <summary>
/// Represents the current value.
/// </summary>
private readonly Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>>
_valueSubject =
new Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>>();
/// <summary>
/// Are we connected
/// </summary>
private bool _connected;
/// <summary>
/// The last calculated value of the properties.
/// </summary>
private Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6> _lastValue;
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Expression<Func<TViewModel, TProperty4>> property4,
Expression<Func<TViewModel, TProperty5>> property5,
Expression<Func<TViewModel, TProperty6>> property6,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, string> message) :
this(viewModel, property1, property2, property3, property4, property5, property6, isValidFunc,
(p, v) => new ValidationText(v ? string.Empty : message(p)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Expression<Func<TViewModel, TProperty4>> property4,
Expression<Func<TViewModel, TProperty5>> property5,
Expression<Func<TViewModel, TProperty6>> property6,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool> isValidFunc,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool, string>
messageFunc) :
this(viewModel, property1, property2, property3, property4, property5, property6, isValidFunc,
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
{
}
public BasePropertyValidation(TViewModel viewModel,
Expression<Func<TViewModel, TProperty1>> property1,
Expression<Func<TViewModel, TProperty2>> property2,
Expression<Func<TViewModel, TProperty3>> property3,
Expression<Func<TViewModel, TProperty4>> property4,
Expression<Func<TViewModel, TProperty5>> property5,
Expression<Func<TViewModel, TProperty6>> property6,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool> validSelector,
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool, ValidationText>
message)
{
_message = message;
_isValidFunc = validSelector;
// add the properties used to our list
AddProperty(property1);
AddProperty(property2);
AddProperty(property3);
AddProperty(property4);
AddProperty(property5);
AddProperty(property6);
// always record the last value seen
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
// setup a connected observable to see when values change and cast that to our value subject
_valueConnectedObservable = viewModel
.WhenAnyValue(property1, property2, property3, property4, property5, property6).DistinctUntilChanged()
.Multicast(_valueSubject);
}
protected override IObservable<ValidationState> GetValidationChangeObservable()
{
Activate();
return _valueSubject.Select(value =>
{
var isValid = _isValidFunc(value);
return new ValidationState(isValid, GetMessage(value, isValid), this);
}).DistinctUntilChanged(new ValidationStateComparer());
}
protected ValidationText GetMessage(
Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6> @params, bool isValid)
{
return _message(@params, isValid);
}
/// <summary>
/// Activate the connection to ensure we start seeing validations.
/// </summary>
private void Activate()
{
if (!_connected)
{
_disposables.Add(_valueConnectedObservable.Connect());
_connected = true;
}
}
public override void Dispose()
{
_disposables.Dispose();
base.Dispose();
}
}
}

Просмотреть файл

@ -0,0 +1,148 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using System;
using System.Linq.Expressions;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ReactiveUI;
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.Components;
using ReactiveUI.Validation.States;
using ReactiveUI.Validation.Comparators;
namespace ReactiveUI.Validation.TemplateGenerators
{
<# int maxFuncLength = 6; #>
<# for(int length=2; length <= maxFuncLength; length++) { #>
<# var templParams = Enumerable.Range(1, length).Select(x => "TProperty" + x.ToString()); #>
<# string valuePropertyParams = String.Join(", ", Enumerable.Range(1, length).Select(x => String.Format("property{0}", x))); #>
<# string actionParams = String.Join(", ", Enumerable.Range(1, length).Select(x => String.Format("prop{0}", x))); #>
public sealed class BasePropertyValidation<TViewModel,<#= String.Join(",",templParams) #>> : BasePropertyValidation<TViewModel>
{
/// <summary>
/// The last calculated value of the properties.
/// </summary>
private Tuple<<#= String.Join(",",templParams) #>> _lastValue;
/// <summary>
/// Represents the current value.
/// </summary>
private readonly Subject<Tuple<<#= String.Join(",",templParams) #>>> _valueSubject = new Subject<Tuple<<#= String.Join(",",templParams) #>>>();
/// <summary>
/// The validation message factory
/// </summary>
private readonly Func<Tuple<<#= String.Join(",",templParams) #>>,bool,ValidationText> _message;
/// <summary>
/// The connected observable to see updates in properties being validated
/// </summary>
private readonly IConnectableObservable<Tuple<<#= String.Join(",",templParams) #>>> _valueConnectedObservable;
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// Are we connected
/// </summary>
private bool _connected;
/// <summary>
/// Function to determine if valid or not.
/// </summary>
private readonly Func<Tuple<<#= String.Join(",",templParams) #>>, bool> _isValidFunc;
public BasePropertyValidation(TViewModel viewModel,
<# for(int property=1;property <= length;++property) { #>
Expression<Func<TViewModel,TProperty<#=property#>>> property<#=property #>,
<# } #>
Func<Tuple<<#=String.Join(",",templParams)#>>, bool> isValidFunc,
Func<Tuple<<#=String.Join(",",templParams)#>>,string> message):
this(viewModel,<#=String.Join(",",valuePropertyParams)#>,isValidFunc,
(Tuple<<#=String.Join(",",templParams)#>> p, bool v) => new ValidationText(v ? string.Empty : message(p)))
{
}
public BasePropertyValidation(TViewModel viewModel,
<# for(int property=1;property <= length;++property) { #>
Expression<Func<TViewModel,TProperty<#=property#>>> property<#=property #>,
<# } #>
Func<Tuple<<#=String.Join(",",templParams) #>>, bool> isValidFunc,
Func<Tuple<<#=String.Join(",",templParams)#>>, bool, string> messageFunc):
this(viewModel,<#=String.Join(",",valuePropertyParams)#>,isValidFunc,(parameters,isValid) => new ValidationText(messageFunc(parameters,isValid)))
{
}
public BasePropertyValidation(TViewModel viewModel,
<# for(int property=1;property <= length;++property) { #>
Expression<Func<TViewModel,TProperty<#=property#>>> property<#=property #>,
<# } #>
Func<Tuple<<#= String.Join(",",templParams) #>>,bool> validSelector,
Func<Tuple<<#= String.Join(",",templParams) #>>,bool,ValidationText> message)
{
_message = message;
_isValidFunc = validSelector;
// add the properties used to our list
<# for(int property=1;property<=length;property++) { #>
AddProperty(property<#=property#>);
<# } #>
// always record the last value seen
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
// setup a connected observable to see when values change and cast that to our value subject
_valueConnectedObservable = viewModel.WhenAnyValue(<#=valuePropertyParams#>).DistinctUntilChanged().Multicast(_valueSubject);
}
protected override IObservable<ValidationState> GetValidationChangeObservable()
{
Activate();
return _valueSubject.
Select(value =>
{
var isValid = _isValidFunc(value);
return new ValidationState(isValid, this.GetMessage(value, isValid), this);
}).DistinctUntilChanged(new ValidationStateComparer());
}
protected ValidationText GetMessage(Tuple<<#=String.Join(",",templParams)#>> @params,bool isValid)
{
return _message(@params,isValid);
}
/// <summary>
/// Activate the connection to ensure we start seeing validations.
/// </summary>
private void Activate()
{
if (!_connected)
{
_disposables.Add(_valueConnectedObservable.Connect());
_connected = true;
}
}
public override void Dispose()
{
_disposables.Dispose();
base.Dispose();
}
}
<# } #>
}

Просмотреть файл

@ -0,0 +1,8 @@
using System;
namespace ReactiveUI.Validation.ValidationBindings.Abstractions
{
public interface IValidationBinding : IDisposable
{
}
}

Просмотреть файл

@ -0,0 +1,273 @@
using System;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using ReactiveUI.Validation.Abstractions;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
using ReactiveUI.Validation.Formatters.Abstractions;
using ReactiveUI.Validation.Helpers;
using ReactiveUI.Validation.States;
using ReactiveUI.Validation.ValidationBindings.Abstractions;
namespace ReactiveUI.Validation.ValidationBindings
{
/// <summary>
/// A validation binding.
/// </summary>
public class ValidationBinding : IValidationBinding
{
private readonly CompositeDisposable _disposables = new CompositeDisposable();
/// <summary>
/// Create an instance with a specified observable for validation changes.
/// </summary>
/// <param name="validationObservable"></param>
public ValidationBinding(IObservable<Unit> validationObservable)
{
_disposables.Add(validationObservable.Subscribe());
}
public void Dispose()
{
_disposables?.Dispose();
}
/// <summary>
/// Create a binding between a view model property and a view property.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TViewModelProperty1"></typeparam>
/// <typeparam name="TViewProperty"></typeparam>
/// <param name="view"></param>
/// <param name="viewModelProperty"></param>
/// <param name="viewProperty"></param>
/// <param name="formatter"></param>
/// <param name="strict"></param>
/// <returns></returns>
public static IValidationBinding ForProperty
<TView, TViewModel, TViewModelProperty1, TViewProperty>(TView view,
Expression<Func<TViewModel, TViewModelProperty1>>
viewModelProperty,
Expression<Func<TView, TViewProperty>>
viewProperty,
IValidationTextFormatter<string> formatter = null,
bool strict = true)
where TView : IViewFor<TViewModel>
where TViewModel : ReactiveObject, ISupportsValidation
{
if (formatter == null) formatter = SingleLineFormatter.Default;
var vcObs = view.WhenAnyValue(v => v.ViewModel)
.Where(vm => vm != null)
.Select(
viewModel =>
viewModel.ValidationContext
.ResolveFor(viewModelProperty, strict)
.ValidationStatusChange).Switch().Select(vc => formatter.Format(vc.Text));
var updateObs = BindToView(vcObs, view, viewProperty).Select(_ => Unit.Default);
return new ValidationBinding(updateObs);
}
/// <summary>
/// Binding a specified view model property to a provided action.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TViewModelProperty1"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="view"></param>
/// <param name="viewModelProperty"></param>
/// <param name="action"></param>
/// <param name="formatter"></param>
/// <param name="strict"></param>
/// <returns></returns>
public static IValidationBinding ForProperty
<TView, TViewModel, TViewModelProperty1, TOut>(TView view,
Expression<Func<TViewModel, TViewModelProperty1>>
viewModelProperty,
Action<ValidationState, TOut> action,
IValidationTextFormatter<TOut> formatter = null,
bool strict = true)
where TView : IViewFor<TViewModel>
where TViewModel : ReactiveObject, ISupportsValidation
{
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
var vcObs = view.WhenAnyValue(v => v.ViewModel).Where(vm => vm != null).Select(
viewModel =>
viewModel.ValidationContext.ResolveFor(viewModelProperty, strict)
.ValidationStatusChange).Switch()
.Select(vc => new {ValidationChange = vc, Formatted = formatter.Format(vc.Text)})
.Do(r => action(r.ValidationChange, r.Formatted)).Select(_ => Unit.Default);
return new ValidationBinding(vcObs);
}
/// <summary>
/// Create a binding between a <see cref="ValidationHelper" /> and a specified view property.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TViewProperty"></typeparam>
/// <param name="view"></param>
/// <param name="viewModelHelperProperty"></param>
/// <param name="viewProperty"></param>
/// <param name="formatter"></param>
/// <returns></returns>
public static IValidationBinding ForValidationHelperProperty
<TView, TViewModel, TViewProperty>(TView view,
Expression<Func<TViewModel, ValidationHelper>> viewModelHelperProperty,
Expression<Func<TView, TViewProperty>> viewProperty,
IValidationTextFormatter<string> formatter = null)
where TView : IViewFor<TViewModel>
where TViewModel : ReactiveObject, ISupportsValidation
{
if (formatter == null) formatter = SingleLineFormatter.Default;
var vcObs = view.WhenAnyValue(v => v.ViewModel).Where(vm => vm != null).Select(
viewModel =>
viewModel.WhenAnyValue(viewModelHelperProperty)
.SelectMany(vy => vy.ValidationChanged)).Switch()
.Select(vc => formatter.Format(vc.Text));
var updateObs = BindToView(vcObs, view, viewProperty).Select(_ => Unit.Default);
return new ValidationBinding(updateObs);
}
/// <summary>
/// Bind a <see cref="ValidationHelper" /> to a specified action.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="view"></param>
/// <param name="viewModelHelperProperty"></param>
/// <param name="action"></param>
/// <param name="formatter"></param>
/// <returns></returns>
public static IValidationBinding ForValidationHelperProperty
<TView, TViewModel, TOut>(TView view,
Expression<Func<TViewModel, ValidationHelper>> viewModelHelperProperty,
Action<ValidationState, TOut> action,
IValidationTextFormatter<TOut> formatter = null)
where TView : IViewFor<TViewModel>
where TViewModel : ReactiveObject, ISupportsValidation
{
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
var vcObs = view.WhenAnyValue(v => v.ViewModel)
.Where(vm => vm != null)
.Select(
viewModel =>
viewModel.WhenAnyValue(viewModelHelperProperty)
.SelectMany(vy => vy.ValidationChanged))
.Switch()
.Select(vc =>
new {ValidationChange = vc, Formatted = formatter.Format(vc.Text)});
var updateObs = vcObs.Do(r => { action(r.ValidationChange, r.Formatted); })
.Select(_ => Unit.Default);
return new ValidationBinding(updateObs);
}
/// <summary>
/// Create a binding between a view model and a specified action.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="view"></param>
/// <param name="action"></param>
/// <param name="formatter"></param>
/// <returns></returns>
public static IValidationBinding ForViewModel<TView, TViewModel, TOut>(TView view,
Action<TOut> action,
IValidationTextFormatter<TOut> formatter)
where TView : IViewFor<TViewModel>
where TViewModel : ReactiveObject, ISupportsValidation
{
if (formatter == null)
throw new ArgumentNullException(nameof(formatter));
var vcObs = view.WhenAnyValue(v => v.ViewModel)
.Where(vm => vm != null)
.Select(vm => vm.ValidationContext.Text)
.Select(formatter.Format);
var updateObs = vcObs.Do(action).Select(_ => Unit.Default);
return new ValidationBinding(updateObs);
}
/// <summary>
/// Create a binding between a view model and a view property.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TViewProperty"></typeparam>
/// <param name="view"></param>
/// <param name="viewProperty"></param>
/// <param name="formatter"></param>
/// <returns></returns>
public static IValidationBinding ForViewModel
<TView, TViewModel, TViewProperty>(TView view,
Expression<Func<TView, TViewProperty>> viewProperty,
IValidationTextFormatter<string> formatter = null
)
where TView : IViewFor<TViewModel>
where TViewModel : ReactiveObject, ISupportsValidation
{
if (formatter == null)
formatter = SingleLineFormatter.Default;
var vcObs = view.WhenAnyValue(v => v.ViewModel)
.Where(vm => vm != null)
.SelectMany(vm => vm.ValidationContext.ValidationStatusChange)
.Select(vc => formatter.Format(vc.Text));
var updateObs = BindToView(vcObs, view, viewProperty).Select(_ => Unit.Default);
return new ValidationBinding(updateObs);
}
public static IObservable<TValue> BindToView<TView, TViewProp, TTarget, TValue>(
IObservable<TValue> valueChange,
TTarget target,
Expression<Func<TView, TViewProp>> viewProperty)
{
var viewExpression = Reflection.Rewrite(viewProperty.Body);
var setter = Reflection.GetValueSetterOrThrow(viewExpression.GetMemberInfo());
if (viewExpression.GetParent().NodeType == ExpressionType.Parameter)
return valueChange.Do(
x => setter(target, x, viewExpression.GetArgumentsArray()),
ex =>
{
//this.Log().ErrorException(String.Format("{0} Binding received an Exception!", viewExpression), ex);
});
var bindInfo = valueChange.CombineLatest(target.WhenAnyDynamic(viewExpression.GetParent(), x => x.Value),
(val, host) => new {val, host});
return bindInfo
.Where(x => x.host != null)
.Do(
x => setter(x.host, x.val, viewExpression.GetArgumentsArray()),
ex =>
{
//this.Log().ErrorException(String.Format("{0} Binding received an Exception!", viewExpression), ex);
})
.Select(v => v.val);
}
}
}