2

在 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 的干净实现。

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

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

如果它是相关的,我正在查看 Caliburn.Micro MVVM 框架,但我很想看到通用的答案。

4

3 回答 3

2

我正在将 RIA 与 Caliburn.Micro 一起使用,并且对我的客户端验证解决方案非常满意。

我所做的是ValidationBaseViewModel介于Screen(由 Caliburn.Micro 提供)和我的实际应用程序虚拟机(EmployeeViewModel在您的情况下)之间。ValidationBaseViewModel实现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,类成员是当前 VM 正在编辑_editing的这种类型的实例。TEdit

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

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

    ...
}
于 2011-04-10T21:16:15.410 回答
0

如果您不想使用外部资源或框架,那么我可以使用ViewModelBasethat implement INotifyDataErrorInfo

该类必须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));
  }
}
于 2011-05-12T22:59:22.967 回答
-1

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

于 2010-11-18T12:44:51.083 回答