使用自定义规则进行动态验证

6
我已经使用.Net语言4年了。我使用WCF、ASP.NET来开发3层和5层应用程序,同时也使用C#来开发Windows应用程序。每次我开始一个项目时,业务规则和验证是一个问题。
那么我应该把自定义验证规则放在哪里(按钮点击事件、页面加载或者在我的类的setter/getter中)?
如果一个项目很大,只有一个字段需要从5个字符变成7个字符——为什么我要重建整个项目(或商业类项目)?
我认为如果我有一个文件来存储我的自定义规则,那么当需要更改时,我可以简单地将新规则放在其中。我在互联网上阅读了一些文章,提供了基于XML的文件来完成此目的,但这似乎有问题,因为:
- 没有智能提示,并且XML文件中的错误很难找到 - 我们必须编写自定义XML解析器 - 由于此方法需要大量转换,所以速度非常慢
我的问题是:是否有设计模式或使用.NET方法(反射、表达式树、Lambda表达式、动态、DLL的运行时创建等)来使用自定义规则进行动态验证?
编辑1) 属性呢?我们可以结合反射使用它们进行自定义验证吗?使用此方法,我们可以根据另一个属性(例如P1应为P2+1)验证属性吗?

Attribute 为类等提供了一些元数据。您可以在 property1 上定义自定义属性,以便将其与同一实例的 property2 相关联。但是这种方法很复杂,会导致硬编码到依赖规则。 - Devendra D. Chavan
2个回答

5
最好的表示业务规则的方式是使用XML。要充分利用这种表示法,您应该从定义规则引擎数据模型的结构开始,即回答以下问题。
  1. 什么是规则?
  2. 规则可以分类吗?
  3. 规则是否包含常见属性(属性),例如允许的值、格式等?
完成此操作后,创建一个虚拟规则XML,然后根据此XML导出XML模式。xsd.exe工具可以帮助您创建模式。如果您可以使用Altova XmlSpy等工具,则更容易创建模式。
至于您特定问题的答案,
  • 我们无法使用Intellisense,如果XML文件中有错误,很难找到它。
一旦有了模式,Visual Studio提供了充足的支持来创建XML(包括Intellisense和验证)。
  • 我们应该编写自定义的XML解析器

不需要,XmlSerializer Class提供了序列化/反序列化逻辑,即将规则XML转换为规则数据模型,反之亦然。

  • 由于此方法需要大量转换,因此非常缓慢

嗯,与硬编码规则(将规则作为类嵌入到程序集中)相比,这部分是有效的观点,但此方法的灵活性远远超过任何性能缺陷。如果规则发生变化,您无需重新构建解决方案。在大多数情况下,性能影响很小。

除非您有严格的性能标准,否则XML方法是实现规则引擎的首选方式。请记住,您的架构越松散耦合,运行时的灵活性就越高,但对性能产生负面影响。

示例规则

<RulesEngine>
  <Rules>
    <Rule Id="Rule1">
      <Function>
        <Equals>
          <Property name="Property1" classId="MyClassId"/>
            <Sum>
              <Property name="Property2" classId="MyClassId"/>
              <Constant type="UInt16" value="1"/>
            </Sum>
          </Equals>
        </Function>
      </Rule>
    </Rules>
    <Classes>
    <Class name="MyNamespace.MyClass" Id="MyClassId">
      <Property name="Property1" type="UInt16"/>
      <Property name="Property2" type="UInt16"/>
    </Class>
  </Classes>
</RulesEngine>

规则引擎需要解释这个规则并相应地推断其含义。

谢谢,如果你想比较XML和动态生成的dll和类,你有什么想法? - Arian
1
是的,这种方法类似于表达式树,只是符号表示不同。如果您熟悉这种方法,可以使用它们。 - Devendra D. Chavan
1
我没有关于表达式树的第一手经验,但你可以参考MSDN(Expression trees)或者Understanding Expression Trees。更好的方法是在stackoverflow上寻找关于表达式树的问题。 - Devendra D. Chavan
我认为如果我使用数据库而不是XML,那会更好,因为在一个使用会话变量的大型网站中,替换XML文件会导致所有会话被放弃。 - Arian
能否在需要时检索规则,而不是一次性加载它(SAX解析器)?如果您有一个规则层次结构,将其表示为XML比数据库更容易。答案使用DOM方法即将XML加载到内存中。 - Devendra D. Chavan
显示剩余4条评论

5
看一下 FluentValidation。它使用表达式,您可以创建条件验证(例如,如果这个属性满足某些条件,则验证这些属性)。FV可能不够动态,但您获得了智能感、表现力和类型安全性。它的通用性意味着它运行相当快。您可以通过传递验证委托或自定义验证器来注入一些运行时动态,可以做任何您想做的事情。
这确实意味着您需要重新编译,但是您可以将验证器放在单独的程序集中。对于验证器而言,它不在类上/内部是有道理的,因为您经常发现验证是在上下文中执行的。例如,如果汽车有所有轮子,则它可能是有效的。但是,如果您试图驾驶它,而它没有电池,则它对于驾驶来说是“无效”的。尽管如此,我会将规则“靠近”它们所验证的内容,因为它们是您领域的一部分。
如果您需要一个依赖于一个或多个属性(包括它本身)的属性的规则,并且如果规则的条件不满足,则需要自定义消息,那么您也可以这样做。请考虑以下内容:
RuleFor(x => x.P1)
    .Must(x => x.P1 > x.P2)
    .Message("P1 must be one more than P2. P1 was {0}; P2 was {1}", x=>x.P1, x=>x.P2);

给出一个简单的比较,但你可以制作更为复杂的内容。

谢谢。FluentValidation 是一个非常有趣的验证框架。请查看编辑1。 - Arian
1
当然。FV使用命令式代码(例如RuleFor(...).Must(...))以流畅的方式创建关于有效性的声明,它可以设置依赖项,就像您使用属性的想法一样。我更喜欢前一种方法,因为依赖关系更简洁地指定(1行代码与分散在要验证的类型中的属性相比)。这还允许您有条件地创建依赖关系。 - Kit
谢谢。FV能否在两个属性之间创建依赖规则?比如P1=P2+1。这在文档中有说明吗? - Arian
3
好的,以下是您所需的操作步骤以及若未符合规则条件时的自定义消息:RuleFor(x => x.P1).Must(x => x.P1 == x.P2 + 1).Message("P1 必须比 P2 大 1。P1 是 {0};P2 是 {1}", x=>x.P1, x=>x.P2); 注意:本翻译保持原文意思,同时使语言更易懂。 - Kit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接