SilverLight Tip 1 : Validation
1:SL的数据验证和WPF的不同
首先,很遗憾,SL中不再存在ValidationRules,要在SL中绑定时验证数据,并且显示在UI,只能依赖于NotifyOnValidationError=True, ValidatesOnExceptions=True这两个属性,如下:
如果要查看WPF的数据验证的方式,可以查看该文《WPF快速指导5:验证》。
2:一般情况下的验证
一般情况下,UI绑定数据类型的属性,如在上图中,绑定的就是Name和Age,它在UI的VIEWMODEL中,如下:
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,而这个实体类,是在服务器端的,形如:
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类型在客户端的代理形式为:
/// <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中建立一一映射的关系,如下:
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。
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添加错误项。
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接口。如下:
[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(); } }