实体/业务对象验证的建议,其中验证依赖于其他实体/服务

时间:2011-11-04 15:45:05

标签: wpf silverlight validation mvvm entity

上下文

对于使用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();
}

2 个答案:

答案 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(),然后在保存之前处理任何问题。在我的具体情况下,我会将错误消息存储在一个集合中然后查询它们(这也允许我存储几个错误消息而不是第一个错误消息)