MVVM - Validation

和自甴很熟 提交于 2019-11-30 12:03:11

问题


We're trying to figure out validation in the mvvm doing validation in the business logic or model. I've implemented the validate by exception type in our business logic - a simplified diagram can be found here:

If we've got lot's of inputs that are independent of each other, there is no problem, the exception is thrown, the textbox catches it an marks it's borders red for each wrong input. However, when we've got dependent values we're in trouble. e.g.

  • Value1 and Value2 in the model must not be the same, so we've got a validate function in each of those looking for the equals value and throw an exception if that happens

  • now, if we set Value1 to 0 and Value2 to 1 everything is fine

  • Value1 gets set in the GUI to 1 --> this one gets marked red, because the validation of the other values is not triggered, so Value2 in the GUI is not marked faulty

  • Value2 gets set to 2 in the GUI, now we've reached a valid state, but only Value2 gets validated, so Value1 still is marked as faulty

Is there a common pattern to solve that issue? we don't want to introduce a dependency in the GUI between the two textboxes, because this logic should only be present in the business logic layer.

Instead of implementing the validate by exception one could also implement the IDataErrorInfo interface, but the problem still exists, there is no way to force depending values to validate their values again, at least none that i can see :)

Any help is appreciated

cheers, manni


[cleanup, removed unecessary step]


15.11.2010 - Part2

ok, big rethought here, we're going with the businesslogic tier. here is our current planned configuration:

( the image is a bit small scaled here, please open it on a separate window to show it in full size) everything is more or less clear, except how to notify all the viewmodels/model clones of the different editors if the data-model under the business logic gets changed. one way to do it is to track the cloned models in the business logic which creates them. When the data-model is changed using the business logic commit(), all the other registered model clones can be notified of the changes and propagate them further. alternatively the business logic could post an event to which all the viewmodels subscribe so that they get the changes as well - could anyone give me a hint what's better?

Thanks again for the help, sorry i'm so mind-blocked ;)


回答1:


You could consider using the System.ComponentModel.IDataErrorInfo interface. This very handy interface gives you the ability to:

  • do validation in a MVVM compliant manner
  • do custom validation for any particular field (the validation could check several values if you want it to)
  • bind your UI to the validation errors

You implement IDataErrorInfo on your viewmodel (or even virtually in your view model base, and override it in your derived view models). Due to the nature of databinding, the values i need to check are all there in the view model, and i can test any combination of them. Of course you still have your validation in your business layer, but you no longer need to make a trip to your business layer (or Model) just to effect some validation.

Here is a quick example from a (WPF) screen that gathers some user details and does basic validation on them:

C# code:

    #region IDataErrorInfo Members

    /// <summary>
    /// Gets an error message indicating what is wrong with this object.
    /// </summary>
    /// <value></value>
    /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
    public override string Error
    {
        get
        {
            return this["UserCode"] + this["UserName"] + this["Password"] + this["ConfirmedPassword"] + this["EmailAddress"];
        }
    }

    /// <summary>
    /// Gets the <see cref="System.String"/> with the specified column name.
    /// </summary>
    /// <value></value>
    public override string this[string columnName]
    {
        get
        {
            switch (columnName)
            {
                case "UserCode":
                    if (!string.IsNullOrEmpty(UserCode) && UserCode.Length > 20)
                        return "User Code must be less than or equal to 20 characters";
                    break;

                case "UserName":
                    if (!string.IsNullOrEmpty(UserCode) && UserCode.Length > 60)
                        return "User Name must be less than or equal to 60 characters";
                    break;

                case "Password":
                    if (!string.IsNullOrEmpty(Password) && Password.Length > 60)
                        return "Password must be less than or equal to 60 characters";
                    break;

                case "ConfirmedPassword":
                    if (Password != ConfirmedPassword)
                        return Properties.Resources.ErrorMessage_Password_ConfirmedPasswordDoesntMatch; 
                    break;

                case "EmailAddress":
                    if (!string.IsNullOrEmpty(EmailAddress))
                    {
                        var r = new Regex(_emailRegex);
                        if (!r.IsMatch(EmailAddress))
                            return Properties.Resources.ErrorMessage_Email_InvalidEmailFormat;
                    }
                    break;
            }
            return string.Empty;
        }
    }

    #endregion

and here is the XAML markup for two of the textboxes on the page (note particularly the ValidatesOnDataErrors and ValidatesOnExceptions properties in the Text binding):

<TextBox Name="UserCodeTextBox" 
         Text="{Binding UserCode, 
                Mode=TwoWay, 
                UpdateSourceTrigger=PropertyChanged, 
                ValidatesOnDataErrors=True, 
                ValidatesOnExceptions=True, 
                NotifyOnSourceUpdated=True, 
                NotifyOnTargetUpdated=True}" 
         GotFocus="Input_GotFocus"
         VerticalAlignment="Top"
         Margin="165,0,150,0"  
         CharacterCasing="Upper"
         />

<TextBox Name="UserNameTextBox" 
         Text="{Binding UserName, 
                Mode=TwoWay, 
                UpdateSourceTrigger=PropertyChanged, 
                ValidatesOnDataErrors=True, 
                ValidatesOnExceptions=True, 
                NotifyOnSourceUpdated=True, 
                NotifyOnTargetUpdated=True}" 
         GotFocus="Input_GotFocus"
         VerticalAlignment="Top"
         Margin="165,30,0,0"  
         />



回答2:


Is there a common pattern to solve that issue? we don't want to introduce a dependency in the GUI between the two textboxes, because this logic should only be present in the business logic layer.

  1. Value1 and Value2 are interdependent due to the condition "Value1 and Value2 in the model must not be the same".

  2. This means that when Value2 changes, Value1 also changes, and vice verse! Actually, when Value2 changes, Value1 validation result changes, but this is close to the former statement.

  3. Value1 and Value2 setters must notify about both Value1 and Value2 property change.

  4. View must reread and revalidate both values and clear faulty mark.

  5. Not sure if WPF will do so if it finds that the notification event has been raised but the value actually hasn't changed.



来源:https://stackoverflow.com/questions/4152346/mvvm-validation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!