数据验证(Validation)是界面程序的常见需求,例如使用正则表达式验证用户输入的Email地址是否合法,然后在界面给出错误提示信息。在Sivlerlight的MVVM模式中,我们在Model和ViewModel可以做Validation,然后需要把Model和ViewModel的Validation结果和错误信息通知视图(View)。在WPF中,我们使用IDataErrorInfo,在Silverlight4中,建议使用INotifyDataErrorInfo。关于这个接口怎么使用,如何实现ErrorProvider的功能,如何做DataForm的Validatio,请参考我的旧一篇。
IDataErrorInfo
先简单说一下IDataErrorInfo,这个接口实现了简单的数据验证和错误报告功能,只能说聊胜于无吧。例子:
1
<
TextBox
Text
="
{Binding Path=CurrentEmployee.Name, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True }
"
/>
INotifyDataErrorInfo
这个接口只有Silverlight4以上支持,非常强大,支持一个绑定属性多重错误、异步数据验证、自动通知视图错误信息、ErrorChanged事件、HasErrors属性、GetErrors方法等等。定义:
1
public
interface
INotifyDataErrorInfo
2
{
3
bool
HasErrors {
get
; }
4
5
event
EventHandler
<
DataErrorsChangedEventArgs
>
ErrorsChanged;
6
7
IEnumerable GetErrors(
string
propertyName);
8
}
实现这个INotifyDataErrorInfo接口也非常简单,来个简单的例子:
1
public
class
SimpleModel : INotifyDataErrorInfo
2
{
3
public
event
EventHandler
<
DataErrorsChangedEventArgs
>
ErrorsChanged;
4
5
private
Dictionary
<
string
, List
<
String
>>
_errors
=
new
Dictionary
<
string
, List
<
String
>>
();
6
7
private
string
_accountID
=
null
;
8
9
public
string
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
public
bool
HasErrors
49
{
50
get
{
return
_errors.Count
>
0
; }
51
}
52
53
54
private
void
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基类:
1
using
System;
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
public
class
BasicViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
19
{
20
public
event
PropertyChangedEventHandler PropertyChanged;
21
public
event
EventHandler
<
DataErrorsChangedEventArgs
>
ErrorsChanged;
22
23
24
private
Dictionary
<
string
, List
<
ValidationErrorInfo
>>
_errors
=
25
new
Dictionary
<
string
, List
<
ValidationErrorInfo
>>
();
26
27
28
protected
void
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
private
void
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
protected
void
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
public
bool
HasErrors
85
{
86
get
{
return
this
._errors.Count
>
0
; }
87
}
88
89
90
private
void
NotifyErrorsChanged(
string
propertyName)
91
{
92
if
(ErrorsChanged
!=
null
)
93
ErrorsChanged(
this
,
new
DataErrorsChangedEventArgs(propertyName));
94
}
95
96
97
protected
void
NotifyPropertyChanged(
string
propertyName)
98
{
99
if
(PropertyChanged
!=
null
)
100
PropertyChanged(
this
,
new
PropertyChangedEventArgs(propertyName));
101
}
102
103
}
104
}
Model:
1
using
System;
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
public
class
CompanyModel : INotifyPropertyChanged
16
{
17
public
event
PropertyChangedEventHandler PropertyChanged;
18
19
public
int
CompanyID {
get
;
set
; }
20
21
private
string
_CompanyName;
22
public
string
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基类:
1
using
System;
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
public
class
CompanyViewModel : BasicViewModel
16
{
17
public
CompanyModel CompanyModelData {
get
;
set
; }
18
19
public
CompanyViewModel(CompanyModel model)
20
{
21
CompanyModelData
=
model;
22
}
23
24
private
string
_CompanyName
=
null
;
25
private
const
int
ACCOUNT_ALREADY_EXIST_ERROCODE
=
100
;
26
27
DomainService1 service
=
new
DomainService1();
28
29
public
string
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
private
void
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
1
<
UserControl
x:Class
="AsycValidation.MainPage"
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
1
using
System;
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
public
partial
class
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:
1
namespace
AsycValidation.Web
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
public
class
DomainService1 : DomainService
15
{
16
[Invoke]
17
public
bool
DoesCompanyExists(
string
companyID)
18
{
19
if
(companyID
==
"
12345
"
)
20
return
true
;
21
22
return
false
;
23
}
24
}
25
}
这个例子稍微复杂,实现了异步调用WCF RIA Service进行业务逻辑的validation并在ViewModel中把验证的错误提示通知视图,完整的代码下载,需要VS2010和Silverlight环境。
主要示例了MVVM的INotifyDataErrorInfo接口和INotifyPropertyChanged接口,异步Validation, WCF Ria Service调用。