
Prism 实时数据验证的相关内容。结合之前对 Prism 导航服务、模块化开发、模块间通信、对话服务、事件聚合器、区域、数据绑定、绑定转换器、验证规则和异步验证的提问我将提供一个精简且聚焦的回答详细介绍 Prism 中实现实时数据验证的示例基于 WPF 环境强调实时反馈即用户输入时立即验证并更新 UI。本示例将结合INotifyDataErrorInfo和异步验证避免重复之前的内容如异步验证的密码强度示例并提供新的场景例如实时验证用户名和邮箱。我还会包括代码、测试代码、适用场景和使用方法。如果您有特定需求如与导航/区域的集成、特定验证规则或复杂测试场景请进一步说明我会定制答案。一、Prism 实时数据验证概述Prism 实时数据验证通过 WPF 的INotifyDataErrorInfo接口和数据绑定机制实现结合BindableBase提供属性变更通知允许在用户输入时立即验证数据并更新 UI。实时验证通常通过以下方式实现属性绑定使用UpdateSourceTriggerPropertyChanged确保输入实时触发验证。异步验证结合async/await处理耗时验证如 API 调用。错误通知通过INotifyDataErrorInfo动态通知 UI 错误。MVVM 集成与 Prism 的 MVVM 模式无缝结合支持模块化开发。实时验证的关键是快速响应用户输入提供即时反馈如错误提示或验证状态。二、适用场景实时数据验证适用于以下场景表单输入如用户名、邮箱或密码的实时验证。动态 UI 反馈输入时显示错误消息或高亮无效控件。远程验证如检查用户名是否已被占用。模块化应用模块中的视图模型需要实时验证。用户体验优化通过即时反馈提升表单交互体验。三、实时数据验证示例以下是一个实时验证用户名和邮箱的示例结合异步 API 调用和本地规则验证。1. 项目配置安装 Prism NuGet 包如Prism.DryIoc。创建 Prism 应用程序继承PrismApplication.确保视图模型继承BindableBase。2. 定义模拟 API 服务模拟异步 API 调用以验证用户名是否可用IValidationService.csusingSystem.Threading.Tasks;namespaceWpfApp.Services{publicinterfaceIValidationService{TaskboolIsUsernameAvailableAsync(stringusername);}}ValidationService.csusingSystem.Threading.Tasks;namespaceWpfApp.Services{publicclassValidationService:IValidationService{publicasyncTaskboolIsUsernameAvailableAsync(stringusername){// 模拟 API 延迟awaitTask.Delay(500);// 假设 admin 和 user 不可用returnusername!adminusername!user;}}}3. 定义视图模型实现INotifyDataErrorInfo和实时验证逻辑使用防抖机制避免频繁触发异步验证MainWindowViewModel.csusingPrism.Commands;usingPrism.Mvvm;usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Linq;usingSystem.Threading;usingSystem.Threading.Tasks;usingWpfApp.Services;namespaceWpfApp.ViewModels{publicclassMainWindowViewModel:BindableBase,INotifyDataErrorInfo{privatereadonlyIValidationService_validationService;privatestring_username;privatestring_email;privatebool_isValidating;privatereadonlyDictionarystring,Liststring_errorsnewDictionarystring,Liststring();privateCancellationTokenSource_validationCts;publicstringUsername{get_username;set{if(SetProperty(ref_username,value)){ValidateUsernameAsync();}}}publicstringEmail{get_email;set{if(SetProperty(ref_email,value)){ValidateEmail();}}}publicboolIsValidating{get_isValidating;setSetProperty(ref_isValidating,value);}publicDelegateCommandSubmitCommand{get;}publicMainWindowViewModel(IValidationServicevalidationService){_validationServicevalidationService;SubmitCommandnewDelegateCommand(OnSubmit,CanSubmit);}privateasyncvoidValidateUsernameAsync(){_errors.Remove(nameof(Username));// 取消之前的验证任务_validationCts?.Cancel();_validationCtsnewCancellationTokenSource();vartoken_validationCts.Token;// 本地验证if(string.IsNullOrEmpty(_username))AddError(nameof(Username),Username cannot be empty.);elseif(_username.Length3)AddError(nameof(Username),Username must be at least 3 characters.);// 异步验证if(!_errors.ContainsKey(nameof(Username))){try{IsValidatingtrue;// 添加防抖延迟awaitTask.Delay(300,token);boolisAvailableawait_validationService.IsUsernameAvailableAsync(_username);if(!isAvailable)AddError(nameof(Username),Username is already taken.);}catch(TaskCanceledException){return;}finally{IsValidatingfalse;}}RaiseErrorsChanged(nameof(Username));SubmitCommand.RaiseCanExecuteChanged();}privatevoidValidateEmail(){_errors.Remove(nameof(Email));if(string.IsNullOrEmpty(_email))AddError(nameof(Email),Email cannot be empty.);elseif(!_email.Contains()||!_email.Contains(.))AddError(nameof(Email),Invalid email format.);RaiseErrorsChanged(nameof(Email));SubmitCommand.RaiseCanExecuteChanged();}privatevoidOnSubmit(){// 提交逻辑}privateboolCanSubmit(){return!HasErrors!IsValidating;}privatevoidAddError(stringpropertyName,stringerror){if(!_errors.ContainsKey(propertyName))_errors[propertyName]newListstring();_errors[propertyName].Add(error);}privatevoidRaiseErrorsChanged(stringpropertyName){ErrorsChanged?.Invoke(this,newDataErrorsChangedEventArgs(propertyName));}publicboolHasErrors_errors.Any();publiceventEventHandlerDataErrorsChangedEventArgsErrorsChanged;publicIEnumerableGetErrors(stringpropertyName){return_errors.ContainsKey(propertyName)?_errors[propertyName]:null;}}}4. 定义视图在MainWindow.xaml中实现实时验证和 UI 反馈Window x:ClassWpfApp.MainWindow xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:localclr-namespace:WpfApp.Converters TitleMainWindow Height450 Width800 Window.Resources local:ErrorConverter x:KeyErrorConverter/ local:BooleanToVisibilityConverter x:KeyBoolToVisibilityConverter/ Style TargetTypeTextBox Style.Triggers Trigger PropertyValidation.HasError ValueTrue Setter PropertyToolTip Value{Binding RelativeSource{RelativeSource Self}, Path(Validation.Errors)[0].ErrorContent}/ Setter PropertyBorderBrush ValueRed/ /Trigger /Style /Style /Window.Resources Grid StackPanel Margin10 TextBox Width200 Margin5 TextBox.Text Binding PathUsername UpdateSourceTriggerPropertyChanged ValidatesOnNotifyDataErrorsTrue/ /TextBox.Text TextBox.ToolTip Binding Path(Validation.Errors) RelativeSource{RelativeSource Self} Converter{StaticResource ErrorConverter}/ /TextBox.ToolTip /TextBox TextBox Width200 Margin5 TextBox.Text Binding PathEmail UpdateSourceTriggerPropertyChanged ValidatesOnNotifyDataErrorsTrue/ /TextBox.Text TextBox.ToolTip Binding Path(Validation.Errors) RelativeSource{RelativeSource Self} Converter{StaticResource ErrorConverter}/ /TextBox.ToolTip /TextBox TextBlock TextValidating... Visibility{Binding IsValidating, Converter{StaticResource BoolToVisibilityConverter}} Margin5/ Button ContentSubmit Command{Binding SubmitCommand} Width100 Margin5/ /StackPanel /Grid /WindowErrorConverter.csusingSystem;usingSystem.Globalization;usingSystem.Windows.Controls;usingSystem.Windows.Data;namespaceWpfApp.Converters{publicclassErrorConverter:IValueConverter{publicobjectConvert(objectvalue,TypetargetType,objectparameter,CultureInfoculture){if(valueisReadOnlyObservableCollectionValidationErrorerrorserrors.Any())returnstring.Join(\n,errors.Select(ee.ErrorContent));returnnull;}publicobjectConvertBack(objectvalue,TypetargetType,objectparameter,CultureInfoculture){thrownewNotImplementedException();}}}BooleanToVisibilityConverter.csusingSystem;usingSystem.Globalization;usingSystem.Windows;usingSystem.Windows.Data;namespaceWpfApp.Converters{publicclassBooleanToVisibilityConverter:IValueConverter{publicobjectConvert(objectvalue,TypetargetType,objectparameter,CultureInfoculture){return(valueisboolisVisibleisVisible)?Visibility.Visible:Visibility.Collapsed;}publicobjectConvertBack(objectvalue,TypetargetType,objectparameter,CultureInfoculture){returnvalueisVisibilityvisibilityvisibilityVisibility.Visible;}}}5. 配置应用程序在App.xaml.cs中注册服务usingPrism.Ioc;usingSystem.Windows;usingWpfApp.Services;namespaceWpfApp{publicpartialclassApp:PrismApplication{protectedoverrideWindowCreateShell(){returnContainer.ResolveMainWindow();}protectedoverridevoidRegisterTypes(IContainerRegistrycontainerRegistry){containerRegistry.RegisterIValidationService,ValidationService();}}}四、测试代码以下是使用 MSTest 和 Moq 的单元测试示例测试实时验证逻辑1. 测试依赖安装 NuGet 包Microsoft.NET.Test.SdkMSTest.TestFrameworkMSTest.TestAdapterMoqPrism.DryIoc2. 测试代码测试 ValidationServiceusingMicrosoft.VisualStudio.TestTools.UnitTesting;usingSystem.Threading.Tasks;usingWpfApp.Services;[TestClass]publicclassValidationServiceTests{privateValidationService_service;[TestInitialize]publicvoidSetup(){_servicenewValidationService();}[TestMethod]publicasyncTaskIsUsernameAvailableAsync_Available_ReturnsTrue(){varresultawait_service.IsUsernameAvailableAsync(testuser);Assert.IsTrue(result);}[TestMethod]publicasyncTaskIsUsernameAvailableAsync_Unavailable_ReturnsFalse(){varresultawait_service.IsUsernameAvailableAsync(admin);Assert.IsFalse(result);}}测试 MainWindowViewModelusingMicrosoft.VisualStudio.TestTools.UnitTesting;usingMoq;usingSystem.Linq;usingSystem.Threading.Tasks;usingWpfApp.Services;usingWpfApp.ViewModels;[TestClass]publicclassMainWindowViewModelTests{privateMockIValidationService_validationServiceMock;privateMainWindowViewModel_viewModel;[TestInitialize]publicvoidSetup(){_validationServiceMocknewMockIValidationService();_viewModelnewMainWindowViewModel(_validationServiceMock.Object);}[TestMethod]publicvoidUsername_Empty_AddsError(){_viewModel.Username;Assert.IsTrue(_viewModel.HasErrors);varerrors_viewModel.GetErrors(nameof(_viewModel.Username))asListstring;Assert.IsTrue(errors.Contains(Username cannot be empty.));}[TestMethod]publicasyncTaskUsername_Unavailable_AddsError(){_validationServiceMock.Setup(ss.IsUsernameAvailableAsync(admin)).ReturnsAsync(false);_viewModel.Usernameadmin;awaitTask.Delay(1000);// 等待异步验证Assert.IsTrue(_viewModel.HasErrors);varerrors_viewModel.GetErrors(nameof(_viewModel.Username))asListstring;Assert.IsTrue(errors.Contains(Username is already taken.));}[TestMethod]publicasyncTaskUsername_Available_NoErrors(){_validationServiceMock.Setup(ss.IsUsernameAvailableAsync(testuser)).ReturnsAsync(true);_viewModel.Usernametestuser;awaitTask.Delay(1000);// 等待异步验证Assert.IsFalse(_viewModel.HasErrors);Assert.IsTrue(_viewModel.SubmitCommand.CanExecute(null));}[TestMethod]publicvoidEmail_Invalid_AddsError(){_viewModel.Emailinvalid;Assert.IsTrue(_viewModel.HasErrors);varerrors_viewModel.GetErrors(nameof(_viewModel.Email))asListstring;Assert.IsTrue(errors.Contains(Invalid email format.));}[TestMethod]publicasyncTaskIsValidating_SetDuringValidation(){_validationServiceMock.Setup(ss.IsUsernameAvailableAsync(testuser)).ReturnsAsync(true);boolisValidatingChangedfalse;_viewModel.PropertyChanged(s,e){if(e.PropertyNamenameof(_viewModel.IsValidating))isValidatingChangedtrue;};_viewModel.Usernametestuser;awaitTask.Delay(1000);// 等待异步验证Assert.IsTrue(isValidatingChanged);Assert.IsFalse(_viewModel.IsValidating);}}3. 测试说明ValidationService 测试验证可用和不可用用户名返回正确结果。MainWindowViewModel 测试验证空用户名触发本地错误。验证不可用用户名触发异步错误。验证有效用户名无错误且命令可执行。验证无效邮箱触发错误。验证异步验证期间IsValidating属性变化。五、注意事项实时验证使用UpdateSourceTriggerPropertyChanged确保输入实时触发验证。使用防抖Task.Delay避免频繁触发异步验证。异步取消使用CancellationTokenSource取消过时的验证任务。UI 反馈使用IsValidating和BooleanToVisibilityConverter显示验证状态。使用ErrorConverter显示错误消息。性能优化通过防抖或节流throttle限制异步验证频率。避免重复触发 API 调用。测试注意使用Task.Delay模拟异步等待。使用 Moq 模拟异步服务。六、总结Prism 实时数据验证通过INotifyDataErrorInfo和async/await实现用户输入的即时验证结合本地规则和异步 API 调用提供动态 UI 反馈。示例展示了用户名和邮箱的实时验证防抖机制优化了性能测试代码验证了核心功能。验证逻辑可集成到模块化应用中与区域或导航结合使用。如果您需要更具体的内容例如与区域/导航/事件聚合器的集成、其他实时验证场景或特定测试用例请提供细节我会进一步优化答案