【问题标题】:Validating data on the UI with WCF RIA and the MVVM pattern使用 WCF RIA 和 MVVM 模式验证 UI 上的数据
【发布时间】:2011-05-11 16:06:01
【问题描述】:

在 Silverlight 中使用 MVVM 和 RIA 服务来构建和验证数据是否存在最佳实践或被广泛接受的方法?

这是我的问题的症结所在。假设我有一个 EmployeeView、EmployeeViewModel 和一些 Employee 实体。在常规 RIA 应用程序中,我将在视图上公开该 Employee 实体,并且“免费”获得验证,因为实体实现了 INotifyDataErrorInfo 和 IDataErrorInfo(正确吗?)。

现在,如果我想通过 ViewModel 而不是直接通过 Entity 公开一些 Employee 属性,那么它会变得更加复杂。我可以直接公开我需要的位并将它们挂钩到后端的实体中,如下所示:

    private Employee _employee;

    public EmployeeViewModel()
    {
        _employee = new Employee();
    }

    public string Name
    {
        get { return _employee.Name; }
        set
        {
            _employee.Name = value;
            // fire property change, etc.
        }
    }

...但我失去了实体的美味“免费”验证。否则,我可以直接在视图模型中公开实体,就像这样

    private Employee _employee;
    public Employee Employee
    {
        get { return _employee; }
    }

    public EmployeeViewModel()
    {
        _employee = new Employee();
    }

在这种情况下,视图将直接绑定到 Employee 实体并在其中找到它的属性,如下所示:

<StackPanel DataContext="{Binding Employee}">
    <TextBox Text="{Binding Name}" />
</StackPanel>

使用这种方法,我们获得了“免费”验证,但它并不完全是 MVVM 的干净实现。

第三种选择是我自己在 VM 中实现 INotifyDataErrorInfo 和 IDataErrorInfo,但考虑到使用上述解决方案对我来说使用上述解决方案并稍微不那么“干净”,这似乎是一个非常多的管道代码但在一天结束时要容易得多。

所以我想我的问题是,这些方法中的哪一种适用于哪种情况?我错过了更好的方法吗?

如果它是相关的,我正在研究 Caliburn.Micro MVVM 框架,但我很想看到普遍适用的答案。

【问题讨论】:

    标签: silverlight validation mvvm wcf-ria-services caliburn.micro


    【解决方案1】:

    我将 RIA 与 Caliburn.Micro 结合使用,我对我的客户端验证解决方案非常满意。

    我所做的是在Screen(由 Caliburn.Micro 提供)和我的实际应用程序虚拟机(在您的情况下为EmployeeViewModel)之间放置一个ValidationBaseViewModelValidationBaseViewModel 实现了INotifyDataErrorInfo,因此您谈论的管道代码只编写一次。然后,我使用以下代码通过 ValidationBaseViewModel 从 (Caliburn.Micro) PropertyChangedBase.NotifyOfPropertyChange 的覆盖中添加/删除/通知错误:

    public override void NotifyOfPropertyChange(string property)
    {
        if (_editing == null)
            return;
    
        if (HasErrors)
            RemoveErrorFromPropertyAndNotifyErrorChanges(property, 100);
    
        if (_editing.HasValidationErrors)
        {
            foreach (var validationError in
                           _editing.ValidationErrors
                                   .Where(error => error.MemberNames.Contains(property)))
            {
                AddErrorToPropertyAndNotifyErrorChanges(property, new ValidationErrorInfo() { ErrorCode = 100, ErrorMessage = validationError.ErrorMessage });
            }
        }
    
        base.NotifyOfPropertyChange(property);
    }
    

    这实际上是在另一个 VM 中(在 ValidationBaseViewModel 和 EmployeeViewModel 之间),定义如下:

    public abstract class BaseEditViewModel<TEdit> :
                                    ValidationBaseViewModel where TEdit : Entity
    

    其中Entity 是RIA System.ServiceModel.DomainServices.Client.Entity_editing 类成员是这种TEdit 类型的实例,当前VM 正在对其进行编辑。

    结合 Caliburn 协程,这让我可以做一些很酷的事情,如下所示:

    [Rescue]
    public IEnumerable<IResult> Save()
    {
        if (HasErrors)
        {
            yield return new GiveFocusByName(PropertyInError);
            yield break;
        }
    
        ...
    }
    

    【讨论】:

      【解决方案2】:

      如果您不想使用外部资源或框架,那么我可以有一个实现INotifyDataErrorInfoViewModelBase

      该类将具有 ValidateProperty(string propertyName, object value) 来验证特定属性,以及 Validate() 方法来验证整个对象。在内部使用Validator 类返回ValidationResults。
      如果您使用反射器,则可以通过将Entity 类本身的验证过程模拟到ViewModelBase 来实现非常容易

      虽然它不是“免费”的,但仍然相对便宜。

      这是IDataErrorInfo 的示例实现。虽然没有经过测试,但会给你这个想法。

      public class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
      {
      
        /*
         * InotifyPropertyChanged implementation
         * Consider using Linq expressions instead of string names
         */
      
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        public IEnumerable GetErrors(string propertyName)
        {
          if (implValidationErrors == null) return null;
          return ImplValidationErros.Where(ve =>
            ve.MemberNames.Any(mn => mn == propertyName));
        }
      
        public bool HasErrors
        {
          get
          {
            return implValidationErrors == null || ImplValidationErros.Any();
          }
        }
      
        private List<ValidationResult> implValidationErrors;
        private List<ValidationResult> ImplValidationErros
        {
          get
          {
            return implValidationErrors ?? 
              (implValidationErrors = new List<ValidationResult>());
          }
        }
        private ReadOnlyCollection<ValidationResult> validationErrors;
        [Display(AutoGenerateField = false)]
        protected ICollection<ValidationResult> ValidationErrors
        {
          get
          {
            return validationErrors ?? 
              (validationErrors =
              new ReadOnlyCollection<ValidationResult>(ImplValidationErros));
          }
        }
        protected void ValidateProperty(string propertyName, object value)
        {
          ValidationContext validationContext =
            new ValidationContext(this, null, null);
          validationContext.MemberName = propertyName;
          List<ValidationResult> validationResults =
            new List<ValidationResult>();
      
          Validator.TryValidateProperty(
            value, 
            validationContext, 
            validationResults);
      
          if (!validationResults.Any()) return;
      
          validationResults
            .AddRange(ValidationErrors
            .Where(ve =>
              !ve.MemberNames.All(mn =>
                mn == propertyName)));
      
          implValidationErrors = validationResults;
      
          if (ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }
      }
      

      【讨论】:

        【解决方案3】:

        您可以使用部分类来扩展您的实体并通过 idataerrorinfo 添加数据验证。

        【讨论】:

        • 我在中间层的验证效果很好,我遇到的问题是如何正确地将实体与视图模型连接起来,同时仍然保持干净的 MVVM 设计。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-02-12
        • 1970-01-01
        • 2011-07-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多