[Silverlight入门系列]使用MVVM模式(5):异步Validation数据验证和INotifyDataErrorInfo接口
数据验证(Validation)是界面程序的常见需求,例如使用正则表达式验证用户输入的Email地址是否合法,然后在界面给出错误提示信息。在Sivlerlight的MVVM模式中,我们在Model和ViewModel可以做Validation,然后需要把Model和ViewModel的Validation结果和错误信息通知视图(View)。在WPF中,我们使用IDataErrorInfo,在Silverlight4中,建议使用INotifyDataErrorInfo。关于这个接口怎么使用,如何实现ErrorProvider的功能,如何做DataForm的Validatio,请参考我的旧一篇。
IDataErrorInfo
先简单说一下IDataErrorInfo,这个接口实现了简单的数据验证和错误报告功能,只能说聊胜于无吧。例子:
INotifyDataErrorInfo
这个接口只有Silverlight4以上支持,非常强大,支持一个绑定属性多重错误、异步数据验证、自动通知视图错误信息、ErrorChanged事件、HasErrors属性、GetErrors方法等等。定义:
2 {
3 bool HasErrors { get; }
4
5 event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
6
7 IEnumerable GetErrors(string propertyName);
8 }
实现这个INotifyDataErrorInfo接口也非常简单,来个简单的例子:
2 {
3 publicevent EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
4
5 private Dictionary<string, List<String>> _errors =new Dictionary<string, List<String>>();
6
7 privatestring _accountID =null;
8
9 publicstring AccountID
10 {
11 get { return _accountID; }
12 set
13 {
14 if (_accountID != value)
15 {
16 var propertyName ="AccountID";
17
18 if (string.IsNullOrEmpty(value))
19 {
20 if (!_errors.ContainsKey(propertyName))
21 _errors.Add(propertyName, new List<string>());
22
23 _errors[propertyName].Add("AccountID can't be null or empty");
24 }
25 else
26 {
27 if (_errors.ContainsKey(propertyName))
28 _errors.Remove(propertyName);
29 }
30
31 NotifyErrorsChanged(propertyName);
32
33 //Maybe you don't want to set this field to a value if the validation fails
34 _accountID = value;
35 }
36 }
37
38 }
39
40 public System.Collections.IEnumerable GetErrors(string propertyName)
41 {
42 if (_errors.ContainsKey(propertyName))
43 return _errors[propertyName];
44
45 return _errors.Values;
46 }
47
48 publicbool HasErrors
49 {
50 get { return _errors.Count >0; }
51 }
52
53
54 privatevoid NotifyErrorsChanged(string propertyName)
55 {
56 if (ErrorsChanged !=null)
57 ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
58 }
59 }
异步Validation数据验证和INotifyDataErrorInfo接口
这个例子稍微复杂,实现了异步调用WCF RIA Service进行业务逻辑的validation并在ViewModel中把验证的错误提示通知视图,完整的代码下载,需要VS2010和Silverlight环境。
代码说明 ViewModel基类:
2 using System.Net;
3 using System.Windows;
4 using System.Linq;
5 using System.Windows.Controls;
6 using System.Windows.Documents;
7 using System.Windows.Ink;
8 using System.Windows.Input;
9 using System.Windows.Media;
10 using System.Windows.Media.Animation;
11 using System.Windows.Shapes;
12 using System.ComponentModel;
13 using System.Collections.Generic;
14 using System.Collections;
15
16 namespace AsycValidation
17 {
18 publicclass BasicViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
19 {
20 publicevent PropertyChangedEventHandler PropertyChanged;
21 publicevent EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
22
23
24 private Dictionary<string, List<ValidationErrorInfo>> _errors =
25 new Dictionary<string, List<ValidationErrorInfo>>();
26
27
28 protectedvoid RemoveErrorFromPropertyAndNotifyErrorChanges(
29 string propertyName,
30 int errorCode)
31 {
32 if (_errors.ContainsKey(propertyName))
33 {
34 RemoveErrorFromPropertyIfErrorCodeAlreadyExist(propertyName, errorCode);
35
36 NotifyErrorsChanged(propertyName);
37 }
38 }
39
40 privatevoid RemoveErrorFromPropertyIfErrorCodeAlreadyExist(
41 string propertyName,
42 int errorCode)
43 {
44 if (_errors.ContainsKey(propertyName))
45 {
46 var errorToRemove = _errors[propertyName].SingleOrDefault(
47 error => error.ErrorCode == errorCode);
48
49 if (errorToRemove !=null)
50 {
51 _errors[propertyName].Remove(errorToRemove);
52
53
54
55
56 if (_errors[propertyName].Count ==0)
57 _errors.Remove(propertyName);
58 }
59 }
60 }
61 protectedvoid AddErrorToPropertyAndNotifyErrorChanges(
62 string propertyName,
63 ValidationErrorInfo errorInfo)
64 {
65 RemoveErrorFromPropertyIfErrorCodeAlreadyExist(propertyName, errorInfo.ErrorCode);
66 if (!_errors.ContainsKey(propertyName))
67 _errors.Add(propertyName, new List<ValidationErrorInfo>());
68
69 _errors[propertyName].Add(errorInfo);
70
71 NotifyErrorsChanged(propertyName);
72 }
73
74
75 public IEnumerable GetErrors(string propertyName)
76 {
77 if (!_errors.ContainsKey(propertyName))
78 return _errors.Values;
79
80 return _errors[propertyName];
81 }
82
83
84 publicbool HasErrors
85 {
86 get { returnthis._errors.Count >0; }
87 }
88
89
90 privatevoid NotifyErrorsChanged(string propertyName)
91 {
92 if (ErrorsChanged !=null)
93 ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
94 }
95
96
97 protectedvoid NotifyPropertyChanged(string propertyName)
98 {
99 if (PropertyChanged !=null)
100 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
101 }
102
103 }
104 }
Model:
2 using System.Net;
3 using System.Windows;
4 using System.Windows.Controls;
5 using System.Windows.Documents;
6 using System.Windows.Ink;
7 using System.Windows.Input;
8 using System.Windows.Media;
9 using System.Windows.Media.Animation;
10 using System.Windows.Shapes;
11 using System.ComponentModel;
12
13 namespace AsycValidation
14 {
15 publicclass CompanyModel : INotifyPropertyChanged
16 {
17 publicevent PropertyChangedEventHandler PropertyChanged;
18
19 publicint CompanyID { get; set; }
20
21 privatestring _CompanyName;
22 publicstring CompanyName
23 {
24 get { return _CompanyName; }
25 set
26 {
27 _CompanyName = value;
28
29 if (PropertyChanged !=null)
30 {
31 PropertyChanged(this, new PropertyChangedEventArgs("CompanyName"));
32 }
33 }
34 }
35 }
36 }
ViewModel,继承了BaseViewModel基类:
2 using System.Net;
3 using System.Windows;
4 using System.Windows.Controls;
5 using System.Windows.Documents;
6 using System.Windows.Ink;
7 using System.Windows.Input;
8 using System.Windows.Media;
9 using System.Windows.Media.Animation;
10 using System.Windows.Shapes;
11 using AsycValidation.Web;
12
13 namespace AsycValidation
14 {
15 publicclass CompanyViewModel : BasicViewModel
16 {
17 public CompanyModel CompanyModelData { get; set; }
18
19 public CompanyViewModel(CompanyModel model)
20 {
21 CompanyModelData = model;
22 }
23
24 privatestring _CompanyName =null;
25 privateconstint ACCOUNT_ALREADY_EXIST_ERROCODE =100;
26
27 DomainService1 service =new DomainService1();
28
29 publicstring CompanyName
30 {
31 get
32 {
33 return _CompanyName;
34 }
35 set
36 {
37 if (_CompanyName != value)
38 {
39 var propertyName ="CompanyName";
40
41 ValidateAccountAlreadyExists(
42 value,
43 propertyName,
44 ACCOUNT_ALREADY_EXIST_ERROCODE,
45 string.Format("Company with the ID {0} already exists", value));
46
47 _CompanyName = value;
48 NotifyPropertyChanged(propertyName);
49 }
50 }
51 }
52
53 privatevoid ValidateAccountAlreadyExists(
54 string CompanyID,
55 string propertyName,
56 int errorCode,
57 string errorMsg)
58 {
59 service.DoesCompanyExists(
60 CompanyID,
61 invokeOperation =>
62 {
63 if (invokeOperation.Value)
64 {
65 AddErrorToPropertyAndNotifyErrorChanges(
66 propertyName,
67 new ValidationErrorInfo()
68 {
69 ErrorCode = errorCode,
70 ErrorMessage = errorMsg
71 });
72 }
73 else
74 {
75 RemoveErrorFromPropertyAndNotifyErrorChanges(
76 propertyName,
77 errorCode);
78 }
79 },
80 null);
81 }
82
83 }
84 }
View / XAML
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:wm="clr-namespace:AsycValidation"
7 mc:Ignorable="d"
8 d:DesignHeight="300" d:DesignWidth="400">
9
10 <Grid Name="Layout">
11 <TextBlock Height="32" HorizontalAlignment="Left" Margin="41,53,0,0" Name="textBlock1" Text="Company:" VerticalAlignment="Top" Width="66"/>
12 <TextBox Height="31" HorizontalAlignment="Left" Margin="120,45,0,0" Name="textBox1" Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=True}" VerticalAlignment="Top" Width="119"/>
13 <TextBox Height="30" HorizontalAlignment="Left" Margin="120,104,0,0" Name="textBox2" VerticalAlignment="Top" Width="119"/>
14 <Button Content="Button" Height="36" HorizontalAlignment="Left" Margin="120,156,0,0" Name="button1" VerticalAlignment="Top" Width="81"/>
15 </Grid>
16 </UserControl>
XAML.CS
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Net;
5 using System.Windows;
6 using System.Windows.Controls;
7 using System.Windows.Documents;
8 using System.Windows.Input;
9 using System.Windows.Media;
10 using System.Windows.Media.Animation;
11 using System.Windows.Shapes;
12
13 namespace AsycValidation
14 {
15 publicpartialclass MainPage : UserControl
16 {
17 public MainPage()
18 {
19 InitializeComponent();
20
21 CompanyModel m1 =new CompanyModel() { CompanyID =1, CompanyName ="abc" };
22
23 companyViewModel =new CompanyViewModel(m1);
24 this.DataContext = companyViewModel;
25 }
26
27 public CompanyViewModel companyViewModel { get; set; }
28 }
29 }
WCF Ria Service:
2 {
3 using System;
4 using System.Collections.Generic;
5 using System.ComponentModel;
6 using System.ComponentModel.DataAnnotations;
7 using System.Linq;
8 using System.ServiceModel.DomainServices.Hosting;
9 using System.ServiceModel.DomainServices.Server;
10
11
12 // TODO: Create methods containing your application logic.
13 [EnableClientAccess()]
14 publicclass DomainService1 : DomainService
15 {
16 [Invoke]
17 publicbool DoesCompanyExists(string companyID)
18 {
19 if (companyID =="12345")
20 returntrue;
21
22 returnfalse;
23 }
24 }
25 }
这个例子稍微复杂,实现了异步调用WCF RIA Service进行业务逻辑的validation并在ViewModel中把验证的错误提示通知视图,完整的代码下载,需要VS2010和Silverlight环境。
主要示例了MVVM的INotifyDataErrorInfo接口和INotifyPropertyChanged接口,异步Validation, WCF Ria Service调用。