Coder Social home page Coder Social logo

reactiveui / reactiveui.validation Goto Github PK

View Code? Open in Web Editor NEW
225.0 16.0 23.0 1.44 MB

Validation helpers for ReactiveUI-based apps.

Home Page: https://reactiveui.net/docs/handbook/user-input-validation

License: MIT License

C# 100.00%
reactiveui validation-library xamarin-forms xamarin-android xamarin-ios netcore xamarin android hacktoberfest avalonia

reactiveui.validation's Introduction

NuGet Stats Build Code Coverage #yourfirstpr Downloads Slack

ReactiveUI.Validation

Validation for ReactiveUI based solutions, functioning in a reactive way. ReactiveUI.Validation was originally developed by @jcmm33 as Vistian.Reactive.Validation, and then refactored and updated by Àlex Martínez Morón and the ReactiveUI Core Team. ReactiveUI.Validation supports all platforms, including .NET Framework, .NET Standard, .NET Core, Maui, MonoAndroid, AndroidX, Tizen, UAP, Xamarin.iOS, Xamarin.Mac, Xamarin.TVOS.

NuGet Packages

Install the following package into you class library and into a platform-specific project.

Platform ReactiveUI Package NuGet
Any Platform ReactiveUI.Validation CoreBadge
AndroidX (Maui, Xamarin) ReactiveUI.Validation.AndroidX DroXBadge
Xamarin.Android ReactiveUI.Validation.AndroidSupport DroBadge

How to Use

  • For ViewModels which need validation, implement IValidatableViewModel.
  • Add validation rules to the ViewModel using the ValidationRule extension methods.
  • Bind to the validation rules in the View via BindValidation or INotifyDataErrorInfo.

Example

  1. Decorate existing ViewModel with IValidatableViewModel, which has a single member, ValidationContext. The ValidationContext contains all of the functionality surrounding the validation of the ViewModel. Most access to the specification of validation rules is performed through extension methods on the IValidatableViewModel interface. Then, add validation to the ViewModel.
using ReactiveUI.Validation.Extensions;

public class SampleViewModel : ReactiveObject, IValidatableViewModel
{
    public SampleViewModel()
    {
        // Creates the validation for the Name property.
        this.ValidationRule(
            viewModel => viewModel.Name,
            name => !string.IsNullOrWhiteSpace(name),
            "You must specify a valid name");
    }

    public ValidationContext ValidationContext { get; } = new ValidationContext();

    private string _name;
    public string Name
    {
        get => _name;
        set => this.RaiseAndSetIfChanged(ref _name, value);
    }
}

For more complex validation scenarios there are several more overloads of the ValidationRule extension method that accept observables. These allow validation to occur asynchronously, and allows complex chains of observables to be combined to produce validation results.

The simplest accepts an IObservable<bool> where the observed boolean indicates whether the ValidationRule is valid or not. The overload accepts a message which is used when the observable produces a false (invalid) result.

IObservable<bool> passwordsObservable =
    this.WhenAnyValue(
        x => x.Password,
        x => x.ConfirmPassword,
        (password, confirmation) => password == confirmation);

this.ValidationRule(
    vm => vm.ConfirmPassword,
    passwordsObservable,
    "Passwords must match.");

Any existing observables can be used to drive a ValidationRule using the extension method overload that accepts an arbitrary IObservable<TState> streams of events. The overload accepts a custom validation function that is supplied with the latest TState, and a custom error message function, responsible for formatting the latest TState object. The syntax for this looks as follows:

// IObservable<{ Password, Confirmation }>
var passwordsObservable =
    this.WhenAnyValue(
        x => x.Password,
        x => x.ConfirmPassword,
        (password, confirmation) =>
            new { Password = password, Confirmation = confirmation });

this.ValidationRule(
    vm => vm.ConfirmPassword,
    passwordsObservable,
    state => state.Password == state.Confirmation,
    state => $"Passwords must match: {state.Password} != {state.Confirmation}");

Note The function to extract a message (messageFunc) is only invoked if the function to establish validity (isValidFunc) returns false, otherwise the message is set to string.Empty.

Finally, you can directly supply an observable that streams any object (or struct) that implements IValidationState; or you can use the ValidationState base class which already implements the interface. As the resulting object is stored directly against the context without further transformation, this can be the most performant approach:

IObservable<IValidationState> usernameNotEmpty =
    this.WhenAnyValue(x => x.UserName)
        .Select(name => string.IsNullOrEmpty(name) 
            ? new ValidationState(false, "The username must not be empty")
            : ValidationState.Valid);

this.ValidationRule(vm => vm.UserName, usernameNotEmpty);

Note As a valid ValidationState does not really require a message, there is a singleton ValidationState.Valid property that you are encouraged to use to indicate a valid state whenever possible, to reduce memory allocations.

  1. Add validation presentation to the View.
using ReactiveUI.Validation.Extensions;

public class SampleView : ReactiveContentPage<SampleViewModel>
{
    public SampleView()
    {
        InitializeComponent();
        this.WhenActivated(disposables =>
        {
            this.Bind(ViewModel, vm => vm.Name, view => view.Name.Text)
                .DisposeWith(disposables);

            // Bind any validations that reference the Name property 
            // to the text of the NameError UI control.
            this.BindValidation(ViewModel, vm => vm.Name, view => view.NameError.Text)
                .DisposeWith(disposables);

            // Bind any validations attached to this particular view model
            // to the text of the FormErrors UI control.
            this.BindValidation(ViewModel, view => view.FormErrors.Text)
                .DisposeWith(disposables);
        });
    }
}

Note Name is an <Entry />, NameError is a <Label />, and FormErrors is a <Label /> as well. All these controls are from the Xamarin.Forms library.

Example with Android Extensions

There are extensions methods for Xamarin.Android and its Material design control TextInputLayout. These extensions use internally the Error property from the TextInputLayout control, allowing you to implement a fully native behavior to showing validation errors. To use these extensions you must import ReactiveUI.Validation.Extensions and install either ReactiveUI.Validation.AndroidSupport or ReactiveUI.Validation.AndroidX:

dotnet add package ReactiveUI.Validation.AndroidX

Note In ReactiveUI.Validation 1.7 and lower, the Android-specific extensions are available in the main package targeting MonoAndroid90, and you don't need to install ReactiveUI.Validation.AndroidSupport. In ReactiveUI.Validation 1.7 and lower, add using ReactiveUI.Validation.Platforms.Android instead of using ReactiveUI.Validation.Extensions.

// This using directive makes Android-specific extensions available.
// Make sure to install either the ReactiveUI.Validation.AndroidSupport
// package or the ReactiveUI.Validation.AndroidX package.
using ReactiveUI.Validation.Extensions;

public class SignUpActivity : ReactiveAppCompatActivity<SignUpViewModel>
{
    // The Android native text boxes declared in an .axml file.
    public TextInputEditText Password { get; set; }
    public TextInputEditText ConfirmPassword { get; set; }

    // The layouts wrapping the text boxes declared in an .axml file.
    public TextInputLayout PasswordField { get; set; }
    public TextInputLayout ConfirmPasswordField { get; set; }

    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.Main);

        // The WireUpControls method is a magic ReactiveUI utility method for Android, see:
        // https://www.reactiveui.net/docs/handbook/data-binding/xamarin-android/wire-up-controls
        this.WireUpControls();
        this.Bind(ViewModel, x => x.Password, x => x.Password.Text);
        this.Bind(ViewModel, x => x.ConfirmPassword, x => x.ConfirmPassword.Text);

        // Bind any validations which reference the Password property 
        // to the Error property of the TextInputLayout control.
        this.BindValidation(ViewModel, x => x.Password, PasswordField);
        this.BindValidation(ViewModel, x => x.ConfirmPassword, ConfirmPasswordField);
    }
}

INotifyDataErrorInfo Support

For those platforms that support the INotifyDataErrorInfo interface, ReactiveUI.Validation provides a helper base class named ReactiveValidationObject. The helper class implements both the IValidatableViewModel interface and the INotifyDataErrorInfo interface. It listens to any changes in the ValidationContext and invokes INotifyDataErrorInfo events.

using ReactiveUI.Validation.Extensions;

public class SampleViewModel : ReactiveValidationObject
{
    public SampleViewModel()
    {
        this.ValidationRule(
            viewModel => viewModel.Name, 
            name => !string.IsNullOrWhiteSpace(name),
            "Name shouldn't be null or white space.");
    }

    private string _name = string.Empty;
    public string Name
    {
        get => _name;
        set => this.RaiseAndSetIfChanged(ref _name, value);
    }
}

Note Keep in mind that INotifyDataErrorInfo is only supported via XAML binding. ReactiveUI binding doesn't use the inbuilt classes of WPF.

When using a ValidationRule overload that accepts an observable, please remember to supply the property which the validation rule is targeting as the first argument. Otherwise it is not possible for INotifyDataErrorInfo to conclude which property the error message is for.

this.ValidationRule(
    vm => vm.ConfirmPassword,
    passwordsObservable,
    "Passwords must match.");

Custom Formatters

You can pass an instance of IValidationTextFormatter<T> to a call to BindValidation if you'd like to override the default SingleLineFormatter used in the validation library. The SingleLineFormatter accepts a separator char and uses whitespace by default, so the code snippet below shows how to use a non-default separator char:

// This formatter is based on the default SingleLineFormatter but uses a custom separator char.
var formatter = new SingleLineFormatter(Environment.NewLine);
this.BindValidation(ViewModel, x => x.ErrorLabel.Text, formatter)
    .DisposeWith(disposables);

The simplest possible custom IValidationTextFormatter<TOut> implementation may look like this one.

private class ConstFormatter : IValidationTextFormatter<string>
{
    private readonly string _text;

    public ConstFormatter(string text = "The input is invalid.") => _text = text;

    public string Format(ValidationText validationText) => _text;
}

// This formatter is based on a custom IValidationTextFormatter implementation.
var formatter = new ConstFormatter("The input is invalid.");
this.BindValidation(ViewModel, x => x.ErrorLabel.Text, formatter)
    .DisposeWith(disposables);

If you'd like to override the IValidationTextFormatter<string> used in ReactiveUI.Validation by default, register an instance of IValidationTextFormatter<string> into Locator.CurrentMutable before your app starts. This could be useful in cases when your app needs localization and you wish to pass message keys instead of messages to ValidationRule calls.

// Register a singleton instance of IValidationTextFormatter<string> into Splat.Locator.
Locator.CurrentMutable.RegisterConstant(new CustomFormatter(), typeof(IValidationTextFormatter<string>));

Capabilities

In essence, ReactiveUI.Validation is a relatively simple model of the ValidationContext containing a list of IValidationComponent instances. An IValidationComponent provides an observable of IValidationState. Whenever validation state changes (either a transition of validity) or ValidationText changes, then a new value is pushed out.

  1. Rules can be composed of single or multiple properties along with more generic Observables.
  2. Validation text can encapsulate both valid and invalid states.
  3. Binding can occur to either a View or an action.
  4. Validation text can reference either the ViewModel or properties which comprise the validation rule e.g. include text entered as part of validation message.
  5. Validation text output can be adjusted using custom formatters, not only allowing for single & multiline output but also for platforms like Android it should be possible to achieve richer renderings i.e. Bold/italics.

Feedback

Please use GitHub issues for questions, comments, or bug reports.

Contribute

ReactiveUI.Validation is developed under an OSI-approved open source license, making it freely usable and distributable, even for commercial use. We ❤ the people who are involved in this project, and we’d love to have you on board, especially if you are just getting started or have never contributed to open-source before.

So here's to you, lovely person who wants to join us — this is how you can support us:

Copyright and License

Code released under the MIT license.

reactiveui.validation's People

Contributors

alexmartinezm avatar chrispulman avatar dependabot-preview[bot] avatar dependabot[bot] avatar dtwk2 avatar glennawatson avatar jasonwurzel avatar kronic avatar lixfeld avatar lszczygielek avatar mallibone avatar renovate[bot] avatar rlittlesii avatar thargy avatar worldbeater avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reactiveui.validation's Issues

WPF ValidationRule is not working when ShowDialog window

platform: .net wpf
version: ReactiveUI.WPF 13.2.2, Validation 2.1.1

Describe the bug
xxxWindow win = new xxxxWindow(); -----> inherit from ReactiveValidationObject
win.ShowDialog(); ---->ValidationRule is not working
win.Show(); ------> It's ok

[Question] Correct data validation with collection

The following classes exist:

public class ParentEntity: ReactiveValidationObject
{
    public string ParentName {get; set;}
    public ObservableCollection<ChildEntity> Items { get; }
}

public class ChildEntity: ReactiveValidationObject
{
    public string Name {get; set;}
}

How can I write a ValidationRule for ParentEntity that also automatically checks the Collection "Items"?

feature: Remove Component from ValidationState, add IValidationState

Is your feature request related to a problem? Please describe.
At the core of ReactiveUI.Validation are IValidationComponents that expose IObservable<ValidationState>. All the ValidationRule extensions fundamentally boil down to producing a ValidationState when necessary. However, it is not an ideal extensibility point because ValidationState is a sealed class, which includes a back-reference to the 'owner' Component.

The ability to produce an ValidationStates directly would be of great benefit. Even better would be for the API to accept an interface IValidationState which does not require the back reference to the owner IValidationComponent. This would allow consumption of observables that implement IValidationState, maximising efficiency, as compared to using one of the existing ValidationRule extension methods to manufacture the ValidationState class for each change in validation (see for example the new method added in #116, which requires two functions to extract the validity and text from a TState).

Describe the solution you'd like
I suggest that a new IValidationState interface is created:

    public interface IValidationState
    {
        bool IsValid { get; }
        ValidationText Text { get; }
    }

This interface should be used wherever ValidationState is currently used in the library.

ValidationState should implement IValidationState, but the Component property should be removed. This would then be a utility struct for consumers to easily provide a default implementation of IValidationState. There is also a strong case for changing ValidationState into a struct - it being a single pointer and a bool, and having a sensible default state (though care should be taken to handle a null Text); alternatively, the class should not be sealed, to allow it to be used as a base class for implementors.

A new ValidationRule extension should be added that that accepts an IObservable<IValidationState>.

Describe alternatives you've considered
This is not a required feature, so no alternatives are required, however, it should be considered that accepting an interface will allow for consumers to supply simple Observables of any struct/class that implements that interface, and not use one of the more complex overloads.

Further, the Compoment property appearing in the child element is code smell as there is a forward and back reference between Component and ValidationState, this is generally considered bad practice as it allows for data to become inconsistent, which can introduce bugs.

Describe suggestions on how to achieve the feature
A review of ValidationState.Component shows that the only meaningful usage is in ReactiveValidationObject.OnValidationStatusChange, a private method call, called from an observable in the constructor:

/// <summary>
/// Initializes a new instance of the <see cref="ReactiveValidationObject"/> class.
/// </summary>
/// <param name="scheduler">Scheduler for OAPHs and for the the ValidationContext.</param>
protected ReactiveValidationObject(IScheduler? scheduler = null)
{
ValidationContext = new ValidationContext(scheduler);
var initial = new ValidationState(true, string.Empty, ValidationContext);
ValidationContext.Validations
.ToObservableChangeSet()
.ToCollection()
.Select(components => components
.Select(component => component.ValidationStatusChange)
.Merge()
.StartWith(initial))
.Switch()
.Subscribe(OnValidationStatusChange);
}

This can be rewritten to pass the IValidationCompoment to the method so that it does not need to get it from the IValidationState.

Additional context
Example usage:

// IObservable<ValidationState> where ValidationState implements IValidationState
var passwordsObservable =
    this.WhenAnyValue(
        x => x.Password,
        x => x.ConfirmPassword,
        (password, confirmation) => new ValidationState(
				state.Password == state.Confirmation,
				$"Passwords must match: {state.Password} != {state.Confirmation}"));

this.ValidationRule(
    vm => vm.ConfirmPassword,
    passwordsObservable);

As noted, this is an improvement over the existing example, in that it only produces one ValidationState object, and does not need to make two function calls on each change. It is also very extensible as any object that implements IValidationState can be consumed.

feature: Make `ValidationText` immutable, and add `Empty` property.

Describe the solution you'd like
Currently ValidationText is mutable, allowing additional texts to be added, or for the collection to be cleared. This functionality is not currently used in the library and has performance and thread-safety implications.

Describe alternatives you've considered
Having an alternative immutable implementation that can be used interchangeably with the existing mutable version, does not remove the thread-safety issues of accessing the collection whilst it may be being mutated.

Describe suggestions on how to achieve the feature
The Add() and Clear() methods should be removed, and the underlying list changed to a nullable array of _texts (null for empty). Although this is a breaking change, it is worth it as it allows a static readonly ValidationText.Empty singleton property to be added and reduces memory utilisation.

Ideally, the current constructors should be deprecated for a factory method that returns Empty when supplied with an empty enumerable.

Additional context

Make Add and Clear operations immutable.

When Add() & Clear() are obsolesced this should be made read-only.

// TODO When Add() & Clear() are obsolesced this should be made read-only.
private /* readonly */ string[] _texts;
/// <summary>
/// Initializes a new instance of the <see cref="ValidationText"/> class.
/// </summary>


This comment was generated by todo based on a TODO comment in 2c54af4 in #144. cc @thargy.

Originally posted by @todo in #144 (comment)

feature: Add a MAUI app to samples

Is your feature request related to a problem? Please describe.

Xamarin.Forms will ship a new major version later this year, and continue to ship minor and service releases every 6 weeks through .NET 6 GA in November 2021. The final release of Xamarin.Forms will be serviced for a year after shipping, and all modern work will shift to .NET MAUI. — https://devblogs.microsoft.com/dotnet/introducing-net-multi-platform-app-ui/

Since Xamarin.Forms is going to be replaced by MAUI, probably worth creating a sample app that demonstrates how to use MAUI with ReactiveUI.Validation. Preferably the new app targeting MAUI should live inside https://github.com/reactiveui/ReactiveUI.Validation/tree/main/samples and reuse the view models from LoginApp as Avalonia, UWP, WPF and Windows Forms sample apps do.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/build-samples.yml
.github/workflows/ci-build.yml
.github/workflows/lock.yml
  • dessant/lock-threads v5
.github/workflows/release.yml
nuget
samples/Directory.build.props
  • Roslynator.Analyzers 4.11.0
  • stylecop.analyzers 1.2.0-beta.556
samples/LoginApp.Avalonia/LoginApp.Avalonia.csproj
  • MessageBox.Avalonia 3.1.5.1
samples/LoginApp.Uwp/LoginApp.Uwp.csproj
  • ReactiveUI.Uwp 18.4.1
  • Microsoft.NETCore.UniversalWindowsPlatform 6.2.14
samples/LoginApp.WinForms/LoginApp.WinForms.csproj
samples/LoginApp.Wpf/LoginApp.Wpf.csproj
  • MahApps.Metro 2.4.10
samples/LoginApp/LoginApp.csproj
src/Directory.build.props
  • Roslynator.Analyzers 4.11.0
  • stylecop.analyzers 1.2.0-beta.556
  • Nerdbank.GitVersioning 3.6.133
  • Microsoft.SourceLink.GitHub 8.0.0
src/ReactiveUI.Validation.AndroidSupport/ReactiveUI.Validation.AndroidSupport.csproj
src/ReactiveUI.Validation.AndroidX/ReactiveUI.Validation.AndroidX.csproj
src/ReactiveUI.Validation.Tests/ReactiveUI.Validation.Tests.csproj
  • DiffEngine 15.3.0
  • coverlet.msbuild 6.0.2
  • xunit.runner.visualstudio 2.5.7
  • Verify.Xunit 23.4.0
  • PublicApiGenerator 11.1.0
  • Microsoft.Reactive.Testing 6.0.0
  • Xunit.StaFact 1.1.11
  • xunit.runner.console 2.7.0
  • xunit 2.7.0
  • Microsoft.NET.Test.Sdk 17.9.0
src/ReactiveUI.Validation/ReactiveUI.Validation.csproj
  • ReactiveUI 19.5.1
  • ReactiveUI 19.5.41
src/global.json
  • dotnet-sdk 8.0.10
  • MSBuild.Sdk.Extras 3.0.44

  • Check this box to trigger a request for Renovate to run again on this repository

feature: Add ValidationRule extensions method overloads to accept any object implementing IValidationState

Is your feature request related to a problem? Please describe.
The ValidationRule extension methods added in #130 only accept and IValidationState, more usefully, they should accept any object/struct that implements that interface (including ValidationState).

Describe the solution you'd like
Adding the following 2 overloads will significantly increase the usability of the API:

        /// <summary>
        /// Setup a validation rule with a general observable based on <see cref="IValidationState"/>.
        /// </summary>
        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
        /// <typeparam name="TValue">Validation observable type.</typeparam>
        /// <param name="viewModel">ViewModel instance.</param>
        /// <param name="validationObservable">Observable to define if the viewModel is valid or not.</param>
        /// <returns>Returns a <see cref="ValidationHelper"/> object.</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, TValue>(
            this TViewModel viewModel,
            IObservable<TValue> validationObservable)
            where TViewModel : IReactiveObject, IValidatableViewModel
            where TValue : IValidationState
        {
            if (viewModel is null)
            {
                throw new ArgumentNullException(nameof(viewModel));
            }

            if (validationObservable is null)
            {
                throw new ArgumentNullException(nameof(validationObservable));
            }

            return viewModel.RegisterValidation(
                new ObservableValidation<TViewModel, bool>(
                    validationObservable.Select(s => s as IValidationState)));
        }

        /// <summary>
        /// Setup a validation rule with a general observable based on <see cref="IValidationState"/>.
        /// </summary>
        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
        /// <typeparam name="TViewModelProp">ViewModel property type.</typeparam>
        /// <typeparam name="TValue">Validation observable type.</typeparam>
        /// <param name="viewModel">ViewModel instance.</param>
        /// <param name="viewModelProperty">ViewModel property referenced in viewModelObservableProperty.</param>
        /// <param name="validationObservable">Observable to define if the viewModel is valid or not.</param>
        /// <returns>Returns a <see cref="ValidationHelper"/> object.</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, TViewModelProp, TValue>(
            this TViewModel viewModel,
            Expression<Func<TViewModel, TViewModelProp>> viewModelProperty,
            IObservable<TValue> validationObservable)
            where TViewModel : IReactiveObject, IValidatableViewModel
            where TValue : IValidationState
        {
            if (viewModel is null)
            {
                throw new ArgumentNullException(nameof(viewModel));
            }

            if (viewModelProperty is null)
            {
                throw new ArgumentNullException(nameof(viewModelProperty));
            }

            if (validationObservable is null)
            {
                throw new ArgumentNullException(nameof(validationObservable));
            }

            return viewModel.RegisterValidation(
                new ObservableValidation<TViewModel, bool, TViewModelProp>(
                    viewModelProperty, validationObservable.Select(v => v as IValidationState)));
        }

These will now accept observables such as IObservable<ValidationState.

Describe alternatives you've considered
Without these methods consumers need to write code like this:

IObservable<ValidationState> observable = ...;
model.ValidationRule(observable.Select(v => v as IValidationState));

Describe suggestions on how to achieve the feature
Implementation above.

feature: Upgrade Android.Support to AndroidX

Currently ReactiveUI.Validation only support the Android.Support Library most ReactiveUI Packages are updated to use AndroidX

It would be great if ReactiveUI.Validation supports AndroidX

Add support for INotifyDataErrorInfo

Is your feature request related to a problem? Please describe.

INotifyDataErrorInfo isn't currently supported by ReactiveUI and ReactiveUI.Validation, although one could write a helper class as shown below in the "Additional Context" section.

INotifyDataErrorInfo is supported by WPF, by Xamarin.Forms and also by AvaloniaUI, so worth providing reactive validation helpers (e.g. use IObservables instead of events) compatible with INotifyDataErrorInfo as well. Worth noting, that such validation mechanics aren't supported by e.g. UWP so we should restrict the usage of such helpers on unsupported platforms.

Describe the solution you'd like

Probably we could create a base class for view models that support validation, as follows:

public abstract class ReactiveValidationObject 
  : ReactiveObject, 
    ISupportsValidation,
    INotifyDataErrorInfo {
  // implementation details
}

But open to any other suggestions. Also worth looking through ReactiveValidation library concepts.

Additional context

Proposed INotifyDataErrorInfo usage with AvaloniaUI:

public class MainWindowViewModel : ReactiveValidationObject<MainWindowViewModel>
{
    [Reactive] 
    public string Message { get; set; }
    
    public MainWindowViewModel()
    {
        this.ValidationRule(
            x => x.Message, 
            x => x.Length > 0,
            "Message shouldn't be empty.");
    }
}

AvaloniaUI markup:

<StackPanel Margin="10">
    <TextBlock FontSize="16" Text="INotifyDataErrorInfo Validation"/>
    <TextBox Watermark="Message" Text="{Binding Message}"/>
</StackPanel>

Beware, this works only with string properties, to support all other types and to remove generic type arguments we need to change ReactiveUI.Validation code.
image

Add multiple validations to a single property

Is your feature request related to a problem? Please describe.
Actually, if you try to add two validations to the same property, eg Password, in the ValidationContext you would do it like this:

[]
this.ValidationRule(
     vm => vm.Password,
     password => !string.IsNullOrEmpty(password),
     "Password is required.");

this.ValidationRule(
     vm => vm.Password,
     password => password.Length > 5,
     "Password minimum length is 5.");
[]

And from the View you would bind to the Password property like this:

[]
this.BindValidation(ViewModel, vm => vm.Password, view => view.PasswordErrorMessage.Text)
    .DisposeWith(disposables);
[]

The problem by doing this is that from the ValidationBindings, used by the ViewForExtensions, it looks up only for the first or default validation rule (see ResolveFor methods in ValidationContextExtensions).

Describe the solution you'd like
Add support for multiple validations in the same property since it's supported in the ValidationContext side.

Describe suggestions on how to achieve the feature
Basically, if more than one property matches the query in the ResolveFor method then unless the Formatter has been provided, it should throw a custom exception like MultipleValidationNotSupportedException.

On the other side, we should provide Mixins to give support for multiple validation scenarios.

[BUG] Property validations backed by ModelObservableValidationBase are not bound to view

Describe the bug

When using a complex validation rule specifying a property name, e.g.

this.ValidationRule(
                m => m.TextInput2,
                m => m.WhenAnyValue(m1 => m1.TextInput1, m1 => m1.TextInput2).Select(both => both.Item1 == both.Item2),
                (vm, isValid) => isValid ? string.Empty : "Both inputs should be the same");

and then

this.BindValidation(ViewModel, vm => vm.TextInput2, view => view.TextInput2Error.Text);

results in no errors in view.TextInput2Error.

This happens because ValidationContext extensions ResolveFor and ResolveForMultiple have not been updated to use the IPropertyValidationComponent interface.

This line:

.OfType<BasePropertyValidation<TViewModel, TViewModelProperty>>()

skips over ModelObservableValidationBase objects.

Steps To Reproduce

Use example mentioned before.

Expected behavior

Validation should bind to view.

Screenshots

Environment

  • OS: Windows / .NET Core 3.0
  • Device: Windows
  • Version: commit aa218ee
  • Working Version: None

but the bug is present on all platforms.

Additional context

I have fixed this bug by refactoring ValidationContext extensions to use the IPropertyValidationComponent interface. I attached the refactored extensions which use the GetPropertyPath extension mentioned in issue #60.

NOTE: There is another bug lurking in the ValidationContext extensions. The overloads with multiple property expressions have typos which reuse TProperty1 instead of TProperty2 or TProperty3:

Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
Expression<Func<TViewModel, TProperty1>> viewModelProperty2)

Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
Expression<Func<TViewModel, TProperty1>> viewModelProperty2,
Expression<Func<TViewModel, TProperty1>> viewModelProperty3)

Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
Expression<Func<TViewModel, TProperty1>> viewModelProperty2)

Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
Expression<Func<TViewModel, TProperty1>> viewModelProperty2,
Expression<Func<TViewModel, TProperty1>> viewModelProperty3)

I know you might be in the process of refactoring property validation, but if you are ready, I'll make a pull request.

ValidationContextExtensions.cs:

// <copyright file="ReactiveUI.Validation/src/ReactiveUI.Validation/Extensions/ValidationContextExtensions.cs" company=".NET Foundation">
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// </copyright>

using System;
using System.Linq;
using System.Linq.Expressions;
using ReactiveUI.Validation.Components.Abstractions;
using ReactiveUI.Validation.Contexts;
using ReactiveUI.Validation.Exceptions;

namespace ReactiveUI.Validation.Extensions
{
    /// <summary>
    /// Extensions methods for <see cref="ValidationContext"/>.
    /// </summary>
    public static class ValidationContextExtensions
    {
        /// <summary>
        /// Resolves the property valuation for a specified property.
        /// </summary>
        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
        /// <typeparam name="TViewModelProperty">ViewModel property type.</typeparam>
        /// <param name="context">ValidationContext instance.</param>
        /// <param name="viewModelProperty">ViewModel property.</param>
        /// <param name="strict">Indicates if the ViewModel property to find is unique.</param>
        /// <returns>Returns a <see cref="IPropertyValidationComponent{TViewModel}"/> object if has a single validation property,
        /// otherwise will throw a <see cref="MultipleValidationNotSupportedException"/> exception.</returns>
        /// <exception cref="MultipleValidationNotSupportedException">
        /// Thrown if the ViewModel property has more than one validation associated.
        /// </exception>
        public static IPropertyValidationComponent<TViewModel> ResolveFor<TViewModel, TViewModelProperty>(
            this ValidationContext context,
            Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
            bool strict = true)
        {
            var validations = context.Validations
                .OfType<IPropertyValidationComponent<TViewModel>>()
                .Where(v => v.ContainsProperty(viewModelProperty, strict))
                .ToList();

            if (validations.Count > 1)
            {
                throw new MultipleValidationNotSupportedException(viewModelProperty.Body.GetPropertyPath());
            }

            return validations[0];
        }

        /// <summary>
        /// Resolves the property valuation for two properties.
        /// </summary>
        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
        /// <typeparam name="TProperty1">ViewModel first property type.</typeparam>
        /// <typeparam name="TProperty2">ViewModel second property type.</typeparam>
        /// <param name="context">ValidationContext instance.</param>
        /// <param name="viewModelProperty1">First ViewModel property.</param>
        /// <param name="viewModelProperty2">Second ViewModel property.</param>
        /// <returns>Returns a <see cref="IPropertyValidationComponent{TViewModel}"/> object if has a single validation property,
        /// otherwise will throw a <see cref="MultipleValidationNotSupportedException"/> exception.</returns>
        /// <exception cref="MultipleValidationNotSupportedException">
        /// Thrown if the ViewModel property has more than one validation associated.
        /// </exception>
        public static IPropertyValidationComponent<TViewModel> ResolveFor<TViewModel, TProperty1,
            TProperty2>(
            this ValidationContext context,
            Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
            Expression<Func<TViewModel, TProperty2>> viewModelProperty2)
        {
            var validations = context
                .Validations
                .OfType<IPropertyValidationComponent<TViewModel>>()
                .Where(v =>
                    v.ContainsProperty(viewModelProperty1) && v.ContainsProperty(viewModelProperty2)
                    && v.PropertyCount == 2)
                .ToList();

            if (validations.Count > 1)
            {
                throw new MultipleValidationNotSupportedException(
                    viewModelProperty1.Body.GetPropertyPath(),
                    viewModelProperty2.Body.GetPropertyPath());
            }

            return validations[0];
        }

        /// <summary>
        /// Resolves the property valuation for three properties.
        /// </summary>
        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
        /// <typeparam name="TProperty1">ViewModel first property type.</typeparam>
        /// <typeparam name="TProperty2">ViewModel second property type.</typeparam>
        /// <typeparam name="TProperty3">ViewModel third property type.</typeparam>
        /// <param name="context">ValidationContext instance.</param>
        /// <param name="viewModelProperty1">First ViewModel property.</param>
        /// <param name="viewModelProperty2">Second ViewModel property.</param>
        /// <param name="viewModelProperty3">Third ViewModel property.</param>
        /// <returns>Returns a <see cref="IPropertyValidationComponent{TViewModel}"/> object if has a single validation property,
        /// otherwise will throw a <see cref="MultipleValidationNotSupportedException"/> exception.</returns>
        /// <exception cref="MultipleValidationNotSupportedException">
        /// Thrown if the ViewModel property has more than one validation associated.
        /// </exception>
        public static IPropertyValidationComponent<TViewModel> ResolveFor<TViewModel,
            TProperty1, TProperty2, TProperty3>(
            this ValidationContext context,
            Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
            Expression<Func<TViewModel, TProperty2>> viewModelProperty2,
            Expression<Func<TViewModel, TProperty3>> viewModelProperty3)
        {
            var validations = context
                .Validations
                .OfType<IPropertyValidationComponent<TViewModel>>()
                .Where(v =>
                    v.ContainsProperty(viewModelProperty1) && v.ContainsProperty(viewModelProperty2)
                                                           && v.ContainsProperty(viewModelProperty3)
                                                           && v.PropertyCount == 3)
                .ToList();

            if (validations.Count > 1)
            {
                throw new MultipleValidationNotSupportedException(
                    viewModelProperty1.Body.GetPropertyPath(),
                    viewModelProperty2.Body.GetPropertyPath(),
                    viewModelProperty3.Body.GetPropertyPath());
            }

            return validations[0];
        }
    }
}

ValidationContextMultipleExtensions.cs:

// <copyright file="ReactiveUI.Validation/src/ReactiveUI.Validation/Extensions/ValidationContextMultipleExtensions.cs" company=".NET Foundation">
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// </copyright>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ReactiveUI.Validation.Components.Abstractions;
using ReactiveUI.Validation.Contexts;

namespace ReactiveUI.Validation.Extensions
{
    /// <summary>
    /// Extensions methods for <see cref="ValidationContext"/> which supports multiple validations.
    /// </summary>
    public static class ValidationContextMultipleExtensions
    {
        /// <summary>
        /// Resolves all the properties valuations for a specified property.
        /// </summary>
        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
        /// <typeparam name="TViewModelProperty">ViewModel property type.</typeparam>
        /// <param name="context">ValidationContext instance.</param>
        /// <param name="viewModelProperty">ViewModel property.</param>
        /// <param name="strict">Indicates if the ViewModel property to find is unique.</param>
        /// <returns>Returns a <see cref="IPropertyValidationComponent{TViewModel}"/> object.</returns>
        public static IEnumerable<IPropertyValidationComponent<TViewModel>> ResolveForMultiple<TViewModel,
            TViewModelProperty>(
            this ValidationContext context,
            Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
            bool strict = true)
        {
            var validations = context.Validations
                .OfType<IPropertyValidationComponent<TViewModel>>()
                .Where(v => v.ContainsProperty(viewModelProperty, strict));

            return validations;
        }

        /// <summary>
        /// Resolves the property valuation for two properties.
        /// </summary>
        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
        /// <typeparam name="TProperty1">ViewModel first property type.</typeparam>
        /// <typeparam name="TProperty2">ViewModel second property type.</typeparam>
        /// <param name="context">ValidationContext instance.</param>
        /// <param name="viewModelProperty1">First ViewModel property.</param>
        /// <param name="viewModelProperty2">Second ViewModel property.</param>
        /// <returns>Returns a <see cref="IPropertyValidationComponent{TViewModel}"/> object.</returns>
        public static IEnumerable<IPropertyValidationComponent<TViewModel>> ResolveForMultiple<
            TViewModel,
            TProperty1, TProperty2>(
            this ValidationContext context,
            Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
            Expression<Func<TViewModel, TProperty2>> viewModelProperty2)
        {
            var validations = context
                .Validations
                .OfType<IPropertyValidationComponent<TViewModel>>()
                .Where(v => v.ContainsProperty(viewModelProperty1) && v.ContainsProperty(viewModelProperty2)
                                                                   && v.PropertyCount == 2);

            return validations;
        }

        /// <summary>
        /// Resolves the property valuation for three properties.
        /// </summary>
        /// <typeparam name="TViewModel">ViewModel type.</typeparam>
        /// <typeparam name="TProperty1">ViewModel first property type.</typeparam>
        /// <typeparam name="TProperty2">ViewModel second property type.</typeparam>
        /// <typeparam name="TProperty3">ViewModel third property type.</typeparam>
        /// <param name="context">ValidationContext instance.</param>
        /// <param name="viewModelProperty1">First ViewModel property.</param>
        /// <param name="viewModelProperty2">Second ViewModel property.</param>
        /// <param name="viewModelProperty3">Third ViewModel property.</param>
        /// <returns>Returns a <see cref="IPropertyValidationComponent{TViewModel}"/> object.</returns>
        public static IEnumerable<IPropertyValidationComponent<TViewModel>>
            ResolveForMultiple<
                TViewModel, TProperty1, TProperty2, TProperty3>(
                this ValidationContext context,
                Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
                Expression<Func<TViewModel, TProperty2>> viewModelProperty2,
                Expression<Func<TViewModel, TProperty3>> viewModelProperty3)
        {
            var validations = context
                .Validations
                .OfType<IPropertyValidationComponent<TViewModel>>()
                .Where(v => v.ContainsProperty(viewModelProperty1) && v.ContainsProperty(viewModelProperty2)
                                                                   && v.ContainsProperty(viewModelProperty3)
                                                                   && v.PropertyCount == 3);

            return validations;
        }
    }
}

feature: Add ValidationRule based on an Observable's value

Currently, I have some code that parses a URL and validates it. Obviously, this can be a slow process so is done asynchronously. A simplified version can be thought of as producing an IObservable<string> where the string represents an error if non-null.

It would be nice to add overloads to the ValidationRule extension method to accept an Observable<T> and 2 functions:

  1. Func<T, bool>, to determine if the item of type T is valid.
  2. Func<T, string>, to return the corresponding error message based on the item.

The ensures that there are no race conditions between evaluating the validity of the item, and its message. In our example, these functions would be:

  1. msg => msg != null;
  2. msg => msg;

An alternative is to define an interface IValidationState (which is implemented by ValidationState), but only defines IsValid and Text. The ValidationRule can then accept IObservable<IValidationState> and doesn't need any extractor functions. This can form the base class of the existing implementations.

Example:

I used the following implementation (note the difficulty of implementing such extensions due to IValidatesProperties<TViewModel>.ContainsProperty<TProp>(Expression<Func<TViewModel, TProp>> propertyExpression, bool exclusively = false) and the internal nature of the GetPropertyPath() extension method (I inlined in this implementation). Further, I specify associated properties on creation (rather than using an AddProperty method)

    /// <inheritdoc cref="ReactiveObject" />
    /// <inheritdoc cref="IDisposable" />
    /// <inheritdoc cref="IPropertyValidationComponent{TViewModel}" />
    /// <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 ObservableValidationRule<TViewModel, T> : ReactiveObject, IDisposable, IPropertyValidationComponent<TViewModel>
    {
        [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed by field _disposables.")]
        private readonly ReplaySubject<ValidationState> _lastValidationStateSubject =
            new ReplaySubject<ValidationState>(1);

        /// <summary>
        /// The list of property names this validator is referencing.
        /// </summary>
        private readonly HashSet<string> _propertyNames;

        // the underlying connected observable for the validation change which is published
        private readonly IConnectableObservable<ValidationState> _validityConnectedObservable;

        private readonly CompositeDisposable _disposables = new CompositeDisposable();

        private bool _isActive;

        private bool _isValid;

        private ValidationText? _text;

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelObservableValidationBase{TViewModel}"/> class.
        /// </summary>
        /// <param name="validityObservable">ViewModel instance.</param>
        public ObservableValidationRule(
            IObservable<T> validityObservable,
            Func<T, bool> isValid,
            Func<T, ValidationText> validationText,
            params string[] properties)
        {
            _isValid = true;
            _text = new ValidationText();

            _disposables.Add(_lastValidationStateSubject.Do(s =>
            {
                _isValid = s.IsValid;
                _text = s.Text;
            }).Subscribe());

            _propertyNames = new HashSet<string>(properties);

            _validityConnectedObservable = Observable.Defer(() => validityObservable)
                .Select(item => new ValidationState(isValid(item), validationText(item), this))
                .Multicast(_lastValidationStateSubject);
        }

        /// <inheritdoc/>
        public int PropertyCount => _propertyNames.Count;

        /// <inheritdoc/>
        public IEnumerable<string> Properties => _propertyNames.AsEnumerable();

        /// <inheritdoc/>
        public ValidationText? Text
        {
            get
            {
                Activate();
                return _text;
            }
        }

        /// <inheritdoc/>
        public bool IsValid
        {
            get
            {
                Activate();
                return _isValid;
            }
        }

        /// <inheritdoc/>
        public IObservable<ValidationState> ValidationStatusChange
        {
            get
            {
                Activate();
                return _validityConnectedObservable;
            }
        }

        /// <inheritdoc/>
        public void Dispose()
        {
            // Dispose of unmanaged resources.
            Dispose(true);

            // Suppress finalization.
            GC.SuppressFinalize(this);
        }

        /// <inheritdoc/>
        public bool ContainsProperty<TProp>(Expression<Func<TViewModel, TProp>> property, bool exclusively = false)
        {
            if (property is null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            var expression = property.Body;
            var path = new StringBuilder();
            while (expression is MemberExpression memberExpression)
            {
                if (path.Length > 0)
                {
                    path.Insert(0, '.');
                }

                path.Insert(0, memberExpression.Member.Name);

                expression = memberExpression.Expression;
            }

            var propertyName = path.ToString();
            return ContainsPropertyName(propertyName, exclusively);
        }

        /// <inheritdoc/>
        public bool ContainsPropertyName(string propertyName, bool exclusively = false) =>
            exclusively
                ? _propertyNames.Contains(propertyName) && _propertyNames.Count == 1
                : _propertyNames.Contains(propertyName);

        /// <summary>
        /// Disposes of the managed resources.
        /// </summary>
        /// <param name="disposing">If its getting called by the <see cref="BasePropertyValidation{TViewModel}.Dispose()"/> method.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _disposables?.Dispose();
            }
        }

        private void Activate()
        {
            if (_isActive)
            {
                return;
            }

            _isActive = true;
            _disposables.Add(_validityConnectedObservable.Connect());
        }
    }

And here is an example of it in use (without the extension method), parsedAddress is an Observable<ParsedAddress> which contain information regarding an entered address that fires sometime after it is entered (there's also a 200ms Throttle involved in its creation).

                var addressRule = new ObservableValidationRule<CameraViewModel, ParsedAddress>(
                    parsedAddress,
                    parsed => string.IsNullOrWhiteSpace(parsed.Address) || parsed.EndPoint != null,
                    parsed => new ValidationText(parsed.Message ?? string.Empty),
                    nameof(Address));
                ValidationContext.Add(addressRule);
                AddressRule = new ValidationHelper(addressRule);

[BUG] Index out of range with multiple validators

Describe the bug
When having multiple validation rules, I get an IndexOutOfRangeException at Runtime. Both validation rules work when they are enabled individually.

Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index

The problem here is that the exception does not give enough information about the reason for the exception, which makes it impossible to fix the underlying problem with the injection.

Version Information

  • .NET Framework 4.7.2
  • DynamicData 6.12.13
  • ReactiveUI 9.19.5
  • ReactiveValidation 1.2.1
  • Splat 8.2.4
  • System.Reactive 4.1.6

Steps To Reproduce

ViewModel

this.ValidationRule(vm => vm.EventId, id => Guid.TryParse(id, out var _), id => "error");
this.ValidationRule(vm => vm.EventRecurrenceId, id => Guid.TryParse(id, out var _), id => "error");

View (Code Behind)

this.BindValidation(ViewModel, vm => vm.EventId, v => v.EventIdErrorLabel.Text);
this.BindValidation(ViewModel, vm => vm.EventRecurrenceId, v => v.EventRecurrenceIdErrorLabel.Text);

View (XAML)

<TextBlock x:Name="EventIdErrorLabel" />
<TextBlock x:Name="EventRecurrenceIdErrorLabel" />

Expected behavior

ValidationContextExtensions.ResolveFor provides a more helpful error message, when the resolving fails.

Environment

  • OS: Windows 10.0.18362.0

Stacktrace

   at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
   at ReactiveUI.Validation.Extensions.ValidationContextExtensions.ResolveFor[TViewModel,TViewModelProperty](ValidationContext context, Expression`1 viewModelProperty, Boolean strict) in d:\a\1\s\src\ReactiveUI.Validation\Extensions\ValidationContextExtensions.cs:line 50
   at ReactiveUI.Validation.ValidationBindings.ValidationBinding.<>c__DisplayClass2_0`4.<ForProperty>b__2(TViewModel viewModel) in d:\a\1\s\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs:line 69

[Bug]: ReactiveValidationObject doesnt work with latest DynamicData version

Describe the bug 🐞

Instantiating ReactiveValidationObject with latest DynamicData package crashes the app.

Step to reproduce

start app from repo

Reproduction repository

https://github.com/tomasfil/ReactiveUI.Validation.DD.Crash

Expected behavior

This should happen...

Screenshots 🖼️

No response

IDE

No response

Operating system

No response

Version

No response

Device

No response

ReactiveUI Version

No response

Additional information ℹ️

No response

[Bug]: HasErrors is incorrect when performing a side effect from WhenAnyPropertyChanged

Describe the bug 🐞

        [Reactive]
        public double MaxValue { get; set; }
        
        [Reactive]
        public double MinValue { get; set; }

If I were to have the above code (code weaved to be a notify property at build time) and I were to write:

var minMaxObs = this.WhenAnyValue(v => v.MinValue, v => v.MaxValue,
      (min, max) => min < max)

this.ValidationRule(v => v.MaxValue, minMaxObs,
      "Value must be less than max value.");

this.WhenAnyPropertyChanged("MaxValue").Subscribe(_ => {
// save to database here
});

Then when inside my subscribe callback HasErrors is false when a validation error has occurred. It seems like the state of HasErrors is behind one, because if the property is updated again and still invalid, HasErrors will be true.

I've tried re-ordering my observables so that validation rules were last, that didn't work.

EDIT:
This seems to be caused by WhenAnyPropertyChanged being called before WhenAnyValue, even though the order that subscribers are set up are right. This may be an upstream issue. A workaround I found is dispatching on the UI thread the subscription of the 'WhenAnyPropertyChanged' callback. I'm using WhenAnyPropertyChanged because I have a framework I created where properties marked with a specified attribute automatically save changes to the database, without requiring me to make observables for all of them.

Step to reproduce

Create a notifiable property and subscribe to changes via ReactiveUI WhenAnyPropertyChanged extension. Notice that validation status is wrong within subscription callback.

Reproduction repository

https://github.com/reactiveui/ReactiveUI

Expected behavior

HasErrors should correctly reflect validation status.

Screenshots 🖼️

No response

IDE

No response

Operating system

Windows

Version

11

Device

PC

ReactiveUI Version

3.1.7

Additional information ℹ️

No response

ArgumentOutOfRangeException when BindValidation is called

Describe the bug
If you call BindValidation inside WhenActivated, it throws an exception on app start for both ios and android

this.WhenActivated(d =>
{
    // Exception here
    this.BindValidation(
        ViewModel,
        vm => vm.Password,
        page => page.validationLabel.Text).DisposeWith(d);
 });

here validationLabel is a Xamarin.Form Label

Error/Exception
page.validationLabel.Text Binding received an Exception! - System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.

Steps To Reproduce
Same as the example code in ReadMe

Environment
Version: Xamarin (4.6.0.726) ReactiveUI.XamForms (11.3.8) ReactiveUI.Validation (1.4.13)

Add Windows samples

Is your feature request related to a problem? Please describe.

There are no samples for WPF, Windows Forms, and UWP now. Let's fix that.

Describe the solution you'd like

We should create samples for WPF and WF.

Describe suggestions on how to achieve the feature

Probably we should port the existing XF sample to WPF and WF.

Worth sharing existing view models across many platforms.

Update

Now we have samples for WPF and Avalonia in the samples directory. Still missing the samples for Windows Forms and UWP.

feature : Multiple validation context support

Is your feature request related to a problem? Please describe.
Had a case where i needed to have validations that stop and block an external process and others that simply display the error without blocking or stopping anything. I also needed to display these validations separately.

Describe the solution you'd like
I thinked of having multiple validation contexts in a single ViewModel. To do so I made overloads for the "ValidationRule(...)" and "BindValidation(...)" methods. I added a "Func<TViewModel, ValidationContext> validationContextProperty" parameter to those methods and use it instead of the one present by default in the "IValidatableViewModel" interface.

Describe alternatives you've considered
An alternative could have been to create multiple validation classes inheriting from IValidationComponent but that also means adding methods to add them to the validation context. I would also need to create functions to filter them in order to know if a validation of type x is false.

Describe suggestions on how to achieve the feature
Made modifications of the code of this repo and uploaded everything on this github in the multiple validation context branch.
https://github.com/brignolff/Mdified.ReactiveUI.Vaidation/tree/multiple_validation_contexts

[BUG] this.IsValid() updated after ReactiveCommand is invoked

Describe the bug
When using this.IsValid() as the argument for CanExecute in a ReactiveCOmmand.Create(), the IsValid is one user input "late".

Steps To Reproduce
This is a simple ViewModel.cs to reproduce the problem, for testing I bind it to a simple WPF TextBox:

    public class ViewModel : ReactiveValidationObject<ViewModel>
    {
        private readonly ReactiveCommand<string, Unit> Command;

        private string _myProperty;
        public string MyProperty
        {
            get => _myProperty;
            set => this.RaiseAndSetIfChanged(ref _myProperty, value);
        }

        public ViewModel()
        {
            Command = ReactiveCommand.Create<string>(x => SetMyProperty(x), this.IsValid());

            this
                .WhenAnyValue(x => x.MyProperty)
                .Do(x => Console.WriteLine("Property value = " + x))
                .InvokeCommand(Command);

            this.ValidationRule(
                viewModel => viewModel.MyProperty,
                x => !string.IsNullOrEmpty(x) && x.Length > 3,
                "Enter a value");

            this.IsValid()
                .Subscribe(x => Console.WriteLine("IsValid = " + x));
        }

        private Unit SetMyProperty(string value)
        {
            Console.WriteLine("Entered method");
            return Unit.Default;
        }
    }

This is the Console output: (notice when the command is executed)
Property value = 1
Property value = 12
Property value = 123
Property value = 1234
IsValid = True
Property value = 12345
Entered method
Property value = 1234
Entered method
Property value = 123
Entered method
IsValid = False
Property value = 12
Property value = 1

Expected behavior
This is the console output I would expect:
Property value = 1
Property value = 12
Property value = 123
Property value = 1234
IsValid = True
Entered method
Property value = 12345
Entered method
Property value = 1234
Entered method
Property value = 123
IsValid = False
Property value = 12
Property value = 1

Environment

  • OS: Windows 10, WPF App .NET Framework
  • Version: Validation=1.5.5, ReactiveUI = 11.5.35

Thank you so much for your help!

[Question] How to use ReactiveValidationObject and INotifyDataErrorInfo in WPF

Hi,
I'm playing with validation using INotifyDataErrorInfo and I'm very confused and I don't know how to use it properly. I couldn't find concrete info in documentation, only sample project gives me some clues, but I don't know if it is all what I should know.

Let's say that I want to make a TextBox and default ErrorTemplate (red border) should be visible if value is empty. Based on code from sample I'm suspecting that I have to:

  • bind TextBox Text to view-model using regular XAML binding,
  • in the code behind of view I have to bind using ReactiveUI a ViewModel to DataContext.

So in short - is there another way to use INotifyDataErrorInfo using only ReactiveUi bindings? Unfortunately I can't turn on INotifyDataErrorInfo in other way than regular XAML binding, documentation didn't help me in this topic, only sample application shows me how to do this in regular XAML.

[Question] Initial List Validation / Getting IsValid

I'm trying to do something very simple. I want to validate that the Sources ObservableCollection is not empty.

var sourceNotEmpty = Sources
    .ToObservableChangeSet(x => x.Path)
    .ToCollection()
    .Select(x => x.Any());
this.ValidationRule(x => x.Sources,
    sourceNotEmpty,
    "You must select source files or folders to encode");

Testing GetErrors works except for this test. It fails because the ValidationRule hasn't yet been evaluated. If I add an item and then remove it, then it works.

[Fact]
public void GetErrors_SourceEmpty_Error()
{
    var errors = Model.GetErrors(nameof(Model.Sources));

    Assert.Single(errors);  // errors is empty
}

Then, how do I get IsValid? Before doing some work in the ViewModel, I first need to check "IsValid" but haven't yet found the way to get the current value. LastAsync won't work if it hasn't yet been evaluated.

[Fact]
public void IsValid_MissingData_ReturnsFalse()
{
    bool? result = null;

    Model.IsValid().Do(x => result = x);
    
    Assert.False(result);
}

These 2 things are extremely simple but there's probably something I'm missing with the whole paradigm?

Adding to this, on the UI, I probably don't want to display all the "field missing" errors right away, but only when he tries to click Submit or re-empties a field after typing in it.

feature: Possibility to change validation rules dymamically

Hello, firstly thanks for your great project.

It would be great if validation would support dynamic validation rules change: for example, i do deed to configure some hardware, different options are available for different models, for ex. "Model A" can be connected either via TCP/IP or via RS232, "Model B" supports RS232 only, so for the first TCP/IP settings required, for the second - RS232.

So i do need smth like:

this.WhenAnyValue(_ => _.Model)
.Subscribe(m => 
{
    this.ClearValidationRules(_ => _.IpAddress);
    this.ClearValidationRules(_ => _.Rs232Port);
   //...

  if (m == Model.A)
      this.ValidationRule(_ => _.IpAddress, _ => !string.IsNullOrEmpty(_), "IP address required");

   //...   
}
)

feature: Support ValidatesOnNotifyDataErrors/ValidatesOnDataErrors/ValidatesOnExceptions on IReactiveBindings

(Following a discussion on Slack)

Currently, to utilise 3rd library controls that do not expose their error display elements, you have to use one of the XAML binding syntaxes such as:

Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True"

Or do this in code-behind by either creating the binding, or retrieve the binding using BindingOperations.GetBinding(DependencyObject, DependencyProperty) and then accessing the ValidatesOnNotifyDataErrors, ValidatesOnDataErrors, ValidatesOnExceptions properties directly.

However, the ReactiveUI Bind system introduces IReactiveBinding<...> which does not expose these properties, nor can you retrieve a binding from the control as creating an IReactiveBinding<...> does not create a System.Windows.Data.Binding. As such you are required to Bind the control using one of the original non-reactive binding methods.

I propose expanding IReactiveBinding<...> to include these properties to better support validation scenarios. At the same time, we should review whether to expose ValidationRules on the binding (as per the original).

[ISSUE] WPF StartUri versus manual Startup

Describe the issue
When using ReactiveUI.Validation in a WPF application, it works as expected if using manual startup (window.Show or windows.ShowDialog) but if using StartUri, the first validation rule is applied/notify when windows is shown which feels like awkward UX.

Steps To Reproduce
I put the sample code in a repo https://github.com/gerardo-lijs/ReactiveUI-Validation-Sample
The last commit in master branch shows how it works with Startup manual and the previous commit how it behaves with StartUri

Expected behavior
Not sure if this is something that can be fixed in ReactiveUI.Validation or it's something in the way WPF starts the window with StartUri. Maybe just mentioning this in the docs is enough so that other developers avoid StartUri if they want to use ReactiveUI.Validation.

**Extra comment
Another user in github @kagerouttepaso found that if the ViewModel uses the TaskpoolScheduler

public MainViewModel() : base(RxApp.TaskpoolScheduler) then every validation rule is applied/notify when Window is Shown.

BindValidation throws System.NotSupportedException

Describe the bug

When calling this.BindValidation on a ReactiveUserControl, it throws the exception:
System.NotSupportedException : 'Index expressions are only supported with constants.'

Steps To Reproduce

Follow: https://www.reactiveui.net/docs/handbook/user-input-validation/
And at the end, the bindValidation will throw an error.

Expected behavior

It should not throw an error and just bind correctly

Code

ConnectView:

public partial class ConnectView : ReactiveUserControl<ConnectViewModel> {

        public ConnectView() {
            InitializeComponent();

            // Input validation
            this.WhenActivated(disposables => {

                this.BindValidation(ViewModel, viewModel => viewModel.Username, view => view.FindControl<TextBlock>("UsernameError").Text).DisposeWith(disposables);

            });

        }

        private void InitializeComponent() {
            AvaloniaXamlLoader.Load(this);
        }

    }

ConnectViewModel:

public class ConnectViewModel : ReactiveObject, IValidatableViewModel {

        public ValidationContext ValidationContext { get; } = new ValidationContext();

        [Reactive] public string Username { get; set; } = "";
        [Reactive] public string Password { get; set; } = "";

        public ConnectViewModel() {

            // Username cannot be empty
            this.ValidationRule(viewModel => viewModel.Username, username => !string.IsNullOrEmpty(username), "Username cannot be empty..");

        }

    }

And of course I have a TextBlock with the x:Name attribute with a value of "UsernameError".

Additional context
I am using Avalonia.
Thanks for your help!

feature: Add Xamarin.Android Sample App

Is your feature request related to a problem? Please describe.

Currently, we support binding validations to a TextInputLayout, but there is no sample app that shows how to use this feature.

Describe the solution you'd like

Ideally, we should add a sample app targeting Xamarin.Android (native) to our LoginApp sample solution. Before doing that, we should rename and move the existing LoginApp.Android and LoginApp.iOS projects to LoginApp.Forms.Android and LoginApp.Forms.iOS respectively as they represent the entry points for the Xamarin.Forms app LoginApp.Forms.

Describe suggestions on how to achieve the feature

Someone experienced in Android should create a new app with axml UI, use WireUpControls, and BindValidation overload that accepts a TextInputLayout. Ideally, we should target AndroidX #134 Also we shouldn't forget to create a separate LoginApp.Android solution file for the new app.

Additional context

https://github.com/reactiveui/ReactiveUI.Validation#example-with-android-extensions

feature: Bind validation to non-string view properties

Is your feature request related to a problem? Please describe.

Not related to a problem.

Describe the solution you'd like

So what I'm trying to do is change the color of control in my view based on the validation state of a validation helper. The control does have an error property or something similar, and I do not want to show an inline error message anyway for this particular situation. What I would like to be able to do is something like this:

this.BindValidation(ViewModel, viewModel => viewModel.Property, view => view.Control.Color,
    validationState => validationState.IsValid ? Color.Green : Color.Red); 

This would similar to the overload of the OneWayBind function that allows you to bind between types like so:

this.OneWayBind(ViewModel, viewModel => vm.BoolProperty, view => view.Control.Color,
    boolProperty => boolProperty ? Color.Green : Color.Red);

Describe alternatives you've considered

Currently this is how I'm achieving the same effect:

this.WhenAnyObservable(v => v.ViewModel.PropertyValidator.ValidationChanged)
    .Select(validationState => validationState.IsValid ? Color.Green : Color.Red)
    .BindTo(this, v => v.Control.Color);

This works, and I don't necessarily hate it, but I would prefer the proposed form for brevity and to match with the rest of my simple bindings.

Describe suggestions on how to achieve the feature

I'm not sure exactly how to make this work or I probably just would have made done it and made PR, but I think the signatures of the overloads for such a change would look something like this:

public static IDisposable BindValidation<TView, TViewModel, TViewModelProperty, TViewProperty>(
    this TView view,
    TViewModel? viewModel,
    Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
    Expression<Func<TView, TViewProperty>> viewProperty,
    Expression<Func<IValidationState, TViewProperty>> selector,
    IValidationTextFormatter<string>? formatter = null)
    where TView : IViewFor<TViewModel>
    where TViewModel : class, IReactiveObject, IValidatableViewModel

and this:

public static IDisposable BindValidation<TView, TViewModel, TViewProperty>(
    this TView view,
    TViewModel? viewModel,
    Expression<Func<TViewModel?, ValidationHelper?>> viewModelHelperProperty,
    Expression<Func<TView, TViewProperty>> viewProperty,
    Expression<Func<IValidationState, TViewProperty>> selector,
    IValidationTextFormatter<string>? formatter = null)
    where TView : IViewFor<TViewModel>
    where TViewModel : class, IReactiveObject, IValidatableViewModel

Additional context

WPF ValidationRule is not working when call ShowDialog

platform: .net wpf
version: ReactiveUI.WPF 13.2.2, Validation 2.1.1

Describe the bug
xxxWindow win = new xxxxWindow(); -----> inherit from ReactiveValidationObject
win.ShowDialog(); ---->ValidationRule is not working
win.Show(); ------> It's ok

Clean warnings.

Is your feature request related to a problem? Please describe.
Currently, there are over 200 warning in the solution.

Describe the solution you'd like
Let's clean all those warnings.

[BUG] Cannot bind validation to a specific ViewModel property, throws NullReferenceException

I'm trying the samples adapted to a WPF project.
I copied the ViewModel sample as-is and created a WPF view with type-safe bindings like the given android sample.

ViewModel:

public class SampleViewModel : ReactiveObject, ISupportsValidation
{
    public SampleViewModel()
    {
        // Name must be at least 3 chars. The selector is the property name and the line below is
        // a single property validator.
        this.ValidationRule(
            viewModel => viewModel.Name,
            name => !string.IsNullOrWhiteSpace(name),
            "You must specify a valid name");

        // Age must be between 13 and 100, message includes the silly length being passed in,
        // stored in a property of the ViewModel.
        AgeRule = this.ValidationRule(
            viewModel => viewModel.Age,
            age => age >= 13 && age <= 100,
            age => $"{age} is a silly age");

        var nameAndAgeValid = this
            .WhenAnyValue(x => x.Age, x => x.Name, (age, name) => new { Age = age, Name = name })
            .Select(x => x.Age > 10 && !string.IsNullOrEmpty(x.Name));

        // Create a rule from an IObservable.
        ComplexRule = this.ValidationRule(
            _ => nameAndAgeValid,
            (vm, state) => !state ? "That's a ridiculous name / age combination" : string.Empty);

        // IsValid extension method returns true when all validations succeed.
        var canSave = this.IsValid();

        // Save command is only active when all validators are valid.
        Save = ReactiveCommand.CreateFromTask(async unit => { }, canSave);
    }

    [Reactive] public int Age { get; set; }

    // Declare a separate validator for age rule.
    public ValidationHelper AgeRule { get; }

    // Declare a separate validator for complex rule.
    public ValidationHelper ComplexRule { get; }

    [Reactive] public string Name { get; set; }

    public ReactiveCommand<Unit, Unit> Save { get; }

    // Initialize validation context that manages reactive validations.
    public ValidationContext ValidationContext { get; } = new ValidationContext();
}

View:

<rxui:ReactiveWindow x:Class="Sncf.RealTimeTrain.Views.MainWindow"
                     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                     xmlns:rxui="http://reactiveui.net"
                     xmlns:vms="clr-namespace:Sncf.RealTimeTrain.ViewModels;assembly=Sncf.RealTimeTrain.Shared"
                     d:DataContext="{d:DesignInstance Type=vms:SampleViewModel}"
                     d:DesignHeight="450"
                     d:DesignWidth="800"
                     x:TypeArguments="vms:SampleViewModel"
                     mc:Ignorable="d">
    <StackPanel Margin="20">
        <TextBox x:Name="NameTextBox" Margin="5" />
        <TextBlock x:Name="ErrorsTextBlock" Margin="5" />
    </StackPanel>
</rxui:ReactiveWindow>
public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
        this.WhenActivated(disposables =>
        {
            this.Bind(ViewModel, vm => vm.Name, view => view.NameTextBox.Text);
            
            // This works fine
            this.BindValidation(ViewModel, view => view.ErrorsTextBlock.Text);

            // This fails with a NullReferenceException
            this.BindValidation(ViewModel, vm => vm.Name, view => view.ErrorsTextBlock.Text);
        });
    }
}

I'm using the latest stable version of ReactiveUI 9.12.1 and the latest validation version 1.0.3:

    <PackageReference Include="ReactiveUI" Version="9.12.1" />
    <PackageReference Include="ReactiveUI.Fody" Version="9.12.1" />
    <PackageReference Include="ReactiveUI.Validation" Version="1.0.3" />

Also with the one that works, every validation message is appended to each other without a NewLine or anything. How do I customize this? I can open another issue if needed.
Capture

[BUG] readme says to use ValidationRule on ReactiveValidationObject but it doesn't exist

Describe the bug
The readme says has tells you that for INotifyDataErrorInfo you can inherit ReactiveValidationObject and call this.ValidationRule to add validation rules.

However ReactiveValidationObject doesn't have a ValidationRule method and so the example does not compile:

It can be seen from https://github.com/reactiveui/ReactiveUI.Validation/blob/main/src/ReactiveUI.Validation/Helpers/ReactiveValidationObject.cs which doesn't have a method called ValidationRule

public class SampleViewModel : ReactiveValidationObject
{
    public SampleViewModel()
    {
        // this line from the readme doesn't compile because ValidationRule doesn't exist.
        this.ValidationRule(
            viewModel => viewModel.Name, 
            name => !string.IsNullOrWhiteSpace(name),
            "Name shouldn't be null or white space.");
    }

    private string _name = string.Empty;
    public string Name
    {
        get => _name;
        set => this.RaiseAndSetIfChanged(ref _name, value);
    }
}

Steps To Reproduce
Us the sample from the readme
-> notice it doesn't compile because it uses a method that doesn't exist.

Expected behavior
I should be able to follow the readme and have a working validation example

Environment

  • OS: ubuntu 20.04
  • Device: Linux desktiop
  • Version: 2.2.1
  • UI toolkit: Avalonia

Increase code coverage

Is your feature request related to a problem? Please describe.

Currently, codecov reports the following coverage percentage for ReactiveUI.Validation:

Code Coverage

Describe the solution you'd like

Let's increase coverage to ensure further pull requests won't break existing API etc.

Probably ~40-50% will be fine enough, but higher coverage is better.

Describe suggestions on how to achieve the feature

Let's write more tests!

[BUG] ValidationContext is not producing updates.

Describe the bug
ValidationContext does produce updates and current values do not reflect correct validation status or update in response to property changes.
Tested in a UWP project.

Steps To Reproduce
Create a Simple ViewModel and View with associated Bindings.
Create simple validation rules for the ViewModel
Create subscriptions to various Validation public APIs (ValidationContext.ValidationStatusChange, ValidationHelper.ValidationChange, etc.)
Run project and update text in View Controls
Observe that the behavior isn't as expected.

Expected behavior
I'd expect the ValidationContext to produce Observable sequeneces indicating the current status of the ViewModel's validation.

Environment

  • OS: Windows 10
  • Version 1809+
  • Device: Custom build PC and Surface Pro 4

Additional context
I'm seeing updates to ValidationHelper.ValidationChanged in accordance with the associated Validation Rule, but no updates beyond the initial values from ValidationContext.

I've attached a bare bones project I've been using to identify this.
ReactiveUI.Validation.Sample.Uwp.zip

[BUG] Validation by property "error sharing" when last property is the same

Describe the bug

Using 2 validation rules ending with the same property name, e.g.

this.ValidationRule(
            viewModel => viewModel.Origin.Name,
            name => !string.IsNullOrWhiteSpace(name),
            "You must specify a valid origin name");

this.ValidationRule(
            viewModel => viewModel.Destination.Name,
            name => !string.IsNullOrWhiteSpace(name),
            "You must specify a valid destination name");

and then

this.BindValidation(ViewModel, vm => vm.Origin.Name, view => view.OriginNameError.Text);
this.BindValidation(ViewModel, vm => vm.Destination.Name, view => view.DestinationNameError.Text);

results in view.OriginNameError and view.DestinationNameError having all "*.Name" errors.

Steps To Reproduce

Use example mentioned before.

Expected behavior

Errors with the same last property name should not "share" errors.

Screenshots

Environment

  • OS: Windows / .NET Core 3.0
  • Device: Windows
  • Version: commit aa218ee
  • Working Version: None

but the bug is present on all platforms.

Additional context

I have fixed this bug by replacing all instances of "Body.GetMemberInfo().Name" in the code by "Body.GetPropertyPath()", which returns "Origin.Name" and "Destination.Name" instead of only the "Name".

I know you might be in the process of refactoring property validation, but if you are ready, I'll make a pull request.

GetPropertyPath code:

// <copyright file="ReactiveUI.Validation/src/ReactiveUI.Validation/Extensions/ExpressionExtensions.cs" company=".NET Foundation">
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// </copyright>

using System.Linq.Expressions;
using System.Text;

namespace ReactiveUI.Validation.Extensions
{
    /// <summary>
    /// Extensions methods associated to <see cref="Expression"/> instances.
    /// </summary>
    public static class ExpressionExtensions
    {
        /// <summary>
        /// Returns a property path expression as a string.
        /// </summary>
        /// <param name="expression">The property path expression.</param>
        /// <returns>The property path string representing the expression.</returns>
        public static string GetPropertyPath(this Expression expression)
        {
            var path = new StringBuilder();
            while (expression is MemberExpression memberExpression)
            {
                if (path.Length > 0)
                    path.Insert(0, '.');

                path.Insert(0, memberExpression.Member.Name);

                expression = memberExpression.Expression;
            }
            return path.ToString();
        }
    }
}

[BUG] BindValidationEx not working as expected

Describe the bug
I'm attempting to use BindValidationEx to bind to a specific property's validation in a Desktop WPF application

It appears that the issue described in #19 is still occurring for BindValidationEx if ViewModel is assigned and activated after view construction. Also, even by forcing it to work by passing ImmediateScheduler.Instance to the ValidationContext constructor it introduces an exception as soon as validation changes.

Steps To Reproduce
Provide the steps to reproduce the behavior:

  1. Open, build, and run the attached project.
  2. Click on 'Set ViewModel' button
  3. Note that no validation error is set in the second text box. Related question, it does not seem possible to bind to the TextBox's Text property directly, a dependency property was necessary on the view.
  4. In ViewModel.cs add ImmediateScheduler.Instance to the ValidationContext construction.
  5. Rebuild and run again.
  6. Notice the second TextBox now displays "You must specify 'Yes'" as expected
    7.Type Yes into the first textbox. This leads to the following exception:

System.Reflection.TargetInvocationException
HResult=0x80131604
Message=Exception has been thrown by the target of an invocation.
Source=mscorlib
StackTrace:
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
at ReactiveUI.Reflection.TrySetValueToPropertyChain[TValue](Object target, IEnumerable1 expressionChain, TValue value, Boolean shouldThrow) in d:\a\1\s\src\ReactiveUI\Expression\Reflection.cs:line 324 at ReactiveUI.PropertyBinderImplementation.<>c__DisplayClass12_05.b__7(Tuple2 isVmWithLatestValue) in d:\a\1\s\src\ReactiveUI\Bindings\Property\PropertyBinderImplementation.cs:line 596 at System.Reactive.AnonymousSafeObserver1.OnNext(T value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\AnonymousSafeObserver.cs:line 43
at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49 at System.Reactive.IdentitySink1.OnNext(T value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\IdentitySink.cs:line 15
at System.Reactive.Subjects.Subject1.OnNext(T value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Subjects\Subject.cs:line 150 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.IdentitySink1.OnNext(T value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\IdentitySink.cs:line 15 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.Linq.ObservableImpl.Where1.Predicate._.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\Where.cs:line 54 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.Linq.ObservableImpl.Select2.Selector._.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\Select.cs:line 48 at System.Reactive.Linq.ObservableImpl.Merge1.Observables._.InnerObserver.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\Merge.cs:line 238
at System.Reactive.Linq.ObservableImpl.Select2.Selector._.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\Select.cs:line 48 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.Linq.ObservableImpl.Select2.Selector._.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\Select.cs:line 47 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.Linq.ObservableImpl.DistinctUntilChanged2._.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\DistinctUntilChanged.cs:line 72 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.Linq.ObservableImpl.Select2.Selector._.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\Select.cs:line 47 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.Linq.ObservableImpl.Where1.Predicate._.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\Where.cs:line 54 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.Linq.ObservableImpl.Switch1._.InnerObserver.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\Switch.cs:line 100 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.IdentitySink1.OnNext(T value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\IdentitySink.cs:line 15 at System.Reactive.Sink1.ForwardOnNext(TTarget value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\Sink.cs:line 49
at System.Reactive.Linq.ObservableImpl.Select2.Selector._.OnNext(TSource value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Linq\Observable\Select.cs:line 47 at System.Reactive.AutoDetachObserver1.OnNextCore(T value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\Internal\AutoDetachObserver.cs:line 61
at System.Reactive.ObserverBase`1.OnNext(T value) in D:\a\1\s\Rx.NET\Source\src\System.Reactive\ObserverBase.cs:line 34
at ReactiveUI.DependencyObjectObservableForProperty.<>c__DisplayClass1_1.b__1(Object o, EventArgs e) in d:\a\1\s\src\ReactiveUI.WPF\DependencyObjectObservableForProperty.cs:line 56
at MS.Internal.ComponentModel.PropertyChangeTracker.OnPropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args)
at System.Windows.DependentList.InvalidateDependents(DependencyObject source, DependencyPropertyChangedEventArgs sourceArgs)
at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
at System.Windows.DependencyObject.SetCurrentDeferredValue(DependencyProperty dp, DeferredReference deferredReference)
at System.Windows.Controls.TextBox.OnTextContainerChanged(Object sender, TextContainerChangedEventArgs e)
at System.Windows.Documents.TextContainerChangedEventHandler.Invoke(Object sender, TextContainerChangedEventArgs e)
at System.Windows.Documents.TextContainer.EndChange(Boolean skipEvents)
at System.Windows.Documents.TextContainer.System.Windows.Documents.ITextContainer.EndChange(Boolean skipEvents)
at System.Windows.Documents.TextRangeBase.EndChange(ITextRange thisRange, Boolean disableScroll, Boolean skipEvents)
at System.Windows.Documents.TextRange.System.Windows.Documents.ITextRange.EndChange(Boolean disableScroll, Boolean skipEvents)
at System.Windows.Documents.TextRange.ChangeBlock.System.IDisposable.Dispose()
at System.Windows.Documents.TextEditorTyping.DoTextInput(TextEditor This, String textData, Boolean isInsertKeyToggled, Boolean acceptControlCharacters)
at System.Windows.Documents.TextEditorTyping.TextInputItem.Do()
at System.Windows.Documents.TextEditorTyping.ScheduleInput(TextEditor This, InputItem item)
at System.Windows.Documents.TextEditorTyping.OnTextInput(Object sender, TextCompositionEventArgs e)
at System.Windows.Controls.Primitives.TextBoxBase.OnTextInput(TextCompositionEventArgs e)
at System.Windows.UIElement.OnTextInputThunk(Object sender, TextCompositionEventArgs e)
at System.Windows.Input.TextCompositionEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
at System.Windows.Input.InputManager.ProcessStagingArea()
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
at System.Windows.Input.TextCompositionManager.UnsafeCompleteComposition(TextComposition composition)
at System.Windows.Input.TextCompositionManager.PostProcessInput(Object sender, ProcessInputEventArgs e)
at System.Windows.Input.InputManager.RaiseProcessInputEventHandlers(ProcessInputEventHandler postProcessInput, ProcessInputEventArgs processInputEventArgs)
at System.Windows.Input.InputManager.ProcessStagingArea()
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
at System.Windows.Input.TextCompositionManager.UnsafeStartComposition(TextComposition composition)
at System.Windows.Input.TextCompositionManager.PostProcessInput(Object sender, ProcessInputEventArgs e)
at System.Windows.Input.InputManager.RaiseProcessInputEventHandlers(ProcessInputEventHandler postProcessInput, ProcessInputEventArgs processInputEventArgs)
at System.Windows.Input.InputManager.ProcessStagingArea()
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
at System.Windows.Interop.HwndKeyboardInputProvider.ProcessTextInputAction(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at System.Windows.Interop.HwndSource.OnPreprocessMessage(Object param)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at System.Windows.Threading.Dispatcher.Invoke(DispatcherPriority priority, Delegate method, Object arg)
at System.Windows.Interop.HwndSource.OnPreprocessMessageThunk(MSG& msg, Boolean& handled)
at System.Windows.Interop.HwndSource.WeakEventPreprocessMessage.OnPreprocessMessage(MSG& msg, Boolean& handled)
at System.Windows.Interop.ThreadMessageEventHandler.Invoke(MSG& msg, Boolean& handled)
at System.Windows.Interop.ComponentDispatcherThread.RaiseThreadMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at TestViews.App.Main()

Inner Exception 1:
InvalidOperationException: Sequence contains no matching element

Environment

  • OS: Windows
  • Version 10
  • Device: Desktop

Additional context
BindValidationEx.zip

feature: add benchmarks

Might be useful to get some benchmarks using benchmark dot net

I would place them in the /benchmarks and in their own solution to make it easier in the build server.

[BUG] UIKit.UIKitThreadAccessException in iOS as canExecute: this.IsValid() doesn't tick in MainThread

Describe the bug
UIKit.UIKitThreadAccessException in iOS as canExecute: this.IsValid() doesn't tick in MainThread

Steps To Reproduce
Bind Command to any Xamarin Forms Button,
SomeCommand = ReactiveCommand.CreateFromObservable(
observable,
canExecute: this.IsValid());
UIKit.UIKitThreadAccessException is thrown

Expected behavior
No thrown UIKit.UIKitThreadAccessException

Environment

  • OS: iOS 13.4
  • Version: Xamarin Forms 4.4.0.991864, Xamarin.iOS 13.16.0.13
  • Working Version: unknown

Additional context
Workaround:

SomeButtonCommand = ReactiveCommand.CreateFromObservable(
    observable,
    canExecute: this.IsValid().ObserveOn(RxApp.MainThreadScheduler));

[BUG] ValidationContex is not updated properly.

Describe the bug
When I change the property value in the ViewModel with the ReactiveValidationObject<> the validation works wrongly on TextBox with NotifyOnValidationError=True, ValidatesOnDataErrors=True. When I change TextBox property directly via keyboard its working perfectly.
When I started to debug, I noticed that the ValidationContext has not up to date information.

Steps To Reproduce

I created a repository with the bug https://github.com/ScarletKuro/ValidationContextBug
It has ReactiveValidationObjectBugViewModel with the bug, and the ClassicValidationViewModel that implements IDataErrorInfo and works correctly, just to make sure the bug is not related with something else. I know that ReactiveValidationObject implements the INotifyDataErrorInfo, but it still should work the same, because if I make IDataErrorInfo to work with ValidationContex it gets the same buggy behaviour.

Provide the steps to reproduce the behavior:
Simple code
ViewModel:

public class ReactiveValidationObjectBugViewModel : ReactiveValidationObject<ReactiveValidationObjectBugViewModel>
{
        [Reactive]
        public string CustomerCode { get; set; } = string.Empty;
        public ReactiveCommand<Unit, string> Numeric0Command { get; }
		public ReactiveCommand<Unit, string> Clear { get; }

        public ReactiveValidationObjectBugViewModel() : base(Scheduler.Immediate)
        {
            Numeric0Command = ReactiveCommand.Create(() => CustomerCode += 0);
            Clear = ReactiveCommand.Create(() => CustomerCode = string.Empty);

            this.ValidationRule(viewModel => viewModel.CustomerCode,
                customerCode => !string.IsNullOrEmpty(customerCode), "You need to enter customer code");
        }
}

View xaml:
<TextBox Text="{Binding CustomerCode, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True, Mode=TwoWay}"/>

Current buggy behavior
As you can notice, when we have a value the Text is red. When the TextBox is empty its not red. But when I type more then it gets updated.

Expected behavior

Environment

  • OS: Windows 10
  • Working Version: Validation=1.5.5, ReactiveUI = 11.5.35, ReactiveUI.Fody = 11.5.35, ReactiveUI.WPF = 11.3.35

[BUG] ValidationContext's Validations property is always empty

Describe the bug

I'm working in a WinForms project, having the ViewModel project as an F# project, and I'm try to use ReactiveUI.Validations as saw in your login sample, but when I try to bind the same property on View with BindValidation, the library throws an expection because cannot find any validation by index. Then, after debugging step by step, even when the private field _validationSource have items in it, it doesn't propage to the Validations property and always is empty.

Special Characteristics:

  • ReactiveUI.Validation version is 1.2.1
  • Projects are .NET Framework 4.7.2
  • WinForms project is a C# project.
  • ViewModel project is an F# project.
  • I've created an abstract ValidatableReactiveObject class that implements the ISupportsValidation interface, and then used on other ViewModels.
  • Validation works if I get the ValidationHelper result and bind my controls to those, but I have many bindable validations and I prefer to avoid to have those helpers in the way if I can...

Steps To Reproduce

Expected behavior

Screenshots

Environment

  • OS: Windows 10
  • Visual Studio 2017
  • Version 1.2.1
  • Device:

Additional context

Repository Sample: https://github.com/Micha-kun/ReactiveUI.Validation.BugTest

PD: I'll fill later with more data, sorry

[BUG] Build failure for uap10.0.17763 target

Describe the bug
On a clean checkout, get the following errors on build.

1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Components\BasePropertyValidation.cs(28,123,28,163): warning CS0618: 'IPropertyValidationComponent<TViewModel>' is obsolete: 'Consider using the non-generic version of an IPropertyValidationComponent.'
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Components\ObservableValidation.cs(240,133,240,173): warning CS0618: 'IPropertyValidationComponent<TViewModel>' is obsolete: 'Consider using the non-generic version of an IPropertyValidationComponent.'
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Components\BasePropertyValidation.cs(283,34,283,118): warning CS0618: 'ValidationState.ValidationState(bool, ValidationText, IValidationComponent)' is obsolete: 'This constructor overload is going to be removed soon.'
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\TemplateGenerators\PropertyValidationGenerator.cs(131,24,131,86): warning CS0618: 'ValidationState.ValidationState(bool, ValidationText, IValidationComponent)' is obsolete: 'This constructor overload is going to be removed soon.'
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\TemplateGenerators\PropertyValidationGenerator.cs(277,24,277,86): warning CS0618: 'ValidationState.ValidationState(bool, ValidationText, IValidationComponent)' is obsolete: 'This constructor overload is going to be removed soon.'
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\TemplateGenerators\PropertyValidationGenerator.cs(769,24,769,86): warning CS0618: 'ValidationState.ValidationState(bool, ValidationText, IValidationComponent)' is obsolete: 'This constructor overload is going to be removed soon.'
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Helpers\ReactiveValidationObject.cs(93,17,94,32): error CS0012: The type 'IAsyncOperation<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Helpers\ReactiveValidationObject.cs(93,17,94,32): error CS0012: The type 'IAsyncOperationWithProgress<,>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Helpers\ReactiveValidationObject.cs(96,17,98,32): error CS0012: The type 'IAsyncOperation<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Helpers\ReactiveValidationObject.cs(96,17,98,32): error CS0012: The type 'IAsyncOperationWithProgress<,>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\TemplateGenerators\PropertyValidationGenerator.cs(432,24,432,86): warning CS0618: 'ValidationState.ValidationState(bool, ValidationText, IValidationComponent)' is obsolete: 'This constructor overload is going to be removed soon.'
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\TemplateGenerators\PropertyValidationGenerator.cs(596,24,596,86): warning CS0618: 'ValidationState.ValidationState(bool, ValidationText, IValidationComponent)' is obsolete: 'This constructor overload is going to be removed soon.'
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Contexts\ValidationContext.cs(56,13,58,27): error CS0012: The type 'CoreDispatcher' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.UniversalApiContract, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Contexts\ValidationContext.cs(56,13,58,27): error CS0012: The type 'DependencyObject' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.UniversalApiContract, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\Contexts\ValidationContext.cs(89,30,89,77): warning CS0618: 'ValidationState.ValidationState(bool, ValidationText, IValidationComponent)' is obsolete: 'This constructor overload is going to be removed soon.'
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(74,25,77,28): error CS0012: The type 'IAsyncOperation<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(74,25,77,28): error CS0012: The type 'IAsyncOperationWithProgress<,>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(131,25,134,28): error CS0012: The type 'IAsyncOperation<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(131,25,134,28): error CS0012: The type 'IAsyncOperationWithProgress<,>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(188,34,190,36): error CS0012: The type 'IAsyncOperationWithProgress<,>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(245,34,247,36): error CS0012: The type 'IAsyncOperationWithProgress<,>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(291,25,294,28): error CS0012: The type 'IAsyncOperation<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(291,25,294,28): error CS0012: The type 'IAsyncOperationWithProgress<,>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(335,25,338,28): error CS0012: The type 'IAsyncOperation<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>C:\Source\ReactiveUI.Validation\src\ReactiveUI.Validation\ValidationBindings\ValidationBinding.cs(335,25,338,28): error CS0012: The type 'IAsyncOperationWithProgress<,>' is defined in an assembly that is not referenced. You must add a reference to assembly 'Windows.Foundation.FoundationContract, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime'.
1>Done building project "ReactiveUI.Validation.csproj" -- FAILED.

They all relate to IAsyncOperation and IAsyncOperationWithProgress being missing on the target.

These disappeared when removing the uap10.0.17763 target from the project file.

Steps To Reproduce
Clean check out, build.

Expected behavior
Build without errors.

Environment

  • OS: Windows 10

[ISSUE] Redesign ISupportsValidation

Describe the issue
It is currently not easily possible to overwrite the ValidationContext class for unit testing. It would be better to make it sealed and implement a base interface to allow for inversion of control (IOC) in unit tests.

Instead of

public interface ISupportsValidation
  {
    /// <summary>Gets get the validation context.</summary>
    ValidationContext ValidationContext { get; }
  }

public class ValidationContext : ReactiveObject, IDisposable, IValidationComponent
{
    // ...
}

use

public interface IValidationContext : IDisposable, IValidationComponent
{
     // ....
}

public sealed class ValidationContext : ReactiveObject, IValidationContext
{
    // ...
}

public interface ISupportsValidation
  {
    /// <summary>Gets get the validation context.</summary>
    IValidationContext ValidationContext { get; }
  }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.