上下文
对于使用MVVM模式的WPF应用程序,我使用实体上的IDataErrorInfo接口验证我的实体(/ business对象),以便WPF自动调用我的实体中的验证规则,并且验证错误会自动出现在View中。 (在本文中受到Josh Smith的启发:http://joshsmithonwpf.wordpress.com/2008/11/14/using-a-viewmodel-to-provide-meaningful-validation-error-messages/
这适用于简单的验证规则,例如(名称> 10个字符,值必须> 0)
但是当模型中的验证规则更复杂时(如名称必须是唯一的/属性的最大值在另一个实体中定义)该怎么办。我首先考虑通过让实体引用存储库来解决这个问题,但是这感觉不太好,因为我认为只应该存储库到实体的引用而不是其他方式(创建循环引用)< / p>
从配方实体到ConfigurationRepository的引用是否“合法”。或者你有更好的建议吗? 您是否有建议如何实施实体/业务对象验证,其中验证依赖于其他实体/服务,如下例所示。
在我的现实世界问题的简化代码之下。 在Recipe实体中,我想验证最高温度是否小于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)的类.RatestSetpoint必须为&lt; 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();
}
答案 0 :(得分:2)
对于基于业务规则的验证,我通常会公开我的ViewModel可以设置的验证委托。
例如,Recipe的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
中进行验证,这样就可以实现。
我IDataErrorInfo
课程Recipe
的实际实现如下:
#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
答案 1 :(得分: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
{
}
然后我为对象级验证创建了一个Validate()方法:
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派生的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();
}
一旦设置完毕,我就可以在验证方法中添加字段验证(存储在我的CBO中而不是分散在我的代码中),我可以在Validate()方法中添加对象级验证。同样,我可以轻松地自定义控件在验证方面的表现。
要使用它,在我的VM中我首先调用.Validate(),然后在保存之前处理任何问题。在我的具体情况下,我会将错误消息存储在一个集合中然后查询它们(这也允许我存储几个错误消息而不是第一个错误消息)