MVVM / NHibernate - 如何进行动态模型验证?

时间:2012-10-01 21:07:37

标签: c# wpf validation nhibernate mvvm

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

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

    < / LI>
  3. 创建一个服务类,每当我想验证它时,我都会调用该服务。

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

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

1 个答案:

答案 0 :(得分:6)

  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. 在服务或视图模型级别,您可以执行此操作以获取验证错误:
    2. var operation = new Operation(){Name="A"};
      var validationResults = operation.SelfValidate();
      

      1. 在View级别,你可能会写这样的东西(在这种情况下,如果出现任何验证错误,它们直接来自OperationValidator类):
      2. &lt; TextBox Text =“{Binding CurrentOperation.Name,Mode = TwoWay,UpdateSourceTrigger = PropertyChanged,ValidatesOnDataErrors = True}”&gt;

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