使用自跟踪实体生成器构建 WPF 应用程序 - 数据验证
本文介绍如何使用自跟踪实体生成器对 WPF/Silverlight 进行数据验证。
目录
引言
在本文中,我们将重点介绍如何使用自跟踪实体生成器对 WPF/Silverlight 进行数据验证。数据验证的目的是确保在将任何数据存储到数据库之前对其进行验证。它在数据输入任务期间为用户提供必要的指导,并且是任何 WPF LOB 应用程序的重要组成部分。首先,我们将介绍自动生成的验证助手方法。之后,我们将讨论在演示应用程序的客户端和服务器端添加验证逻辑的不同方法。
验证助手方法
客户端的自动生成验证助手方法包含以下成员
TryValidate()
方法会循环遍历实体对象的所有数据注释属性和所有自定义验证操作。如果任何验证失败,此函数将返回 false,否则返回 true。TryValidate(string propertyName)
方法会循环遍历实体对象指定属性名的所有数据注释属性和所有自定义验证操作。如果任何验证失败,此函数将返回 false,否则返回 true。TryValidateObjectGraph()
方法会遍历整个对象图,并对每个实体对象调用TryValidate()
。如果任何验证失败,此函数将返回 false,否则返回 true。- 部分方法
InitializeValidationSettings()
是添加为实体类定义的任何自定义验证操作的地方。 AddPropertyValidationAction(string propertyName, Action<object> validationAction)
方法为实体对象指定的属性名添加自定义验证操作。AddError(string propertyName, ValidationResult validationResult)
方法为实体对象指定的属性名添加新错误。- 公共字段
SuspendValidation
可以设置为 true 以临时关闭数据验证。但是,设置此字段不会影响TryValidate()
、TryValidate(string propertyName)
或TryValidateObjectGraph()
方法。
此外,服务器端验证助手方法如下
Validate()
方法会循环遍历实体对象的所有数据注释属性和所有自定义验证操作。如果任何验证失败,它将抛出异常。ValidateObjectGraph()
方法会遍历整个对象图,并对每个实体对象调用Validate()
。如果任何验证失败,它将抛出异常。
使用 IDataErrorInfo 接口启用验证
当将视图中的控件绑定到我们希望通过 IDataErrorInfo
接口进行验证的属性时,我们将数据绑定的 ValidatesOnDataErrors
属性设置为 true。这将确保数据绑定引擎会请求有关数据绑定属性的错误信息。此外,我们将 NotifyOnValidationError
属性设置为 true,因为我们希望接收 BindingValidationFailed
事件,然后我们将 ValidatesOnExceptions
属性设置为 true,因为我们希望捕获其他类型的错误,例如数据转换问题。
下面是我们演示应用程序的屏幕截图,其中显示了一些验证错误。标签的标题文本来自数据绑定属性的 DisplayAttribute.Name
属性(在上面的示例中,数据绑定属性是 CurrentInstructor.Name
),其颜色从黑色变为红色以指示存在错误。在每个文本框的右侧,有一个 DescriptionViewer
控件,该控件显示一个信息图标,并在鼠标指针悬停在图标上时在工具提示中显示文本描述。此文本描述来自数据绑定属性的 DisplayAttribute.Description
属性。最后,我们从验证摘要控件获取所有错误消息的摘要。
在检查了数据验证的用户界面外观后,接下来我们将讨论添加数据验证逻辑的不同方法。
客户端数据验证
客户端数据验证逻辑可以通过实体数据模型设计器或自定义验证操作添加。此外,在提交更改之前,我们需要调用 TryValidate()
、TryValidate(string propertyName)
或 TryValidateObjectGraph()
,以确保所有客户端验证逻辑都能成功通过。
通过实体数据模型设计器添加验证
让我们先看看如何通过实体数据模型设计器添加验证。为此,我们首先需要确保所有 系统要求 都已满足。之后,打开 SchoolModel.edmx 的实体数据模型设计器,并选择 Person
实体的 Name
属性。从下面的“属性”窗口中,我们可以指定将用于生成 Person
类的 Display
属性的 Name
和 Description
字段。
接下来,通过选择“验证”集合(上面突出显示)来打开“验证编辑器”窗口,并添加两个验证条件的架构元数据。
在保存了 EDM 文件 SchoolModel.edmx 的更改后,T4 模板将自动重新生成所有自跟踪实体类,并添加下面显示的新数据注释属性。
通过实体数据模型设计器添加验证逻辑的一个限制是它目前不支持 CustomValidationAttribute
。要添加自定义验证,我们必须采用下面描述的不同方法。
添加自定义验证
自定义验证操作在客户端和服务器端的验证文件夹内定义。以下是验证 Person
类的 Name
属性是否包含数字的示例。
public partial class Person
{
/// <summary>
/// Partial method to add all validation actions
/// defined for this class
/// </summary>
partial void InitializeValidationSettings()
{
AddPropertyValidationAction("Name", ValidateName);
}
#region "Private Validation Methods"
/// <summary>
/// Validation Action:
/// check whether name contains no digit number
/// </summary>
/// <param name="value"></param>
private void ValidateName(object value)
{
var name = (string)value;
if (name != null && Regex.IsMatch(name, @"\d"))
{
#if (WPF)
ValidationResult result =
new ValidationResult("This field cannot contain any digit.",
new List<string> { "Name" });
AddError("Name", result);
#else
FaultReason faultReason =
new FaultReason("Name cannot contain any digit.");
throw new FaultException(faultReason);
#endif
}
}
#endregion "Private Validation Methods"
}
首先,我们可以看到部分方法 InitializeValidationSettings()
,这是我们添加为 Person
类定义的任何自定义验证操作的地方。由于这是一个部分方法,如果我们没有自定义验证逻辑,我们也可以选择不实现它。
在此部分方法中,我们使用另一个验证助手方法 AddPropertyValidationAction()
添加所有自定义验证操作。此方法的第一参数是一个字符串值,即需要验证的属性的名称;第二个参数指向一个自定义验证方法。在上面的代码示例中,自定义验证方法是 ValidateName()
。
此自定义验证方法 ValidateName()
只有一个参数,即需要验证的属性的值。如果验证失败,该方法根据是从客户端还是服务器端调用而表现不同。
如果此方法是从客户端调用的,则会创建一个带有适当错误消息的新的 ValidationResult
对象,然后调用 AddError()
方法。AddError()
方法是另一个自动生成的验证助手方法,它为 Person
对象添加了一个新的 Name
属性错误,这会触发用户界面以突出显示验证失败的属性,并显示相应的错误消息。
但是,如果该方法是从服务器端调用的,则会抛出一个带有错误消息的 FaultException
。此异常将传回客户端,用户将收到有关问题所在地的通知。
到目前为止,我们已经完成了对两种不同验证逻辑添加选项的讨论。我们的下一个主题是如何确保在将更改提交到服务器端之前不跳过任何客户端验证。
使用 TryValidateObjectGraph() 进行验证
验证助手方法 TryValidate()
、TryValidate(string propertyName)
和 TryValidateObjectGraph()
用于确保在提交更改到服务器端之前所有客户端验证逻辑都成功通过。第一个助手方法不带参数,它会循环遍历实体对象的所有数据注释属性和所有自定义验证操作。如果任何验证失败,此函数返回 false;否则返回 true。第二个方法带有一个字符串值参数,即需要验证的属性名,此助手方法仅针对指定的属性进行验证。最后一个方法 TryValidateObjectGraph()
与第一个方法类似,但它会遍历整个对象图,而不是仅实体对象。
下面的代码示例来自 InstructorPageViewModel
类,我们通过对 AllInstructors
列表中的每个元素调用 TryValidateObjectGraph()
方法来验证该列表,以确保我们仅在每个讲师都通过验证时才保存更改。
private void OnSubmitAllChangeCommand()
{
try
{
if (!_schoolModel.IsBusy)
{
if (AllInstructors != null)
{
// we only save changes when all instructors passed validation
var passedValidation = AllInstructors.All(o => o.TryValidateObjectGraph());
if (!passedValidation) return;
_schoolModel.SaveInstructorChangesAsync();
}
}
}
catch (Exception ex)
{
// notify user if there is any error
AppMessages.RaiseErrorMessage.Send(ex);
}
}
使用 SuspendValidation 关闭验证
客户端的另一个功能是公共字段 SuspendValidation
。通过将此字段设置为 true,我们可以在初始化一个我们通常不希望向用户显示验证错误的实体对象时跳过调用数据验证逻辑。以下示例来自 InstructorPageViewModel
类。当用户想要添加新讲师记录时,首先将 SuspendValidation
字段设置为 true 来创建一个新的 Instructor
对象。这可以确保设置其他属性不会触发任何数据验证错误。在对象完全初始化后,我们可以将 SuspendValidation
设置回 false,以便用户进行的任何后续更改都将触发验证逻辑。
最后要记住的一点是,设置 SuspendValidation
不会影响 TryValidate()
、TryValidate(string propertyName)
或 TryValidateObjectGraph()
方法。即使 SuspendValidation
设置为 true,这三个方法仍然会触发数据验证。
private void OnAddInstructorCommand()
{
// create a temporary PersonId
int newPersonId = AllInstructors.Count > 0
? ((from instructor in AllInstructors select Math.Abs(instructor.PersonId)).Max() + 1) * (-1)
: -1;
// create a new instructor
CurrentInstructor = new Instructor
{
SuspendValidation = true,
PersonId = newPersonId,
Name = string.Empty,
HireDate = DateTime.Now,
Salary = null
};
CurrentInstructor.SuspendValidation = false;
// and begin edit
OnEditCommitInstructorCommand();
}
这标志着我们对客户端数据验证逻辑的各个方面进行了讨论。接下来我们将转到服务器端数据验证的主题。
服务器端数据验证
两个自动生成的服务器端验证助手方法是 Validate()
和 ValidateObjectGraph()
。除此之外,我们还可以添加跨实体验证,只要有需要。让我们先看看如何使用这两个验证助手方法。
使用 ValidateObjectGraph() 进行验证
Validate()
和 ValidateObjectGraph()
方法在服务器端使用,以确保所有传入的更新调用都通过与客户端定义的相同验证逻辑集。我们添加此额外步骤是因为服务器端公开为 WCF 服务,并且我们假设调用可能来自任何地方。因此,WCF 服务还需要先验证其数据。
以下是 SchoolService
类中的 UpdateInstructor()
方法,我们在添加和更新操作中都对 Instructor
对象调用了 ValidateObjectGraph()
。此验证助手方法会遍历整个对象图,并对每个实体对象调用 Validate()
。这基本上会重复与客户端定义的相同的数据验证逻辑集。如果任何验证失败,将抛出异常并传回客户端。否则,通过调用 context.People.ApplyChanges(item)
然后调用 context.SaveChanges()
来保存更改。
public List<object> UpdateInstructor(Instructor item)
{
var returnList = new List<object>();
try
{
using (var context = new SchoolEntities())
{
switch (item.ChangeTracker.State)
{
case ObjectState.Added:
// server side validation
item.ValidateObjectGraph();
// save changes
context.People.ApplyChanges(item);
context.SaveChanges();
break;
case ObjectState.Deleted:
// verify whether there is any course assigned to this instructor
var courseExists =
context.Courses.Any(n => n.InstructorId == item.PersonId);
if (courseExists)
{
returnList.Add("Cannot delete, there still " +
"exists course assigned to this instructor.");
returnList.Add(item.PersonId);
return returnList;
}
// save changes
context.People.ApplyChanges(item);
context.SaveChanges();
break;
default:
// server side validation
item.ValidateObjectGraph();
// save changes
context.People.ApplyChanges(item);
context.SaveChanges();
break;
}
}
returnList.Add(string.Empty);
returnList.Add(item.PersonId);
}
catch (OptimisticConcurrencyException)
{
var errorMessage = "Instructor " + item.PersonId +
" was modified by another user. " +
"Refresh that item before reapply your changes.";
returnList.Add(errorMessage);
returnList.Add(item.PersonId);
}
catch (Exception ex)
{
Exception exception = ex;
while (exception.InnerException != null)
{
exception = exception.InnerException;
}
var errorMessage = "Instructor " + item.PersonId +
" has error: " + exception.Message;
returnList.Add(errorMessage);
returnList.Add(item.PersonId);
}
return returnList;
}
跨实体验证
对于上面显示的同一个 UpdateInstructor()
方法,删除操作的数据验证要复杂一些。没有必要调用 ValidateObjectGraph()
方法。相反,我们执行跨实体验证,并检查是否有任何课程仍与该讲师关联。如果为 true,我们将警告消息发送回客户端,说明我们无法删除此讲师(实际消息如下所示)。否则,删除操作将继续执行,从数据库中删除该讲师行。
总结
我们已经完成了对如何使用自跟踪实体生成器对 WPF/Silverlight 进行数据验证的讨论。首先,我们简要介绍了所有可用的自动生成验证助手方法。然后,我们讨论了使用 IDataErrorInfo
接口启用验证的各种属性。之后,我们讨论了在客户端和服务器端添加数据验证逻辑的各种方法。
希望您觉得本文有用,请在下方评分和/或留下反馈。谢谢!
历史
- 2012 年 1 月 4 日 - 初始发布。
- 2012 年 1 月 20 日 - 添加了
SuspendValidation
的文档。