2

在Silverlight中使用MVVM结合RIA服务构建和验证数据是否有最佳实践或广泛接受的方法?使用WCF RIA和MVVM模式在UI上验证数据

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

现在,如果我想通过ViewModel而不是直接通过实体公开一些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框架,但我希望看到一般适用的答案。

回答

2

我使用RIA和Caliburn.Micro,并且非常满意我的客户端验证解决方案。

我所做的是在Screen(由Caliburn.Micro提供)和我的实际应用程序VM(在您的情况下为EmployeeViewModel)之间放置ValidationBaseViewModelValidationBaseViewModel implements INotifyDataErrorInfo,这样您所谈论的管道代码只写一次。我然后从(Caliburn.Micro)的重写PropertyChangedBase.NotifyOfPropertyChange添加/删除/差错通知的经由ValidationBaseViewModel用下面的代码:

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编辑的一个实例。

与卡利协同程序组合,这允许我做像一些很酷的东西如下:

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

    ... 
} 
+0

这很好,谢谢。 – 2011-04-11 00:25:35

-1

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

+0

我在中间层的验证工作很好,我遇到的问题是如何在保持干净的MVVM设计的同时正确连接实体和视图模型。 – 2010-11-21 19:18:48

0

如果你不想使用外部资源或框架,然后我你可以有一个ViewModelBase实现INotifyDataErrorInfo

该类将有ValidateProperty(string propertyName, object value)验证特定的属性,Validate()方法来验证整个对象。在内部使用Validator类来返回ValidationResult
如果您使用反射器,它可以是非常容易通过模仿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)); 
    } 
}