[RxUI] 15 – 用户输入验证

[RxUI] 15 – 用户输入验证

ReactiveUI本身提供了一些强大的功能,可以即时验证用户输入。使用WhenAnyValue,可以侦听视图模型属性的更改并控制ReactiveCommand的可执行性。当响应式命令的CanExecute可观察对象返回false时,与该命令绑定的控件将会进入禁用状态。最简单的验证器如下所示:

// Declare name validator as IObservable<bool> which emitts a new value when name changes.
var nameValid = this.WhenAnyValue(x => x.Name, name => !string.IsNullOrWhiteSpace(name));

// The reactive command will stay disabled while name is invalid.
var saveName = ReactiveCommand.CreateFromTask(_ => Save(this.Name), canExecute: nameValid);

这绝对适用于简单的场景,但是对于较大的表单和更复杂的验证,肯定需要尝试ReactiveUI.Validation包。

ReactiveUI.Validation

这是进行验证的主要方式。该包包含了基于ReactiveUI的解决方案的验证帮助,并以响应式来提供。ReactiveUI.Validation源码在GitHub上。该包支持所有平台,包括.NET Framework、.NET Standard、MonoAndroid、Tizen、UAP、Xamarin.iOS、Xamarin.Mac、Xamarin.TVOS。将以下包安装到类库和特定于平台的项目中。

平台 ReactiveUI包 NuGet
所有平台 ReactiveUI.Validation NuGet
AndroidX (Xamarin) ReactiveUI.Validation.AndroidX NuGet
Xamarin.Android ReactiveUI.Validation.AndroidSupport NuGet

入门

  1. 将现有的ViewModel继承IValidatableViewModel接口,该接口仅有一个成员ValidationContextValidationContext包含ViewModel验证所需的所有功能。对验证规则规范的大部分访问是通过IValidatableViewModel接口上的扩展方法执行的。然后,将验证添加到ViewModel。
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);
    }
}

对于更复杂的验证方案,ValidationRule扩展方法还有更多的重载可以接收可观察对象。允许验证异步发生,并允许将复杂的可观察对象链组合以产生验证结果。最简单的方法时接受IObservable<bool>,其中观察到的布尔值用来指示ValidationRule是否有效。重载可以接受一条消息,当可观察对象产生false(无效)结果时使用该消息。

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

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

使用扩展方法的重载(可以接受任意的IObservable<TState>事件流),可以使用任何现有的可观察对象来驱动ValidationRule。重载接受最新的TState提供的自定义验证函数,以及负责格式化最新的TState对象的自定义错误消息函数。其语法如下所示:

// 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}");

注释 仅当建立有效性的函数(isValidFunc)返回false时,才调用提取消息的函数(messageFunc),否则将消息设置为string.Empty

最后,可以直接提供一个可观察对象,该对象可以以流传输实现IValidationState接口的任何对象(或结构);或者可以使用已实现该接口的ValidationState基类。由于结果对象是直接针对上下文存储的,无需进一步转换,因此这可能是性能最高的方法:

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);

注释 由于有效的ValidationState确实不需要消息,因此建议使用单例ValidationState.Valid属性来表示有效状态,以减少内存消耗。

  1. 将验证展示添加到View。
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);
        });
    }
}

注释 Name是一个<Entry />,NameError是一个<Label />,FormErrors也是一个<Label />。所有这些控件都来自Xamarin.Forms库。

ReactiveUI.Validation扩展使用

ReactiveUI.Validation还支持XAML平台使用的INotifyDataErrorInfo验证,包括WPF和Avalonia;绑定到Xamarin.Android和AndroidX中的TextInputLayout控件,自定义格式化还可以用于本地化。请参见ReactiveUI.Validation GitHub页面,来了解有关此ReactiveUI库的更多用法信息:

备选方案

还可以按照David Britch的文章中说的使用Xamarin验证。其它比较出色的用于用户输入验证的工具还有FluentValidationSprache

原文 https://www.reactiveui.net/docs/handbook/user-input-validation

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注