MVVM / NHibernate - 如何实现动态模型验证?

6
我正在使用MVVM模式构建一个C# WPF应用程序。我有存储库类,使用NHibernate来持久化我的领域模型。
我的模型由较大的树形结构组成(包含操作和阶段的Recipe)。操作和阶段都包含一个动态的键值映射列表作为IDictionary。Operation的对应NHibernate映射为:
<class name="Operation" table="operations">
  <id column="id" type="int" generator="native" />
  <property name="Name" column="name" />

  <map name="Parameters" table="operation_params">
    <key column="operation" />
    <index column="param" type="string" />
    <element column="value" type="string" />
  </map>

  <list name="Phases" cascade="all-delete-orphan">
    <key column="operation" />
    <index column="`index`" />
    <one-to-many class="Phase" />
  </list>
</class>

现在,这部分很容易且运行良好。Operation类目前是一个几乎没有逻辑的POCO,只是一个简单的数据容器。
我的问题是:我必须根据应用程序从.xml文件中读取的外部模式验证参数。该模式包含单个参数的限制(范围、有效值等)以及多个参数之间的依赖关系(即有效值取决于另一个参数的值)。
最佳的集成验证逻辑的方法是什么?最近几天我阅读了很多,到目前为止,我遇到了以下替代方案:
1. 将验证逻辑添加到模型类本身中。 对此,我不知道如何将验证模式正确注入NHibernate创建的对象中。我不需要一直使用验证功能,只有在用户编辑参数或者我导入操作(例如从备份中)时才需要。因此,我可以在模型类中实现实际的验证逻辑,并在需要验证时使用属性注入验证规则? 将该功能添加到使用NHibernate存储的模型类中是否被认为是良好的做法,还是模型类应该保持“愚蠢”?
2. 为验证逻辑创建一个装饰器类,它包装我的Operation对象。 这样,每次需要验证时都会使用包装器,而只需要显示它时则使用原始模型类。我的问题在于,我的ViewModel类已经是包装器,所以我会得到另一层包装。此外,由于Operation类是较大树结构(Recipe/Operation/Phase)的一部分,因此我需要为集合创建包装器,并将集合更改映射回底层集合,这可能是一个复杂的任务。
3. 创建一个服务类,每当我想要验证操作时就调用它。 我看到的问题是该服务是无状态的,因此必须每次用户更改单个参数时重新验证整个参数列表。这似乎不是最佳方法,特别是当我想为UI触发某种更改事件时,该事件表示参数的验证状态更改时。
对于我的问题,常见的方法是什么?是否有我还没有找到的完美解决方案模式?我的意思是,有很多实现依赖于用于验证的外部模式定义(读取:XML/XSD和类似的文档结构),肯定有一些天才已经找到了我问题的完美解决方案;-) 帮帮我吧!
1个回答

6
  1. 在模型类本身中添加验证逻辑。
  2. - 不是最好的方法,因为您将通过逻辑来扩大 POCO(纯净的 C# 对象)并最终使用 ActiveRecord 模式,这种做法不太干净。
  3. 为包装我的操作对象的验证逻辑创建修饰符类。
  4. - 认为这是更好的方法,唯一的区别就是您需要包装已经存在的包装器,并且您也将扩大抽象层级,因此也不建议使用。
  5. 创建一个服务类,每当我想要验证它时,都会调用该类传递操作。
  6. - 如果我正确地理解了您所说的是关于 Web 服务或其他类型的远程服务,那么这种解决方案可能也不适合您。如果应验证规则应该集中在多个客户端上,并且不限于具体应用程序,则此解决方案更加适合。

我倾向于以下解决方案:

向您的解决方案添加一个验证器项目,其中包含:

  • 根据应用程序从 .xml 文件读取的外部架构验证参数的逻辑。

  • 用于每个您在项目中使用的 POCO 对象的验证规则,并且需要验证(或者,如果您已经有这样的实现,则可以在更高级别上应用这些规则,即不是 POCO 而是一些包装器上,但最佳实践是直接将规则应用于 POCO - 更干净,更正确)

因此:

1-您的 POCO 将包含属性和简单的验证 SelfValidate():


namespace Core.Domain {
    public class Operation : ValidatableDomainObject {
        #region Properties
        public virtual String Name { get; set; }
        public virtual ISet Phases { get; set; }     
        #endregion Properties
#region Validation public override ValidationResult SelfValidate() { return ValidationHelper.Validate(this); } #endregion Validation } }

2-您的 POCO 验证器将包含应基于 XML 文件应用于 POCO 验证的规则:


#region 引用

using System.Linq;
using FluentValidation;
using FluentValidation.Results;
#endregion 引用
namespace Core.Validation {
public class OperationValidator : AbstractValidator { #region 构造函数 /// /// 用于操作目的的构造函数 /// public OperationValidator() { 验证(); } #endregion 构造函数
/// /// 操作的验证规则 /// private void 验证() { //在此处,您可以从xml文件中获取验证规则,并根据要求结构化以下代码 //名称 RuleFor(x => x.Name).Length(2, 20).WithMessage("操作名称应在2到20个字符之间"); //ApplicationFormsWrapper Custom(entity => { foreach (var item in entity.Phases) if (item.PhaseState == null) return new ValidationFailure("Phases", "缺少第一个阶段"); return null; }); } } }
#region 引用 using System.ComponentModel; using System.Linq; using FluentValidation.Results; using Core.Validation.Helpers;
#endregion 引用
namespace Core.Domain.Base { public abstract class ValidatableDomainObject : DomainObject, IDataErrorInfo {
public abstract ValidationResult SelfValidate();
public bool IsValid { get { return SelfValidate().IsValid; } }
public string Error { get { return ValidationHelper.GetError(SelfValidate()); } }
public string this[string columnName] { get { var validationResults = SelfValidate(); if (validationResults == null) return string.Empty; var columnResults = validationResults.Errors.FirstOrDefault(x => string.Compare(x.PropertyName, columnName, true) == 0); return columnResults != null ? columnResults.ErrorMessage : string.Empty; } } } }
#region 引用 using System; using System.Text; using FluentValidation; using FluentValidation.Results;
#endregion 引用
namespace Core.Validation.Helpers { public class ValidationHelper { public static ValidationResult Validate(TK entity) where T : IValidator, new() where TK : class { IValidator validator = new T(); return validator.Validate(entity); }
public static string GetError(ValidationResult result) { var validationErrors = new StringBuilder(); foreach (var validationFailure in result.Errors) { validationErrors.Append(validationFailure.ErrorMessage); validationErrors.Append(Environment.NewLine); } return validationErrors.ToString(); } } }

它将允许您在应用程序代码中执行以下操作

  1. 在服务或视图模型级别上,可以执行以下操作以获取验证错误:
var operation = new Operation(){Name="A"};
var validationResults = operation.SelfValidate();
在视图层面,你可以像这样编写代码(在这种情况下,如果有任何验证错误出现,它们将直接从OperationValidator类中获取):
<TextBox Text="{Binding CurrentOperation.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
注意:此实现基于FluentValidation(.NET的一个小型验证库,使用流畅的接口和Lambda表达式),请参见http://fluentvalidation.codeplex.com/,但是当然你也可以使用其他验证库。希望我成功地描述了将验证从领域对象解耦的机制。

这不是最好的方法,因为你会在POCO中引入逻辑,最终会使用ActiveRecord模式,这并不是很干净的做法。这是可怕的建议,并且与领域模型模式所规定的相反 - 将逻辑放在模型中,这就是模型的作用! - codekaizen

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