SilverLight Tip 1 : Validation
1:SL的数据验证和WPF的不同
首先,很遗憾,SL中不再存在ValidationRules,要在SL中绑定时验证数据,并且显示在UI,只能依赖于NotifyOnValidationError=True, ValidatesOnExceptions=True这两个属性,如下:
如果要查看WPF的数据验证的方式,可以查看该文《WPF快速指导5:验证》。
2:一般情况下的验证
一般情况下,UI绑定数据类型的属性,如在上图中,绑定的就是Name和Age,它在UI的VIEWMODEL中,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | public class MainPageVM : INotifyPropertyChanged { public MainPageVM() { } private string name = "luminji" ; public string Name { get { return name; } set { if ( string .IsNullOrEmpty(value)) { throw new Exception( "姓名不能为空" ); } name = value; OnPropertyChanged( "Name" ); } } private int age = 1; public int Age { get { return age; } set { if (value > 100) { throw new Exception( "年龄必须小与100" ); } age = value; OnPropertyChanged( "Age" ); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged( string propertyName) { PropertyChangedEventHandler pceh = PropertyChanged; if (pceh != null ) { pceh( this , new PropertyChangedEventArgs(propertyName)); } } } |
采用UI直接绑定VM的属性,如果Age>100,则UI会提示出现输入有误。
3:绑定实体类型
不过,如果属性很多,我们就会考虑绑定实体类型。如User,而这个实体类,是在服务器端的,形如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class User { private string name = "luminji" ; public string Name { get { return name; } set { name = value; } } private int age = 1; public int Age { get { return age; } set { age = value; } } } |
比如,我们使用的是Ria Service。Ria Service有可能是从DAL层获取数据并开放接口给客户端。我们都知道,客户端的代码都是自动生成的,自动生成的代码在SL项目的Generated_Code目录下的(SL项目名).Web.g.cs文件下。所以,最终User类型在客户端的代理形式为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | /// <summary> /// The 'User' entity class. /// </summary> [DataContract(Namespace= "http://schemas.datacontract.org/2004/07/SilverlightApplication3.Web.Model" )] public sealed partial class User : Entity { private int _age; private string _name; #region Extensibility Method Definitions /// <summary> /// This method is invoked from the constructor once initialization is complete and /// can be used for further object setup. /// </summary> partial void OnCreated(); partial void OnAgeChanging( int value); partial void OnAgeChanged(); partial void OnNameChanging( string value); partial void OnNameChanged(); #endregion /// <summary> /// Initializes a new instance of the <see cref="User"/> class. /// </summary> public User() { this .OnCreated(); } /// <summary> /// Gets or sets the 'Age' value. /// </summary> [DataMember()] public int Age { get { return this ._age; } set { if (( this ._age != value)) { this .OnAgeChanging(value); this .RaiseDataMemberChanging( "Age" ); this .ValidateProperty( "Age" , value); this ._age = value; this .RaiseDataMemberChanged( "Age" ); this .OnAgeChanged(); } } } /// <summary> /// Gets or sets the 'Name' value. /// </summary> [DataMember()] [Editable( false , AllowInitialValue= true )] [Key()] [RoundtripOriginal()] public string Name { get { return this ._name; } set { if (( this ._name != value)) { this .OnNameChanging(value); this .ValidateProperty( "Name" , value); this ._name = value; this .RaisePropertyChanged( "Name" ); this .OnNameChanged(); } } } /// <summary> /// Computes a value from the key fields that uniquely identifies this entity instance. /// </summary> /// <returns>An object instance that uniquely identifies this entity instance.</returns> public override object GetIdentity() { return this ._name; } } |
这个时候,如果我们在UI中继续绑定这个实体类型,势必会丢掉UI异常通知的行为,因为,显然,我们不能跑到这个客户端的代理类中throw new Exception("姓名不能为空"); ,那会在下一次代码自动生成的时候被覆盖掉。
4:解决方案之建立映射
一种解决方案是我们的UI仍旧不绑定实体类型,而是为类型的属性在ViewModel中建立一一映射的关系,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | public class MainPageVM : INotifyPropertyChanged { public MainPageVM() { user = new User(); serviceUser = new DomainServiceUser(); serviceUser.Load<User>(serviceUser.GetAUserQuery(), new Action<System.ServiceModel.DomainServices.Client.LoadOperation<User>>( this .GetAUserCallBack), null ); } DomainServiceUser serviceUser; User user; void GetAUserCallBack(LoadOperation<User> arg) { user = (arg.Entities as IList<User>)[0]; Name = user.Name; Age = user.Age; } public string Name { get { return user.Name; } set { if ( string .IsNullOrEmpty(value)) { throw new Exception( "姓名不能为空" ); } user.Name = value; OnPropertyChanged( "Name" ); } } public int Age { get { return user.Age; } set { if (value > 100) { throw new Exception( "年龄必须小与100" ); } user.Age = value; OnPropertyChanged( "Age" ); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged( string propertyName) { PropertyChangedEventHandler pceh = PropertyChanged; if (pceh != null ) { pceh( this , new PropertyChangedEventArgs(propertyName)); } } } |
UI效果图:
到此位置的源码下载为:SilverlightApplication20110618.zip
5:解决方案之使用ValidationSummary
使用ValidationSummary,需要我们引入程序集System.Windows.Controls.Data.Input,在UI前台,我们需要安置一个ValidationSummary:
接着,我们让前台的ValidationSummary的Errors赋值给VM。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); ViewModel = new MainPageVM( this .vs.Errors); } public MainPageVM ViewModel { get { return (MainPageVM) this .DataContext; } set { this .DataContext = value; } } private void btnSave_Click( object sender, RoutedEventArgs e) { } } |
我们还需要为UI绑定一些Command,以便在需要验证输入的时候,让VM去判断是否有错误发生。一旦有错误发生,则为Errors添加错误项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | public class MainPageVM : INotifyPropertyChanged { public MainPageVM(ObservableCollection<ValidationSummaryItem> errors) { m_errors = errors; Click = new ActionCommand( this .OnClick); serviceUser = new DomainServiceUser(); serviceUser.Load<User>(serviceUser.GetAUserQuery(), new Action<System.ServiceModel.DomainServices.Client.LoadOperation<User>>( this .GetAUserCallBack), null ); //serviceUser.Load<User>(serviceUser.GetAUserQuery(), LoadBehavior.RefreshCurrent, new Action<System.ServiceModel.DomainServices.Client.LoadOperation<User>>(this.GetAUserCallBack), null); } DomainServiceUser serviceUser; User user; ObservableCollection<ValidationSummaryItem> m_errors; void GetAUserCallBack(LoadOperation<User> arg) { user = (arg.Entities as IList<User>)[0]; OnPropertyChanged( "User" ); } public User User { get { return user; } set { user = value; OnPropertyChanged( "User" ); } } public ICommand Click { get ; private set ; } public void OnClick( object arg) { m_errors.Clear(); if (User.Age > 100) { m_errors.Add( new ValidationSummaryItem( "年龄不能大雨100" )); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged( string propertyName) { PropertyChangedEventHandler pceh = PropertyChanged; if (pceh != null ) { pceh( this , new PropertyChangedEventArgs(propertyName)); } } } |
最后的运行结果为:
备注:要让实体类在SL端支持EDIT,必须要让DomainService针对该实体类支持开放GUID接口。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | [EnableClientAccess()] public class DomainServiceUser : DomainService { [Query] public IList<User> GetUsers() { return new List<User>() { new User() { Name = "huzhonghua" , Age = 99 } }; } public User GetAUser() { return new User() { Name = "luminji" , Age = 98 }; } [Delete] public void DeleteUser(User user) { throw new NotImplementedException(); } [Insert] public void InsertUser(User user) { throw new NotImplementedException(); } [Update] public void UpdateUser(User user) { throw new NotImplementedException(); } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器