RiaTasks:集中的 Silverlight 业务规则验证






4.98/5 (28投票s)
在网站上使用 Fluent Validation 来验证 Silverlight 应用程序中的业务规则。

实时示例: http://silverlight.adefwebserver.com/riatasks2businessvalidation/riatasksweb/
管理网站中的 Silverlight 业务规则

为 Silverlight 提供验证的方法有很多。本文介绍了一种为 Silverlight 应用程序在启动该 Silverlight 应用程序的网站中提供业务规则验证的方法。您希望这样做的原因包括:
- 多个共享同一数据库的 Silverlight 应用程序将共享相同的验证规则。
- 使用同一数据库的普通 ASP.NET 应用程序将共享相同的验证规则。
- 验证规则易于维护。
简单的 Silverlight 验证

本文接续博客文章基本的 Silverlight View Model 验证的讲述。在那篇博客中,我们介绍了客户端的简单验证。我们只关心验证用户输入的数据是否与底层数据类型匹配。在该示例中,我们验证了用户输入了有效日期,或者根本没有输入日期。
我们可以创建更多的验证规则,但是,如果存在多个View Models怎么办?我们需要在每个View Model中复制验证逻辑。即使我们集中了验证逻辑,也无法轻松地在多个 Silverlight 应用程序或使用同一数据库的任何 ASP.NET 网站之间共享。
Silverlight 业务规则验证应用程序

让我们来看看最终的应用程序。
如果您不输入姓名或描述,应用程序将不会保存任何数据,并且会在屏幕底部一个可滚动框中显示错误消息。

如果您输入的日期是过去的日期,应用程序将不会保存任何数据,并且还会显示错误消息。
是的,如果您同时出现所有三种错误,它将在可滚动错误框中向您显示所有错误。
网站业务规则验证

Silverlight 应用程序在 Web 服务器上处理所有验证。

上面的流程图解释了这个过程。
Fluent Validation

我们首先从Fluent Validation开始。它允许您使用 Linq 语法轻松创建业务规则。
您可以从这里下载Fluent Validation。

我们将它的程序集添加到网站项目(而不是 Silverlight 项目)。

接下来,我们创建一个名为 _BusinessValidation_ 的文件夹和一个名为 _BusinessRules.cs_ 的文件,并将以下代码添加到其中:
using FluentValidation;
using System;
namespace RIATasks.Web
{
// This validator will be used for all operations on the Tasks table
public class TaskValidator : AbstractValidator<Task>
{
// These rules ensure that you always have a Name and Description for a Task
public TaskValidator()
{
RuleFor(Task => Task.TaskName).NotEmpty()
.WithMessage("Please specify Task Name");
RuleFor(Task => Task.TaskDescription).NotEmpty()
.WithMessage("Please specify Task Description");
}
}
// This validator will only be used for Insert operations on the Tasks table
public class TaskInsertValidator : AbstractValidator<Task>
{
public TaskInsertValidator()
{
// When inserting the date must be null or in the future
RuleFor(Task => Task.DueDate).Must(BeADateInTheFuture)
.WithMessage("Please specify a date that has not already passed");
}
// If a non-null date is entered make sure it is in the future
private bool BeADateInTheFuture(DateTime? dtCurrentDate)
{
DateTime dtDateInTheFuture = DateTime.Now.AddDays(1);
return ((dtCurrentDate ?? dtDateInTheFuture) > DateTime.Now.AddDays(-1));
}
}
}
需要注意的一些事项。
TaskValidator()
将仅用于验证Update
方法。TaskValidator()
和TaskInsertValidator()
将用于验证Insert
方法。- 如果数据库字段发生更改,此代码将无法编译(这是一个非常好的特性!)。

然后我们添加一个文件 _DataBaseParticalClasses.cs_,并将以下代码添加到其中:
using FluentValidation;
using System;
using FluentValidation.Results;
using System.Collections.Generic;
using System.ComponentModel;
namespace RIATasks.Web
{
#region public partial class Task
public partial class Task
{
public List<string> Errors = new List<string>();
}
#endregion
}
这会向 Tasks
表添加一个 Errors
列。它提供了一个属性,可以将任何错误传递回 Silverlight 应用程序。您应该为每个数据库表都添加此项。
将以下代码添加到文件中:
public partial class RIATasksDBDataContext
{
#region UpdateTask
// This runs whenever the Task table is updated
partial void UpdateTask(Task instance)
{
TaskValidator validator = new TaskValidator();
ValidationResult results = validator.Validate(instance);
if (!results.IsValid)
{
// There was an error
foreach (var failure in results.Errors)
{
instance.Errors.Add(failure.ErrorMessage);
}
}
// Only proceed if there are no errors
if (instance.Errors.Count == 0)
{
this.ExecuteDynamicUpdate(instance);
}
}
#endregion
#region InsertTask
// this runs whenever a record is Inserted into the Task table
partial void InsertTask(Task instance)
{
TaskValidator validator = new TaskValidator();
TaskInsertValidator Insertvalidator = new TaskInsertValidator();
ValidationResult results = validator.Validate(instance);
ValidationResult Insertresults = Insertvalidator.Validate(instance);
if (!results.IsValid)
{
foreach (var failure in results.Errors)
{
instance.Errors.Add(failure.ErrorMessage);
}
}
if (!Insertresults.IsValid)
{
foreach (var failure in Insertresults.Errors)
{
instance.Errors.Add(failure.ErrorMessage);
}
}
// Only proceed if there are no errors
if (instance.Errors.Count == 0)
{
// No errors - proceed with Update
this.ExecuteDynamicInsert(instance);
}
}
#endregion
}
此代码实现了Linq to SQL类中的 Update
和 Insert
部分方法。
此代码调用 TaskValidator()
和 TaskInsertValidator()
方法中的验证代码。如果存在错误,它们将被添加到 Errors
字段,并且不会进行数据库更改。
如果没有错误,将为 Update
调用 this.ExecuteDynamicUpdate(instance)
,为 Insert
调用 this.ExecuteDynamicInsert(instance)
。
Web 服务

Web Service 方法需要修改以处理任何错误。
这是修改后的 Update
方法:
#region UpdateTask
[WebMethod]
public Task UpdateTask(Task objTask)
{
RIATasksDBDataContext DB = new RIATasksDBDataContext();
try
{
var result = (from Tasks in DB.Tasks
where Tasks.TaskID == objTask.TaskID
where Tasks.UserID == GetCurrentUserID()
select Tasks).FirstOrDefault();
if (result != null)
{
result.TaskDescription = objTask.TaskDescription;
result.TaskName = objTask.TaskName;
result.DueDate = objTask.DueDate;
DB.SubmitChanges();
// Set any errors
objTask.Errors = result.Errors;
}
}
catch (Exception ex)
{
// Log the error
objTask.Errors.Add(ex.Message);
}
return objTask;
}
#endregion
Insert
方法:
#region InsertTask
[WebMethod]
public Task InsertTask(Task objTask)
{
RIATasksDBDataContext DB = new RIATasksDBDataContext();
try
{
Task InsertTask = new Task();
InsertTask.TaskDescription = objTask.TaskDescription;
InsertTask.TaskName = objTask.TaskName;
InsertTask.UserID = GetCurrentUserID();
InsertTask.DueDate = objTask.DueDate;
DB.Tasks.InsertOnSubmit(InsertTask);
DB.SubmitChanges();
// Set the TaskID
objTask.TaskID = InsertTask.TaskID;
// Set any errors
objTask.Errors = InsertTask.Errors;
}
catch (Exception ex)
{
// Log the error
objTask.Errors.Add(ex.Message);
}
return objTask;
}
#endregion
Silverlight 应用程序

在 Silverlight 应用程序中唯一需要修改的 _cs_ 文件是 _TabControlModel.cs_ 文件。之前的 Message
属性已更改为 String 类型的 List,并添加了 MessageVisibility
属性。
Update
方法更改为:
#region UpdateTask
private void UpdateTask(Task objTask)
{
// Call the Model to UpdateTask the Task
TasksModel.UpdateTask(objTask, (Param, EventArgs) =>
{
if (EventArgs.Error == null)
{
// Show any errors
Task objResultTask = EventArgs.Result;
Message = objResultTask.Errors.ToList();
// Set the visibility of the Message ListBox
MessageVisibility = (Message.Count > 0) ?
Visibility.Visible : Visibility.Collapsed;
}
});
}
#endregion
Insert
方法更改为:
#region InsertTask
private void InsertTask(Task objTask)
{
// Call the Model to Insert the Task
TasksModel.InsertTask(objTask, (Param, EventArgs) =>
{
if (EventArgs.Error == null)
{
// Set the CurrentTaskID Property
// So it can be selected when Tasks re-load
CurrentTaskID = EventArgs.Result.TaskID;
// Show any errors
Message = EventArgs.Result.Errors.ToList();
// Set the visibility of the Message ListBox
MessageVisibility = (Message.Count > 0) ?
Visibility.Visible : Visibility.Collapsed;
// If there are no errors - refresh Task List
if (Message.Count == 0)
{
GetTasks();
}
}
});
}
#endregion
显示错误

最后一步是在Microsoft Expression Blend 4+中打开项目,并将一个 ListBox
拖放到视图上……

……然后将 Message
集合从数据上下文窗口拖动……

……并将其拖放到 ListBox
上以创建绑定,使其显示错误消息。

接下来,在 ListBox
上,选择可见性旁边的高级选项。

将其绑定到 MessageVisibility
属性。当没有错误时,这将隐藏 ListBox
。
简单验证
对于任何需要进行类型验证的 UI 元素,也就是说,您希望确保 Date
字段输入的是日期,或者数字字段输入的是整数,您可以使用以下方法:基本的 Silverlight View Model 验证。
对于其他所有情况,您可以使用此处描述的方法。虽然这演示了仅验证Linq to SQL类,但相同的验证类也可以在 Web Service 方法中调用。这将使您能够创建跨多个表的复杂业务规则。
这主要使您能够将所有验证集中在一个地方,供多个Silverlight 应用程序和 ASP.NET 网站使用。