2011-11-04 78 views
2

語境建議實體/業務對象驗證,其中驗證是依賴於其他實體/服務

對於使用MVVM模式驗證我的實體(/業務對象)一個WPF應用程序實體,以便我的實體中的驗證規則由WPF自動調用,驗證錯誤自動顯示在視圖中。 (由約什 - 史密斯這篇文章的啓發:http://joshsmithonwpf.wordpress.com/2008/11/14/using-a-viewmodel-to-provide-meaningful-validation-error-messages/

這適用於像(名> 10個字符簡單的驗證規則確定,值必須是> 0)

但要做到什麼時候在模型驗證規則更復雜的(像名稱必須是唯一的/最大值的屬性是在另一個實體中定義的)我首先想到通過讓實體具有對存儲庫的引用來解決這個問題,但是這並不好,因爲我認爲應該只能從存儲庫中引用到實體,而不是其他方式(創建循環引用)

是否從Re中引用了「合法」 cipe實體添加到ConfigurationRepository中。或者你有更好的建議? 如果在驗證依賴於其他實體/服務的情況下如何實現實體/業務對象驗證,您是否有建議?

下面是我真實世界問題的簡化代碼。 在配方實體中,我想驗證最高溫度是否低於存儲在Configuration.MaximumTemperature中的值。 你會如何解決這個問題?

的配置實體(商店的最大容許溫度爲配方)

public class Configuration: INotifyPropertyChanged, IDataErrorInfo 
{ 
    private int _MaxTemperatureSetpoint; 
    public int MaxTemperatureSetpoint 
    { 
     get { return _MaxTemperatureSetpoint; } 
     set 
     { 
      if (value != _MaxTemperatureSetpoint) 
      { 
       _Setpoint = value; 
       RaisePropertyChanged("MaxTemperatureSetpoint"); 
      } 
     } 
    } 

簡化配方(類,其中用戶配置與所希望的溫度(TemperatureSetpoint配方)和期望時間(TimeMilliSeconds)。TemperatureSetpoint必須爲< Configuration.MaxTemperature)

public class Recipe: INotifyPropertyChanged, IDataErrorInfo 
{ 
    private int _TemperatureSetpoint; 
    public int TemperatureSetpoint 
    { 
     get { return _TemperatureSetpoint; } 
     set 
     { 
      if (value != _TemperatureSetpoint) 
      { 
       _Setpoint = value; 
       RaisePropertyChanged("Setpoint"); 
      } 
     } 
    } 

    private int _TimeMilliSeconds; 
    public int TimeMilliSeconds 
    { 
     get { return _TimeMilliSeconds; } 
     set 
     { 
      if (value != _TimeMilliSeconds) 
      { 
       _TimeMilliSeconds= value; 
       RaisePropertyChanged("TimeMilliSeconds"); 
      } 
     } 
    } 

    #region IDataErrorInfo Members 
    public string Error 
    { 
     get { throw new NotImplementedException(); } 
    } 

    public string this[string propertyName] 
    { 
     get 
     { 
      switch(propertyName) 
      { 
       case "TimeMilliSeconds": 
        //TimeMilliSeconds must be < 30 seconds 
        if (TimeMilliSeconds < 30000) 
        { return "TimeMilliSeconds must be > 0 milliseconds";} 
       case "TemperatureSetpoint": 

        //MaxTemperatureSetpoint < maxTemperature stored in the ConfigurationRepository 

        int maxTemperatureSetpoint = ConfigurationRepository.GetConfiguration().MaxTemperatureSetpoint; 
        if (TemperatureSetpoint> maxTemperatureSetpoint) 
        { return "TemperatureSetpoint must be < " + maxTemperatureSetpoint.ToString();} 
     } 
    } 

    #endregion 
} 

配方庫

public interface IRecipeRepository 
{ 
    /// <summary> 
    /// Returns the Recipe with the specified key(s) or <code>null</code> when not found 
    /// </summary> 
    /// <param name="recipeId"></param> 
    /// <returns></returns> 
    TemperatureRecipe Get(int recipeId); 

    .. Create + Update + Delete methods 
} 

配置庫

public interface IConfigurationRepository 
{ 
     void Configuration GetConfiguration(); 
} 

回答

2

進行驗證是基於業務規則,我通常會暴露出驗證委託我的ViewModel可以設置。

例如,視圖模型的配方中可能包含的代碼看起來像這樣:

public GetRecipe(id) 
{ 
    CurrentRecipe = DAL.GetRecipe(id); 
    CurrentRecipe.AddValidationErrorDelegate(ValidateRecipe); 
} 

private string ValidateRecipe(string propertyName) 
{ 
    if (propertyName == "TemperatureSetpoint") 
    { 
     var maxTemp = Configuration.MaxTemperatureSetpoint; 
     if (CurrentRecipe.TemperatureSetpoint >= maxTemp) 
     { 
      return string.Format("Temperature cannot be greater than {0}", maxTemp); 
     } 
    } 
    return null; 
} 

的想法是,你Model應該只包含原始數據,因此它應該只驗證原始數據。這可以包括驗證最大長度,必填字段和允許的字符等內容。包含業務規則的業務邏輯應在ViewModel中進行驗證,並允許這樣做。

Recipe類實際執行的我IDataErrorInfo應該是這樣的:

#region IDataErrorInfo & Validation Members 

/// <summary> 
/// List of Property Names that should be validated 
/// </summary> 
protected List<string> ValidatedProperties = new List<string>(); 

#region Validation Delegate 

public delegate string ValidationErrorDelegate(string propertyName); 

private List<ValidationErrorDelegate> _validationDelegates = new List<ValidationErrorDelegate>(); 

public void AddValidationErrorDelegate(ValidationErrorDelegate func) 
{ 
    _validationDelegates.Add(func); 
} 

#endregion // Validation Delegate 

#region IDataErrorInfo for binding errors 

string IDataErrorInfo.Error { get { return null; } } 

string IDataErrorInfo.this[string propertyName] 
{ 
    get { return this.GetValidationError(propertyName); } 
} 

public string GetValidationError(string propertyName) 
{ 
    // If user specified properties to validate, check to see if this one exists in the list 
    if (ValidatedProperties.IndexOf(propertyName) < 0) 
    { 
     //Debug.Fail("Unexpected property being validated on " + this.GetType().ToString() + ": " + propertyName); 
     return null; 
    } 

    string s = null; 

    // If user specified a Validation method to use, Validate property 
    if (_validationDelegates.Count > 0) 
    { 
     foreach (ValidationErrorDelegate func in _validationDelegates) 
     { 
      s = func(propertyName); 
      if (s != null) 
      { 
       return s; 
      } 
     } 
    } 

    return s; 
} 

#endregion // IDataErrorInfo for binding errors 

#region IsValid Property 

public bool IsValid 
{ 
    get 
    { 
     return (GetValidationError() == null); 
    } 
} 

public string GetValidationError() 
{ 
    string error = null; 

    if (ValidatedProperties != null) 
    { 
     foreach (string s in ValidatedProperties) 
     { 
      error = GetValidationError(s); 
      if (error != null) 
      { 
       return error; 
      } 
     } 
    } 

    return error; 
} 

#endregion // IsValid Property 

#endregion // IDataErrorInfo & Validation Members 
+0

感謝您的回答!我喜歡將業務規則注入模型的想法,因爲這會將模型的依賴關係移除到存儲庫。 我看到的唯一缺點是模型驗證現在分散在代碼中(在簡單屬性模型中,在ViewModel中進行驗證,依賴於其他服務),而不是在1個地方(模型) –

+0

@ArjendenHartog You也可以在創建對象的任何層中添加驗證。例如,您可以有一個「CustomerRepository」查詢數據庫,創建模型,添加驗證,然後將對象返回給調用它的人。 – Rachel

0

說實話,我發現,在WPF驗證方法出爐的不完整和/或不夠優雅。我發現使用WPF方法會在我的應用程序中分散驗證代碼和邏輯,甚至會在我的UI中放入一些驗證代碼和邏輯。和你一樣,我爲所有事情都使用了自定義業務對象(CBO),並且我真的想保留我的對象中的驗證,因爲我在多個項目(Web服務,UI,移動設備等)中使用它們。

我所做的就是帶走我的CBO(在這種情況下是Recipe),並添加一些驗證方法作爲屬性。例如:

public Func<string> NameValidation 
    { 
     get 
     { 
      return() => 
      { 
       string result = null; 
       if (String.IsNullOrEmpty(Name)) result = "Name cannot be blank"; 
       else if (Name.Length > 100) result = "Name cannot be longer than 100 characters"; 
       return result; 
      }; 
     } 
    } 

之後,我裝飾它與自定義屬性:

[AttributeUsage(AttributeTargets.Property)] 
    public class CustomValidationMethod : Attribute 
    { 
    } 

然後我創建對象級檢驗一個驗證()方法:

public override void Validate() 
    { 
     var a = GetType().GetProperties().Where(w => w.GetCustomAttributes(typeof(CustomValidationMethod), true).Length > 0); 
     foreach (var a2 in a) 
     { 
      var result = a2.GetValue(this, null) as Func<string>; 
      if (result != null) 
      { 
       var message = result(); 
       if (message != null) 
        //There was an error, do something 
       else if (message == null && Errors.ContainsKey(a2.Name)) 
        //There was no error 
      } 
     } 
    } 

然後我創建支持我的驗證的自定義控件。在這種情況下,這是我從標準的ComboBox獲得的組合框,並添加以下代碼:

public Func<string> ValidationMethod 
    { 
     get { return (Func<string>) GetValue(ValidationMethodProperty); } 
     set { SetValue(ValidationMethodProperty, value); } 
    } 

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 
    { 
     base.OnPropertyChanged(e); 
     if (ValidationMethod != null && !String.IsNullOrEmpty(ValidationMethod())) 
      SetControlAsInvalid(); 
     else 
      SetControlAsValid(); 
    } 

一旦設置完畢,我可以在驗證方法(存儲在我的社區組織添加字段驗證而不是分散在我的代碼中),我可以在我的Validate()方法中添加對象級驗證。同樣,我可以輕鬆定製控件在驗證方面的表現。

要使用這個,在我的VM中,我會先調用.Validate(),然後在保存之前處理任何問題。在我的情況下,具體來說,我會將錯誤消息存儲在一個集合中,然後查詢它們(這也允許我存儲多個錯誤消息而不是第一個錯誤消息)

+0

嗨CamronBute,感謝您的回答。我喜歡重寫OnPropertyChanged以強制自動完成驗證的想法。對於我的主要問題,我無法找到答案:「如果驗證依賴於其他實體/服務,您是否有實施Entity/Business對象驗證的建議,如下例所示。」 –

+0

您可以在幾個不同的地方添加驗證。你可以通過創建一個方法來檢查ViewModel,或者把它放在.Validate()方法中。我個人沒有一個方法或這樣做,但我會將它添加到.Validate()方法中,因爲您知道每次都會調用.Validate()。 – CamronBute