Question

I was using the ReactiveUI 5, but now i need to do Validation in my ViewModel, so I used the ReactiveValidatedObject as described in documentations. The configuration was made as tha same in the version 4 sample, but it seems not to work. Running the sample code works well, but with version 5 it does not work, my validation method defined in the ValidatesViaMethod Attribute is not fired.

I double checked every thing and its equal to the sample code, with a simple textbox validation and nothing.

I don't know any other thing that could be made. Is there any other way to do validation with ReactiveUI? I can't find any documentation or examples of how to do it except for version 4.

This is my ViewMode, I'm using the ReactiveValidatedObject from version 4 and routing from version 5.

public class InputViewModel : ReactiveValidatedObject , IRoutableViewModel
{
    bool val;
    bool invalid = false;

    public InputViewModel(IScreen hostscreen)
    {            
        ValidationObservable.Subscribe(x => IsValid = this.IsObjectValid());                
        var whenAnyValuesChange = this.WhenAny(x => x.IsValid, x => x.Value);                       
        HostScreen = hostscreen ?? RxApp.DependencyResolver.GetService<IScreen>();
    }

    [ValidatesViaMethod(AllowBlanks = false, AllowNull = false, Name = "IsNameValid", ErrorMessage = "Favor informe o nome corretamente")]
    public string Name
    {
        get {  return _name; }
        set 
        {
            this.RaiseAndSetIfChanged(ref _name, value);
        }
    }

    public bool IsNameValid(string name)
    {
        return name.Length >= 2;
    }

    public IScreen HostScreen
    {
        get;
        private set;
    }

    public string UrlPathSegment
    {
        get { return "InputView"; }
    }

    public Simulation Simulation { get; set; }

    private bool _IsValid;
    public bool IsValid
    {
        get { return _IsValid; }
        set { this.RaiseAndSetIfChanged(ref _IsValid, value); }
    }
}
Was it helpful?

Solution

If you need more control over validation, I'd suggest giving FluentValidation a try. It integrates nicely with any MVVM framework and by using InlineValidator you can tackle more complex validation scenarios compared to property based validation. I'm using it myself in most of my projects.

OTHER TIPS

If you want to use FluentValidation with ReactiveUI, you can do something like this:

Validator

public sealed class ContactViewModelValidator 
    : AbstractValidator<ContactViewModel>
{
    pulbic ContactViewModelValidator()
    {
        RuleFor(vm => vm.FirstName)
            .Required()
            .WithMessage("The first name is required");
        // more rules
    }
}

ViewModel

public sealed class ContactViewModel : ReactiveObject, ISupportsActivation
{
    public ViewModelActivator Activator { get; } = new ViewModelActivator();

    [Reactive] public string FirstName { get; set; }
    [Reactive] public string FirstNameErrorMessage { get; set; }

    // other properties

    private IValidator<ContactViewModel> Validator { get; }

    public ContactViewModel(IValidator<ContactViewModel> validator)
    {
        Validator = validator ?? throw new ArgumentNullException(nameof(validator));

         this.WhenActivated(d =>
         {
             ActivateValidation(this, d);

             // other activations
         });
    }

    // since this is static, you can put it in an external class
    private static void ActivateValidation(ContactViewModel viewModel, CompositeDisposable d)
    {
        var validationSubject = new Subject<ValidationResult>().DisposeWith(d);
        viewModel.WhenAnyValue(vm => vm.FirstName /* other properties */)
            .Select(_ => viewModel)
            .Select(viewModel.Validator.Validate)
            .ObserveOn(RxApp.MainThreadScheduler)
            .SubscribeOn(RxApp.MainThreadScheduler)
            .Subscribe(result => validationSubject.OnNext(result))
            .DisposeWith(d);

        validationSubject
            .Select(e => e.Errors)
            .ObserveOn(RxApp.MainThreadScheduler)
            .SubscribeOn(RxApp.MainThreadScheduler)
            .Subscribe(errors =>
            {
                using (viewModel.DelayChangeNotifications())
                {
                    viewModel.FirstNameErrorMessage = 
                        errors.GetMessageForProperty(nameof(viewModel.FirstName));

                    // handle other properties
                }
            })
            .DisposeWith(d);
    }
}

Extensions

public static class ValidationFailureExtensions
{
    // This is an example that gives you all messages,
    // no matter if they are warnings or errors.
    // Provide your own implementation that fits your need.
    public static string GetMessageForProperty(this IList<ValidationFailure> errors, string propertyName)
    {
        return errors
            .Where(e => e.PropertyName == propertyName)
            .Select(e => e.ErrorMessage)
            .Aggregate(new StringBuilder(), (builder, s) => builder.AppendLine(s), builder => builder.ToString());
    }
}

View

public partial class ContactControl : IViewFor<ContactViewModel>
{
    public ContactControl()
    {
        InitializeComponent();
    }

    object IViewFor.ViewModel
    {
        get => ViewModel;
        set => ViewModel = value as ContactViewModel;
    }

    public ContactViewModel ViewModel
    {
        get => DataContext as ContactiewModel;
        set => DataContext = value;
    }
}
d:DataContext="{d:DesignInstance Type=local:ContactViewModel, IsDesignTimeCreatable=True}" 
<UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="..." />
                <ResourceDictionary>
                    <Style BasedOn="{StaticResource {x:Type TextBlock}}" 
                           TargetType="TextBlock"
                           x:Key="ErrorMessageTextBlock">
                            <Style.Setters>
                                <Setter Property="Foreground" Value="Red" />
                                <Setter Property="Height" Value="Auto" />
                                <Setter Property="TextWrapping" Value="Wrap" />
                                <Setter Property="Padding" Value="4" />
                        </Style.Setters>
                            <Style.Triggers>
                                <Trigger Property="Text" Value="{x:Null}">
                                    <Setter Property="Height" Value="0" />
                                    <Setter Property="Visibility" Value="Collapsed" />
                                </Trigger>
                                <Trigger Property="Text" Value="">
                                    <Setter Property="Height" Value="0" />
                                    <Setter Property="Visibility" Value="Collapsed" />
                                </Trigger>
                            </Style.Triggers>
                    </Style>
                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
<TextBlock Text="{Binding FirstNameErrorMessage}"
           Style="{StaticResource ErrorMessageTextBlock}" />
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top