65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用业务规则引擎验证 ASP.NET 和 MVC Web Forms

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (18投票s)

2011年8月16日

CPOL

14分钟阅读

viewsIcon

123657

downloadIcon

1818

学习如何使用业务规则引擎验证复杂的 Web 窗体。

如何使用业务规则引擎验证 ASP.NET 和 MVC Web Forms

Web Forms 验证简介

如您所知,HTML 表单的目的是将用户输入从客户端传递到服务器。Web 应用程序的目标之一是确保传入的数据有效、安全并可供进一步处理。这称为“输入验证”。输入验证过程可分为三个步骤:  

  1. 格式验证。例如,服务器需要确保字段不包含任何不安全的值,必需字段中有数据,不同类型的值可以安全地转换为那些类型,等等。通常,格式验证使用验证控件完成。
  2. 将接收到的字符串值转换为类型化值。例如,如果表单接受贷款申请,则传入的数据需要转换为某种贷款申请对象,其中字符串“年总收入”可以转换为类型为 System.Decimal 的 GrossIncome 属性。
  3. 针对强类型数据的可选业务规则验证。例如,服务器可能会检查年收入是否大于总债务,并且申请人是否已就业五年。

前两个步骤有据可查且易于理解。它们几乎总是必需的,并且在某种程度上可以使用通用或自定义工具和控件自动完成。但第三步并非如此简单。许多开发人员坚信他们从不在 Web Forms 中使用业务规则。而且他们中的大多数都错了。原因很简单:我们经常在不经意间将格式验证和规则验证混合在一个语句中。当然,一些小型表单根本不使用业务规则。但这通常是规则的例外。考虑以下代码

DateTime test;

if(txtLastEmployed.Text.Length == 0 ||
	!DateTime.TryParse(txtLastEmployed.Text, out test) ||
	test < DateTime.Now.AddYears(-5))
{
		DisplayWarningMessage("The date of last employment is either empty, 
			or has invalid format, or is in the past of limit.");
}

在此示例中,第一个条件是格式验证,第二个是类型转换,第三个是业务规则的评估。很容易看出,前两个条件永远不会改变,除非 LastEmployed 属性已从我们虚构的数据对象中移除。但第三个条件不能保证永远保持不变。由于其编写方式,如果业务所有者将五年期限更改为任何其他数字,我们将不得不重新编译、测试和部署整个应用程序,仅仅因为这个微小的更改。  

当然,我们可以始终将条件值存储在配置文件中。例如,以下代码片段演示了这种实现

try
{
	// some code
}
catch(Exception ex)
{
	if (ConfigurationManager.AppSettings["Production"] == "true")
	{
		LogException(ex);
		DisplayAnExcuse("Sorry, we screwed up!");
	}
	else throw ex;
}

此代码强制执行业务规则“如果环境是生产环境,则记录异常并道歉。否则,将异常抛出到堆栈中,以便开发人员可以处理它”。该规则非常小,只包含一个条件,并且可以轻松地在配置文件中更改其值,而无需重新编译整个 Web 应用程序。(请注意,此示例仅用于演示。除非预期存在特殊异常,否则您不希望在页面代码中处理常规异常。异常处理属于 Global.asax。)

但是,如果 Web Form 有数十个输入控件,并且必须强制执行复杂的业务规则(这些规则可能会(而且肯定会)在将来更改)?例如,大型购物车或企业税务表格?在这种情况下,如果我们采用业务规则引擎,就可以消除许多问题、错误、测试用例和维护难题。

在深入研究之前,让我们先简要谈谈业务规则和规则引擎。

Business Rules

业务规则是条件集合,可以根据其执行优先级分组为规则集。您无需学习任何特殊知识即可理解它们是什么以及它们如何工作。只需参加任何公司会议,聆听业务分析师解释软件需求。“我们需要确保所有已批准的申请人都年满 18 岁,并且信用评分至少为 640”。此要求是业务规则。让我们将其以机器可读的格式重写

If Applicant.Age is greater or equal to 18
	and GetCredit(Aplicant.SSN) is greater or equal to 640
then Approve
else Reject

Applicant 类通常称为“源对象”或“事实对象”。其所有公共值类型属性(Age、SSN)都称为“规则字段”或“事实”。一些规则引擎还支持规则内方法(GetCredit),它们可以在规则评估期间调用。

规则可以是两种类型:评估类型和执行类型。评估类型规则旨在只回答一个问题:“源对象是否符合规则?”。此类规则的返回始终是一个布尔值。这种类型的规则不能有作为规则评估结果而调用的操作。它们也不能有“else if”和“else”流程部分。以下是一个评估类型规则的示例

Check if Car.Year > (Today.Year - 4)
	and (Car.Engine.Type = Petrol or Car.Engine.Turbo = True)

主要的引擎还支持执行类型规则。这些规则允许将逻辑划分为流程部分,就像任何计算机语言中的“if - else”语句一样。执行类型规则只需要“if”部分。“else if”和“else”流程部分是可选的。每个流程部分必须包含一个或多个引擎在部分评估为 true 时调用的操作。.NET 引擎中的规则操作是返回 System.Void 的公共实例方法或静态方法。操作方法可以是无参的,也可以接受任意数量的值类型参数或值集合。执行类型规则还可以为源对象的属性赋值。以下是一个执行类型规则的示例

If Car.Engine.Cylinders = 16
	or (Car.Brand = "Ferrari" and Car.Price < 10,000)
	then BuyRightAway()
else if Car.Price > 30,000 then MakeOffer(25,000)
else ShopSomewhereElse()

业务规则引擎

我们可以定义规则引擎应实现的三项主要功能

  1. 无需重新编译主代码即可创建、编辑、测试、部署规则并针对源对象进行评估。
  2. 业务用户必须能够制定规则,而 IT 部门的参与度很低甚至为零。
  3. 规则评估必须快速。

构建它

那么,为什么规则引擎对 Web Form 验证如此有用呢?因为一旦设置好,它就可以让业务用户在不打扰您(开发人员)的情况下管理业务逻辑。当然,您可能会争辩说,引擎本身的成本(有时高达数十万美元)甚至实施和维护此类系统的成本,当您只需要验证一个 Web Form 时,即使它很复杂,也 hardly justifies。这类系统适用于银行和政府机构,它们针对数百万传入源对象执行数百个大型规则集。

然而,事实已不再如此。如今,如果我们使用最近开始出现的新型免费或廉价规则引擎,业务规则就非常适合 Web Form 验证。

那么,让我们来构建一个将使用此类引擎进行数据验证的 Web Form。不过,我们首先需要处理两件事。我们需要定义我们小型示例的要求,并且我们需要了解我们将要使用的引擎的详细信息。然后我们就可以开始。

为了简化本文并使补充项目更易于管理,我从一个供应商网站上获取了一个 VS 2012 MVC 演示项目,并对其进行了修改以满足本文的要求。使用该项目(下载链接在此文章顶部提供),您将能够创建一个规则,用测试数据填写表单,并在同一 HTML 视图中针对该规则评估您的输入。最终结果将如下图所示

但在实际生活中,您肯定希望将规则编辑器与 Web Form 分开。编辑器将作为您企业网络上的内部 Web 应用程序的一部分。业务用户将使用它来管理您的 Web Form 的验证规则,并将规则保存到数据库或文件系统中。需要验证的表单将托管在另一个网站上。它的服务器将负责从其存储位置加载正确的规则,并在每次用户提交表单时针对发布的输入进行评估。

在我们的示例中,我们将使用一个相对较新的 ASP.NET 和 MVC 业务规则引擎,名为 Code Effects,它实现为一个服务器控件。它有一个免费版本,支持 .NET 4.0 及以上版本,包含一个小的 .NET 程序集,您只需在项目中引用它,并且无需在服务器上安装或任何特殊帐户权限,因此它可以运行在任何地方。在此处下载 Code Effects 的副本:http://codeeffects.com/Doc/Business-Rule-Engine-Downloader

Code Effects 组件具有独特的 Web UI,允许规则作者通过简单地从上下文菜单中选择元素来创建业务规则。这与 Visual Studio 中的 IntelliSense 工作方式非常相似。它使规则的制定对业务人员来说是一个非常直观且简单的过程,几乎不需要学习曲线。

Code Effects 规则编辑器还使用括号来确定规则方程的执行顺序。这与传统方法截然不同,传统方法仅为每个方程分配一个“优先级”。使用括号极大地简化了规则作者的工作。例如,一个典型的具有四个方程的执行类型规则在传统规则引擎中可能看起来像这样

When
	And
		Name = "John" #priority 10
		Age < 60 #priority 100
		Or
			Street = "123 Main Street" #priority 1
			City = "London" #priority 10
Then Do()
Else DoNot()

对于业务用户来说,这实在不是一个友好的陈述。但有了括号,相同的规则现在几乎任何人都可以阅读和理解

if (Name = "John" and Age < 60)
	or (Street = "123 Main Street" and City = "London")
then Do
else DoNot

这只是我认为这些新引擎对 Web Form 验证很棒的众多原因之一。非常酷的东西。

不过,让我们不要过于沉迷于这个特定的引擎。尽管它具有出色的功能并且速度非常快,但它确实缺少一些大型企业通常认为非常有价值的关键功能。例如,它不支持 RETE 算法。尽管我参与的约 95% 的项目实际上并不需要 RETE,但仍需指出此引擎的这一缺点。但就我们而言,Code Effects 引擎将做得很好。

正如我之前所说,我从供应商那里获取了一个 MVC 演示项目(http://codeeffects.com/Doc/Business-Rule-Demo-Project),并移除了其中的大部分内容,只留下一个包含规则编辑器和一个简单 Web Form 的视图。该演示项目使用一个名为 Patient 的类作为规则编辑器的测试源对象。我对医疗程序/流程/政策一无所知,也从未见过任何医疗验证规则。但这正是本文的重点——让专业人士去担心他们的业务逻辑以及他们想如何处理它。我们只需要开发一个工具来使他们能够做到这一点。因此,我保留了 Patient 类作为我的数据源,并移除了其大部分成员以进一步简化此演示。

我将带您了解该项目及其主要元素。同样,这是 VS 2012 MVC 项目。供应商网站还提供 VS 2010 的 MVC 和 ASP.NET 的演示。

  • 该项目引用了位于 /Lib 文件夹中的 Code Effects 程序集 CodeEffects.Rule.dll。/Views/Web.config 文件引用了主要的 Code Effects 命名空间,第 18 和 19 行。
  • 主视图位于 /Views/Post/Index.cshtml 文件中。它通过调用三个 Code Effects 例程来声明规则编辑器:第一个初始化主 CSS 主题,第二个声明规则编辑器,定义其在页面上的位置并设置其基本值,第三个例程实际渲染编辑器的 HTML 并引用其客户端脚本
@{
       ViewBag.Title = "Web Form Validation Example";
       Html.CodeEffects().Styles()
              .SetTheme(ThemeType.White)
              .Render();   
}

@using (Html.BeginForm("Evaluate", "Post", FormMethod.Post))
{
       <div class="area">
              <div style="margin:20px 0;">
                     <span>Validation output:</span>
                     <span style="color:Red;">@ViewBag.Message</span>
              </div>
              <div style="margin:20px 0;">
                     @{
                           Html.CodeEffects().RuleEditor()
                                  .Id("ruleEditor")
                                  .Mode(RuleType.Ruleset)
                                  .ShowToolBar(false)
                                  .ShowHelpString(false)
                                  .Rule(ViewBag.Rule)
                                  .Render();
                     }
              </div>
       </div>
       <div class="area">
              <h1 class="title" style="margin-top:20px;">Web Form That Needs Validation</h1>
              @{
                     Html.RenderPartial("_PatientForm");
              }
       </div>
}

@{
       Html.CodeEffects().Scripts().Render();
}

我重用了原始演示的局部视图 /Views/Shared/_PatientForm.cshtml,将我们的简单 Web Form 添加到主 Index.cshtml 视图中。该表单中的控件代表我们 Patient 源对象的属性。

因为我们使用回发,所以视图将规则编辑器和 Web Form 包装在 HTML Form 标签中,该标签将整个内容发布到 Post 控制器的 Evaluate 操作,该操作将在下面介绍。

  • /Models/Patient.cs 文件中声明了 Patient 源对象。它定义了我将用于描述我们虚构的医疗患者的几个属性。它还声明了几个值得注意的成员
    • Output 属性。我将使用它来显示错误消息。由于我不会直接从规则设置其值,因此使用 ExcludeFromEvaluation 属性将其隐藏在规则编辑器之外。
    • 公共实例 Warn() 方法。它将用作规则操作,在特定控件的输入数据无效时设置错误消息。
    • 公共实例 IsEmail() 方法。此方法将用作 Code Effects 的规则内方法,以验证患者电子邮件地址的格式。规则内方法是 .NET 方法,可以在规则中像普通属性一样使用。
public class Patient
{
    public Patient()
    {
        this.Gender = Gender.Unknown;
    }
    
    [Field(Description = "Patient's name", Max = 30)]
    public string Name { get; set; }

    [Field(Description = "Patient's email", Max = 100)]
    public string Email { get; set; }

    [Field(ValueInputType = ValueInputType.User)]
    public Gender Gender { get; set; }

    [Field(Min = 0, Max = 200, Description = "Current pulse")]
    public int? Pulse { get; set; }

    public bool Allergies { get; set; }

    public Address Home { get; set; }
    
    [ExcludeFromEvaluation]
    public string Output { get; set; }

    [Method("Is Email")]
    public bool IsEmail(string email)
    {
        return Regex.IsMatch(
            email,
            @"^([\w-]+\.)*?[\w-]+@[\w-]+\.([\w-]+\.)*?[\w]+$",
            RegexOptions.IgnoreCase | RegexOptions.Compiled);
    }

    [Action]
    public void Warn(string message)
    {
        this.Output += "; " + message;
    }
}

使用 Code Effects,您可以将任何普通 .NET 类用作源对象,而无需向其添加规则元数据。编辑器将查找该类的所有公共值类型属性和字段,并将它们用作规则字段。但是,Code Effects 组件定义了许多属性,我们可以使用它们来指定引擎的行为方式。例如,通过用属性装饰源类最实用的功能之一是能够为字段、操作和规则内方法设置自定义 DisplayName 值。程序员可能觉得 Patient.Home.Street 等默认名称还可以,但业务用户更愿意使用“Home Street”。您可以在 Code Effects 的文档中找到有关源属性及其使用方法的详细信息。

  • /Controllers/PostController.cs 文件声明了我们的 Post 控制器,它使用两个 MVC 操作:GET Index 和 POST Evaluate,分别位于第 25 和 34 行。这两个操作都实例化 RuleModel 类,并通过 ViewBag 对象将其发送到主视图。除其他外,规则模型指示 Code Effects 反射我们的 Patient 类,并将其属性和方法用作规则字段、规则内方法和操作。Evaluate 操作还定义了规则评估逻辑,我将在本文稍后详细介绍。

这就是在 MVC 应用程序中设置 Code Effects 所需的全部内容。现在,让我们进入有趣的部分——创建实际的规则。

我们的 Web Form 中的每个控件都有自己的验证逻辑。Code Effects 允许我们通过将 RuleEditor.Mode 属性设置为 Ruleset 值来一次性评估所有逻辑。此模式允许我们在编辑器的同一规则区域中定义多个复杂规则,为每个需要验证的控件使用一个单独的规则。在 Code Effects 中,此规则集合称为规则集。Evaluator 类一次性评估规则集中的所有规则,而不是一个接一个地评估。

为了使此演示简单化,我将只创建三个规则来定义 Name、DOB 和 Email 字段的验证逻辑。当然,您可以运行此演示并创建任意数量的规则,以熟悉编辑器及其功能。这是我的规则集

If Name has no value or Name is “”
   then Warn (“The Name field cannot be blank; ”)

If Email has no value or Email is “” or Is Email(Email) is False
   then Warn (“The Email field cannot be blank and must be in a correct format; ”)

If Allergies is True and (Pulse has no value or Pulse is greater than 140)
   then Warn (“No patients with allergies who have abnormal pulse or no pulse at all!”)

以下是客户端的整体外观

如果我将 Name 和 Pulse 文本框留空,在 Email 文本框中输入“blah blah”,勾选 Allergies 复选框并单击 Validate 按钮,我将得到以下结果

我的 Web Form 发布到 Evaluate 操作,将 Patient 和 RuleModel 的实例作为参数传递。该操作首先确保规则区域不为空且提交的规则有效(PostController 的第 48 行)。然后,它从编辑器中获取我的规则集的 XML,创建 Evaluator<Patient> 类的一个新实例,根据 Patient 实例评估规则集,并将任何错误消息输出到 ViewBag 对象。

信不信由你,我们已经完成了 Web Form 的验证,并且可以根据我们自己的业务规则进行评估。我认为您已经意识到,阅读本文所需的时间比实际构建本文描述的代码所需的时间要长。当然,真正的系统会更复杂。Code Effects 支持许多功能,您可以阅读其文档以了解更多关于编辑器和引擎的信息。我想指出我们代码中还有一个有趣的地方。

请注意,Evaluate 操作验证我们的规则集所花费的精力有多么少。显然,如果业务规则缺少元素或元素顺序无效,其价值就非常小。机器不会创建规则——人会。因此,确保每个规则都有效且可用至关重要。

规则验证是所有主要规则引擎中的重要步骤。通常,它是一个独立的组件或一个您必须单独购买、安装和学习使用的过程。Code Effects 会自动为您完成所有这些工作。您无需编写任何代码即可验证您的规则。Code Effects 会拒绝无效规则并突出显示每个无效元素,因此您在创建规则时会立即知道错误所在。将鼠标悬停在每个高亮显示的元素上即可看到问题的详细描述。

希望本文能帮助您更深入地理解业务规则和 Web Form 验证。祝您编程愉快!

© . All rights reserved.