【问题标题】:MVVM / NHibernate - How to do dynamic model validation?MVVM / NHibernate - 如何进行动态模型验证?
【发布时间】:2012-10-01 21:07:37
【问题描述】:

我正在使用 MVVM 模式构建一个 C# WPF 应用程序。我有使用 NHibernate 来持久化我的域模型的存储库类。

我的模型包含一个更大的树结构(Recipes 包含 Operations 包含 Phases)。操作和阶段都包含一个动态的键值映射列表作为IDictionary<string, string>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 类是较大树结构(配方/操作/阶段)的一部分,我需要为集合创建包装器并将集合更改映射回底层集合,这可能是一项复杂的任务。

  3. 创建一个服务类,只要我想验证它,我就会调用它来传递操作。

    我在这里看到的问题是服务是无状态的,因此每次用户更改单个参数时都必须重新验证整个参数列表。这似乎不是最好的方法,尤其是当我想在参数的验证状态更改时为 UI 触发某种更改事件时。

解决我的问题的常用方法是什么?有没有我还没有找到完全符合我需要的模式?我的意思是,有很多实现依赖于外部模式定义进行验证(阅读:XML/XSD 和类似的文档结构),只需要一些天才已经找到了我的问题的完美解决方案;-)帮帮我吧!

【问题讨论】:

    标签: c# wpf validation nhibernate mvvm


    【解决方案1】:
    1. 将验证逻辑添加到模型类本身。
    2. - 不是最好的方法,因为你会用逻辑破坏 POCO,你最终会使用 ActiveRecord 模式,它不是那么干净。
    3. 为包装我的 Operation 对象的验证逻辑创建一个装饰器类。
    4. - 认为这是一种更好的方法,不同之处在于您将必须包装已经存在的包装器,并且您最终也会破坏抽象级别,因此也不推荐。
    5. 创建一个服务类,只要我想验证它,我就会调用它来传递操作。
    6. - 完成这些事情可能也不是你的情况(如果我正确理解你所说的关于 web 服务或其他类型的远程服务),如果你对这些验证规则有限制,那么这个解决方案更合适,例如集中用于多个客户端,而不是紧贴具体应用程序。

    我赞成以下解决方案

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

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

    • 您在项目中使用的每个 POCO 对象的验证规则,并且它需要验证(或者,您也可以在更高级别应用这些规则,意味着不是 POCO,而是 POCO 上的一些 Wrapper,如果您已经这样的实现,但作为最佳实践,尝试将规则直接应用于 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 Usings using System.Linq; using FluentValidation; using FluentValidation.Results; #endregion Usings namespace Core.Validation { public class OperationValidator : AbstractValidator { #region .Ctors /// /// .Ctor used for operation purpose /// public OperationValidator() { Validate(); } #endregion .Ctors /// /// Validation rules for Operation /// private void Validate() { //here you may get validations rules from you xml file and structure the following code after your requirements //Name RuleFor(x => x.Name).Length(2, 20).WithMessage("Operation name should have length between 2 and 20 symbols"); //ApplicationFormsWrapper Custom(entity => { foreach (var item in entity.Phases) if (item.PhaseState == null) return new ValidationFailure("Phases", "First Phase is missing"); return null; }); } } }

    3-添加 ValidatableDomainObject 类,它实现 System.ComponentModel.IDataErrorInfo(提供提供自定义错误信息的功能,用户界面可以绑定到该类):

    #region Usings using System.ComponentModel; using System.Linq; using FluentValidation.Results; using Core.Validation.Helpers; #endregion Usings 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; } } } }

    4-添加以下ValidationHelper:

    #region Usings using System; using System.Text; using FluentValidation; using FluentValidation.Results; #endregion Usings 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();

    1. 在视图级别,您可以编写类似这样的内容(在这种情况下,如果出现任何验证错误,则它们是直接从 OperationValidator 类中获取的):

    注意:实现基于 FluentValidation(一个用于 .NET 的小型验证库,使用 fluent 接口和 lambda 表达式),请参阅http://fluentvalidation.codeplex.com/,当然你也可以使用另一个,希望我成功地描述了将验证与域对象解耦的机制。

    【讨论】:

    • “这不是最好的方法,因为你会用逻辑破坏 POCO,你最终会使用 ActiveRecord 模式,它不那么干净。” - 这是一个糟糕的建议,与领域模型模式所禁止的直接矛盾 - 将逻辑放入模型中,这就是模型的用途!
    猜你喜欢
    • 2019-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-24
    相关资源
    最近更新 更多