[英]Validation on ReactiveUi 5
我使用的是ReactiveUI 5,但现在我需要在ViewModel中进行验证,因此我按照文档中的描述使用了ReactiveValidatedObject。 在第4版示例中进行了相同的配置,但似乎不起作用。 运行示例代码效果很好,但是在版本5中却无法运行,因此不会触发ValidatesViaMethod Attribute中定义的验证方法。
我通过简单的文本框验证对所有内容及其与示例代码相同的内容进行了仔细检查,但一无所获。
我不知道还有什么可以做的。 还有其他方法可以使用ReactiveUI进行验证吗? 除了版本4,我找不到任何文档或执行该操作的示例。
这是我的ViewMode,我正在使用版本4中的ReactiveValidatedObject,并从版本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); }
}
}
如果您需要对验证的更多控制,建议您尝试一下FluentValidation 。 它可以与任何MVVM框架很好地集成,并且与基于属性的验证相比,通过使用InlineValidator,您可以处理更复杂的验证方案。 我在大多数项目中都在使用它。
如果要将FluentValidation
与ReactiveUI
一起使用,则可以执行以下操作:
验证器
public sealed class ContactViewModelValidator
: AbstractValidator<ContactViewModel>
{
pulbic ContactViewModelValidator()
{
RuleFor(vm => vm.FirstName)
.Required()
.WithMessage("The first name is required");
// more rules
}
}
视图模型
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);
}
}
扩展
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());
}
}
视图
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}" />
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.