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)); 
    } 
}