ModelBase Validation on PropertyChanged via DataAnotations

Topics: Questions
Sep 4, 2014 at 8:27 AM
I am trying to dig in the functionality of the framework but have hard time validating models.

Here is my scenario:
I use the Telerik DataAccess ORM and changed the template to generate models inheriting EntityModelBase as in the guide in the documentation.

Created simple DataWindow, which have few textboxes bound to the model which is exposed by the ViewModel.
The binding is set like this:
<TextBox Text="{Binding User.Username, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}" />
There is [Required] attribute set on the model, which should be validated on propertychange, but catel is validating DataAnotations on propertychange only for properties based on PropertyData.

Tried to use ViewModelToModel property without any luck. Is there something that I have to setup to enable DataAnotation validations on properties on property change.

While looking in the code i found that in ModelBase.validations.cs in method
internal void Validate(bool force, bool validateDataAnnotations)
the condition :
if (force && validateDataAnnotations)
is never passed, since it is not forced to validate the model.
My question is how can i force the ModelBase to validate the DataAnotations on entities which is exposing properties without PropertyData properties on PropertyChange ?
Coordinator
Sep 4, 2014 at 10:07 AM
Make sure the properties you want to check actually support INotifyPropertyChanged on the model. For performance reasons, Catel will only check the changed properties for attributes.

You can force the validation (in 4.0) like this:

((IModelValidation)myModel).Validate(true);
Sep 4, 2014 at 10:42 AM
Edited Sep 4, 2014 at 10:45 AM
Here is my BaseEntityModel:
public abstract partial class BaseEntityModel : ModelBase, IInitializeTransients
    {
        protected BaseEntityModel()
        {
            //SuspendValidation = false;
        }

        protected BaseEntityModel( SerializationInfo info, StreamingContext context ) : base( info, context )
        {
        }

        public virtual void PostLoad()
        {
            ClearDirtyFlag();
            SuspendValidation = false;
        }

        public virtual void PreRemove( IObjectScope objectScope )
        {
        }

        public virtual void PreStore()
        {
        }

        public virtual void InitializeTransients( InitOperation initOperation )
        {
        }

        void ClearDirtyFlag()
        {
            IsDirty = false;
        }
    }
The actual generated persistent model:
[Table("sp_user")]
    [KeyGenerator(KeyGenerator.Autoinc)]
    public partial class User : BaseEntityModel
    {
        private int _id;
        [Column("id", IsBackendCalculated = true, IsPrimaryKey = true, Length = 0, Scale = 0, SqlType = "integer unsigned")]
        [Storage("_id")]
        [Required()]
        [System.ComponentModel.DataAnnotations.Key()]
        public virtual int Id
        {
            get
            {
                return this._id;
            }
            set
            {
                if(this._id != value)
                {
                    var oldValue = _id;
                    _id = value;
                    RaisePropertyChanged( () => Id, oldValue, value );
                }
            }
        }
        
        private string _username;
        [Column("username", Length = 120, Scale = 0, SqlType = "varchar")]
        [Storage("_username")]
        [Required()]
        public virtual string Username
        {
            get
            {
                return this._username;
            }
            set
            {
                if(this._username != value)
                {
                    var oldValue = _username;
                    _username = value;
                    RaisePropertyChanged( () => Username, oldValue, value );
                }
            }
        }
        
        private string _password;
        [Column("password", Length = 120, Scale = 0, SqlType = "varchar")]
        [Storage("_password")]
        [Required()]
        public virtual string Password
        {
            get
            {
                return this._password;
            }
            set
            {
                if(this._password != value)
                {
                    var oldValue = _password;
                    _password = value;
                    RaisePropertyChanged( () => Password, oldValue, value );
                }
            }
        }
        
        private IList<Document> _documents = new List<Document>();
        [Collection(InverseProperty = "CreatedByUser")]
        [Storage("_documents")]
        public virtual IList<Document> Documents
        {
            get
            {
                return this._documents;
            }
        }
        
    }
The ViewModel :
public class UserViewModel : ViewModelBase
    {
        #region Properties

        #region User property

        /// <summary>
        /// Gets or sets the User value.
        /// </summary>
        public User User
        {
            get { return GetValue< User >( UserProperty ); }
            private set { SetValue( UserProperty, value ); }
        }

        /// <summary>
        /// User property data.
        /// </summary>
        public static readonly PropertyData UserProperty = RegisterProperty( "User", typeof( User ) );

        #endregion

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="UserViewModel"/> class.
        /// </summary>
        public UserViewModel( User user )
        {
            User = user;
        }

        /// <summary>
        /// Gets the title of the view model.
        /// </summary>
        /// <value>The title.</value>
        public override string Title
        {
            get { return User.Id == 0 ? "Нов потребител" : "Редакция - " + User.Username; }
        }

        // TODO: Register models with the vmpropmodel codesnippet
        // TODO: Register view model properties with the vmprop or vmpropviewmodeltomodel codesnippets
        // TODO: Register commands with the vmcommand or vmcommandwithcanexecute codesnippets
    }
And in the window the binding is :
<TextBox Text="{Binding User.Username, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}" />
Should this raise property changed and actually validate the Required attribute ?

I can implement my custom validation, but thats not the point, since Catel already have a wonderful implementation of it, just can't understand how to use it properly.
Everywhere I edit the persistent data have to validate it in the persistent model, the ViewModel should only expose it and handle saving to the DB via commands.
Coordinator
Sep 4, 2014 at 10:45 AM
This will never validate on ViewModelToModel because you are directly binding to the model. I am not sure why it doesn't validate the non-catel properties, but is there a reason why you are not using Catel properties in the actual entity?

Maybe you can provide a repro with a model with data attribute validation on non-catel properties, then I might look into it.
Sep 4, 2014 at 11:01 AM
There is no reason, to not use catel properties in the entity. It is something that I will try in a moment.
Tried with the ViewModelToModel properties too, but the problem is the same, since they are not decorated with other attributes. If i decorate the ViewModel properties everything is ok. While debuging deeper I saw, that the property (ViewModelToModel) is actually trying to validate the model property, but default implementation of string IDataErrorInfo.this[string columnName] calls EnsureValidationIsUpToDate, where the IsValid is always true and doesn't fires the Validate method.
Even adding Validate manually like :
RaisePropertyChanged( () => Username, oldValue, value );
Validate();
doesn't validates the data annotations. Only Validate( true ); causes to handle the attributes, but i think that forces full model validation, which hurts the performance noticeably.
Sep 5, 2014 at 7:26 AM
Fixed my issue in the BaseEntityModel like this:
#region IDataErrorInfo Members

        /// <summary>
        ///     Gets the current error.
        /// </summary>
        string IDataErrorInfo.Error
        {
            get
            {
                if( HideValidationResults ) {
                    return string.Empty;
                }

                Validate( true );
                return GetBusinessRuleErrors() ?? string.Empty;
            }
        }

        /// <summary>
        ///     Gets an error for a specific column.
        /// </summary>
        /// <param name="columnName">Column name.</param>
        /// <returns>The error or <see cref="string.Empty" /> if no error is available.</returns>
        string IDataErrorInfo.this[ string columnName ]
        {
            get
            {
                if( string.IsNullOrEmpty( columnName ) ) {
                    return string.Empty;
                }

                if( HideValidationResults ) {
                    return string.Empty;
                }

                Validate( true );
                return GetFieldErrors( columnName ) ?? string.Empty;
            }
        }

        #endregion
But the ViewModel is not respecting the validation state of the Model, since I haven't used the ViewModelToModel. I think that validation should be handled by the model itself than the ViewModel.
The attribute annotations should be validated per property for performance reasons, and maybe be extracted as separate method.
Coordinator
Sep 8, 2014 at 7:26 AM
"Of course" the view model does not handle any validation if the validation is only done in the model itself (without mappings). Then it's just an object living on the VM of which the VM has no knowledge.