Auto validated ViewModel feature

Developer
Nov 17, 2011 at 2:12 PM

I'm notice the inclusion on the current release (2.3) the follow feature:

(+) Added full support for data annotations. For example, if a view model property is decorated
with the RequiredAttribute, the error is gathered and even provided to the ValidateFields and
finally exposed via the IDataErrorInfo interface (all validation eventually ends up in one place,
the IDataErrorInfo)

Recently, I need to introduce, more complex validation rules, so, I intercept (and seal) the catel validation step as follow, on my own view model base, realizing the validation work using FluentValidation validators.

    public class ViewModelBasePlusValidation : Catel.MVVM.ViewModelBase
    {
        #region Constants and Fields

        private List<ValidatorValidationResultTypeBucket> buckets;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the type information about validators, validation results properties and it's validation result types.
        /// </summary>
        private IEnumerable<ValidatorValidationResultTypeBucket> Buckets
        {
            get
            {
                if (this.buckets == null)
                {
                    this.buckets = new List<ValidatorValidationResultTypeBucket>();
                    List<PropertyInfo> validationResultProperties = this.GetType().GetPropertiesInfoWithAttribute<ValidationResultFromAttribute>().ToList();
                    List<PropertyInfo> validatorProperties = this.GetType().GetPropertiesInfoWithAttribute<ValidatorAttribute>().ToList();
                    foreach (PropertyInfo validatorProperty in validatorProperties)
                    {
                        object value = validatorProperty.GetValue(this, null);
                        if (value is IValidator)
                        {
                            IEnumerable<ValidatorAttribute> validatorAttributes = validatorProperty.GetCustomAttributes<ValidatorAttribute>(true);
                            ValidatorAttribute validatorAttribute = validatorAttributes.FirstOrDefault();
                            if (validatorAttribute != null)
                            {
                                ValidationResultType validationResultType = validatorAttribute.ValidationResultType;
                                PropertyInfo validationResultProperty =
                                    validationResultProperties.FirstOrDefault(
                                        info =>
                                            {
                                                ValidationResultFromAttribute validationResultFromAttribute = info.GetCustomAttributes<ValidationResultFromAttribute>(true).FirstOrDefault();
                                                return validationResultFromAttribute != null && validationResultFromAttribute.ValidatorName == validatorProperty.Name;
                                            });

                                this.buckets.Add(new ValidatorValidationResultTypeBucket(value as IValidator, validationResultType, validationResultProperty));
                            }
                        }
                    }
                }

                return this.buckets.AsReadOnly();
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Realize the auto validation work
        /// </summary>
        protected override sealed void OnValidatingFields()
        {
            foreach (ValidatorValidationResultTypeBucket bucket in this.Buckets)
            {
                ValidationResult validationResult = bucket.Validator.Validate(this);
                if (!validationResult.IsValid)
                {
                    foreach (ValidationFailure validationFailure in validationResult.Errors)
                    {
                        this.SetFieldValidationResult(new FieldValidationResult(validationFailure.PropertyName, bucket.ResultType, validationFailure.ErrorMessage));
                    }
                }

                if (bucket.ValidationResultProperty != null)
                {
                    bucket.ValidationResultProperty.SetValue(this, validationResult, null);
                }
            }
        }

        #endregion

        /// <summary>
        /// This class hold a validator, it's validation result type, and the property where the validation result will be set. 
        /// </summary>
        private class ValidatorValidationResultTypeBucket
        {
           // Implementation removed for simplification 
        }
    }
}

I introduce a couple of attributes ValidatorAttribute and ValidationResultFromAttribute as follow, that allow me recovery the validation properties involved on the validation step. Also note a couple of generics extension methods in order to get attributes and properties.

The implementations of ViewModelBasePlusValidation looks as follow. Note how I use the ValidationResultType in order to reports Errors or Warnings.

public sealed class PersonViewModel : ViewModelBasePlusValidation
{
        /// <summary>
        /// ViewModelBasePlusValidation puts the result of NoteValidator in this property
        /// </summary>
        [ValidationResultFrom("PersonValidator")]
        public ValidationResult PersonValidationResult { get; set; }

        /// <summary>
        /// ViewModelBasePlusValidation execute this validator and reports this result as ValidationResultType.Error, 
        /// </summary>
        [Validator]
        public PersonValidator PersonValidator { get; private set; }
        /// <summary>
        /// ViewModelBasePlusValidation execute this validator and reports this results as ValidationResultType.Warning 
        /// </summary>
        [Validator(ValidationResultType.Warning)]
        public PersonValidatorWarning PersonValidatorWarning { get; private set; }
}

It's possible includes this feature, code name "Auto validated ViewModel", or similar, juggle Catel with an existing validation framework (eg: FluentValidation) on forthcoming Catel release?

Coordinator
Nov 17, 2011 at 2:19 PM

Excellent idea, we are actually looking into such a way, and I think you provided a very neat implementation. The issue is how to make it as generic as possible without too much performance loss. This is a very FluentValidation specific implementation, but we need to implement some kind of interface for this.

If you have ideas, just let us know!

Nov 18, 2011 at 1:10 PM

Hi,

We are using FluentValidation in our services, and I will soon start looking into also using it in the UI, so I am also interested in this feature.

Geert.

Coordinator
Nov 25, 2011 at 8:49 AM

Would an interface with the following definitions be enough for you:

 

public interface IExternalValidator<T>
{
    void ValidateFields(T instance, List<FieldValidationResult> validationResults);

    void ValidateBusinessRules(T instance, List<BusinessRuleValidationResult> validationResults);
}

public interface IExternalValidator : IExternalValidator<object>
{
} 

Or do you need more to implement custom external validation?

Developer
Nov 25, 2011 at 12:03 PM
Edited Nov 25, 2011 at 12:16 PM

Great implementation of Alex, but like Geert i think that it's better to make a very Generic implementation.

Geert, can we have a IValidationService<T> like service (in the same like IPleaseWaitService or IProcessService) which will be inject through Container like :

var validator = GetService<IValidationService<PersonViewModel>>();

validator. ValidateFields(person.FieldsValResults);

validator. ValidateBusinessRules(person.BusinessRuleValResults);

 where person is an instance of   PersonModel

using this contrats:

public interface IValidationService<T>
{
  void ValidateFields(List<FieldValidationResult> validationResults);

  void ValidateBusinessRules(List<BusinessRuleValidationResult> validationResults);
}

 

public class PersonModel : DataObjectBase<PersonModel>

{

     Public   List<FieldValidationResult>  FieldsValResults

     {
              get { return GetValue<List<FieldValidationResult>>(FieldsValResultsProperty); }
              set { SetValue(FieldsValResultsProperty, value); }
      }

      public static readonly PropertyData FieldsValResultsProperty = RegisterProperty("FieldsValResults", typeof( List<FieldValidationResult>));

 

     Public   List<BusinessRuleValidationResult>  BusinessRuleValResults

     {
              get { return GetValue<List<BusinessRuleValidationResult>>(BusinessRuleValResultsProperty); }
              set { SetValue(BusinessRuleValResultsProperty, value); }
      }

      public static readonly PropertyData  BusinessRuleValResultsProperty = RegisterProperty("BusinessRuleValResults", typeof( List<BusinessRuleValidationResult>));


 }

 

and have in a namespace, implementation of  BusinessRuleValidationResult and FieldValidationResult class.

Developer
Nov 25, 2011 at 12:06 PM
Edited Nov 25, 2011 at 12:10 PM

My apologies, this :

public interface IValidationService<T>

{
      void ValidateFields(T instance, List<FieldValidationResult> validationResults);

      void ValidateBusinessRules(T instance, List<BusinessRuleValidationResult> validationResults);
}

Developer
Nov 25, 2011 at 1:13 PM

Looks good, but I think the validation result must be complex type instead a List, and implements an interface like this one.

interface IValidationResult
{
    bool IsValid { get; )
}

 

Note how I report the validation result to a property. It allow me use this result inside an implementation of any can execute method of commands.

I'm not sure right now, but I think that I can associate automatically a validation result with a can execute method of a command.

I'm really thinking on the service approach, but just like this.

interface IValidationService<T>
{
    IValidationResult Validate(T instance);
}


The construction of the validation service must take in account that a view model instance can have multiples validator. It can resolve using a composite pattern implementation.

In order to report the results as business rule or field validation results, other parameter to the Validator attribute can added, specifying the if will be reported as field error or business rule, with similar use of the parameter ValidationResultType.

What did you think?



Developer
Nov 25, 2011 at 1:41 PM
Edited Nov 25, 2011 at 8:19 PM

Following with the Rajivhost approach, we can get another advantage, when storage the validation result into a Catel "dependency like property".

Catel resolve the propagation of errors in a "Nested controls" scenario, it could resolve the error propagation in a "Composite View" (non-nested, looks like a mosaic) scenario too, using the InterestedIn attribute.

Forgive me, if Catel now can handle  the "Composite View" problem.  I'm still discovering Catel, so I didn't known all Catel features yet.

Coordinator
Nov 26, 2011 at 1:56 PM

Thanks for all the valuable input. I don't think it is a good idea to create a service because there will be lots and lots of validation services registered in the IoC container. I think there should be just a single IValidationServiceProvider that provides external validators. Then, Catel can call it for view models (and thus also the DataObjectBase) like this:

public interface IValidationServiceProvider
{
    IValidationService GetValidationService<T>();

IValidationService GetValidationService(Type targetType); }

This service will return null if no validator is available or the right validator for the specified object (for example, a view model). Then, you can still choose whether you want one single validator that  validates all, or a single validator per target type.

I will add something like this to the constructor of the ViewModelBase (actually the DataObjectBase, but that is the same class).

if (ServiceLocator.IsTypeRegistered<IValidationServiceProvider>())
{
    var validationServiceProvider = GetService<IValidationServiceProvider>();
    var validationService = validationServiceProvider.GetValidationService(GetType());
    if (validationService != null)
    {
        // now we have a very dynamic validation service where you can what you want
    }
} 

Then, I still think there needs to be a simple IValidationService implementation which will only receive the instance and a list where it can add new validations to (like my original post).

@alex: each view model has its own errors and warnings. You can gather these around using the WarningAndErrorValidator. Then, you can subscribe to each event (just like the InfoBarMessageControl does, it shows the warnings and errors of all nested user controls in one single place). 

What do you guys think?

Developer
Nov 28, 2011 at 1:53 PM
Edited Nov 28, 2011 at 7:03 PM

Great work, but I still need a instance of validation result that compile the result of validation process of an specific instance validation service and query it at any time. I know that result of validation process can query via IDataErrorInfo interface implementation, but if a view model have more than one validation service, how know which validation service fail. If an error is raised from validation service could provoke that a command could not execute, while other commands still work.

Doesn't work for you the idea of use the validation result as part of the implementation of a can execute method?

@GeertvanHorrik:  In a mosaic scenario I don't know when a specific view plus its view-model is loaded or unloaded. I have no a well know code point to execute the subscription or unsubscription work. I can delegate this work into the  mosaic it self or any object able to inspect the viewmodel instance during the load/unload process, but InterestedIn arrtibute give me the safety and dynamism that’s I need without typing unnecesary code.

Coordinator
Nov 28, 2011 at 7:55 PM

I was working on the IValidator interface, and came up with the following methods:

  • BeforeValidateFields
  • ValidateFields
  • AfterValidateFields
  • BeforeValidateBusinessRules
  • ValidateBusinessRules
  • AfterValidateBusinessRules

The before is called before any validation is done on the object. The after is called when all validation (attributes, custom validation in view model, other IValidator instances) are done. You will get a list of all field and business rule errors and warnings in that method. Maybe it's a good idea to add BeforeValidate and AfterValidate which are called before everything again.

I think the results should be used like this in the CanExecute:

private bool OnOkCommandCanExecute()
{
    return this.HasErrors;
}

This works because all errors caused by an external IValidator implementation are added to the object as errors like they were caused by the object itself.

Does this cover all scenarios you have in mind?

Developer
Nov 28, 2011 at 8:38 PM

Assume that I have more than one command, with your approach at end of the validation process I have a single access to all errors in the view model.

All commands have no the same rule to allow it's execution. 


private bool OkCommandCanExecute()
{
    return this.HasErrors; // may be right or not?
}

private bool AddCommandCanExecute()
{
    return this.HasErrors; // may be right or not?
}

 

 The view model has error but it can allow me execute some commands.

private bool OkCommandCanExecute()
{
    return this.OkCommandValidationResult.IsValid; // I'm sure about the execution condition 
}

private bool AddCommandCanExecute()
{
    return this.AddCommandValidationResult.IsValid; // I'm sure about the execution condition 
}

 It's possible reach the same result spliting the current view model in small ones, but the solution will grow in complexity unnecessarily.

 

Coordinator
Nov 29, 2011 at 8:29 AM

I understand your issue, I have to think about this on how to solve it.

Developer
Nov 29, 2011 at 7:30 PM

We has been talking about the solution. The main ideas around this problem was really written in this thread. Just group the validation results.

Allow me introduce a final idea, about this topic. What about if you add a new property to the FieldValidationResult / BusinessRuleValidationResult classes named GroupName. Non breaking changes are introduced.

The group name can be use also to the auto-report feature [ValidationResultOf("GroupName")].

Note: ValidationResultOf originally named ValidationResultFrom.

public class FieldValidationResult
{
    public FieldValidationResult(...., string groupName = string.Empty)
    {
        this.GroupName = groupName;
    }

    public string GroupName { get; private set;}
}

// It can be use like this.
public class Person : ViewModelBase 
{
        protected override void ValidateFields(List<FieldValidationResult> validationResults)
        {
            if(string.IsNullOrEmpty(this.Name))
            {
                validationResults.Add(new FieldValidationResult("Name", ValidationResultType.Error, "This field is requiered", "Main"));   
            }
        }

       [ValidationResultOf("Main")]
       List<FieldValidationResult> PersonValidationResult { ... }
}

// External validators must translate its results to this simplified Catel Style.

Coordinator
Nov 29, 2011 at 7:35 PM

I understand your point. I almost implemented the external validators (they are needed anyway). Next is to write a IValidationDescriptor. It would be nice if that would contain the data you are interested in, because it doesn't feel right to implement the additional validation info into the class itself.

So, for example you can do something like this to create a validation description about the current object:

var validationDescriptor = new ValidationDescription(this);

Then, you can get all errors and maybe even add mappings. This way, you get the best of both worlds.

I can indeed add a tag property to the IFieldValidationResult, but default null, but you are able to group.

Coordinator
Nov 30, 2011 at 4:47 PM

The first working version of IValidator and IValidationProvider is checked in. The next issue I am going to solve is this one:

http://catel.codeplex.com/workitem/7042

It should do exactly what you need. You can query what properties have errors. I hope that is what you need. If not, please add comments to the issue so we can create just what you (and others) need.

Coordinator
Dec 2, 2011 at 10:02 AM

I got good news: I have implemented the ValidationContext and it seems to do exactly what you need. It provides an extensive way to query all errors and warnings (including tags filtering, etc). You can get the latest beta here:

http://dl.dropbox.com/u/8455721/Catel%20beta.zip

If this works for you, I will release this version. In the meantime I will write the documentation with some examples on how to use the validation context, but here is the idea:

var viewModel = new MyViewModel();

var validationContext = viewModel.ValidationContext;

var fieldErrorsWithTag = validationContext.GetFieldErrors((object)"myTag");

 

You want all validation (wwarnings and errors) of a specific property?

var fieldValidationResultsForProperty = validationContext.GetFieldValidationResults("MyProperty");

 

You want all business rule warnings?

var businessRuleWarnings = validationContext.GetBusinessRuleWarnings();

 

The cool thing about this improvement is:

1) The DataObjectBase becomes more simple (validation container code is now implemented in ValidationContext)

2) You can now specify multiple field errors per property

Dec 3, 2011 at 11:43 AM

So, Catel now offers 3 ways of setting validation results:

- by overriding ValidateFields and ValidateBusinessRules
- by using annotations on Model or ViewModel properties
- by implementing an IValidor (or deriving from ValidatorBase) 

Is the result of all of these mechanisms exposed by the ViewModelBase in through the standard interfaces (INotifyDataErrorInfo an such),
so no matter what mechanism is used, the visual result is the same (validation results are displayed in the UI)?

And... can they be combined in one ViewModel (e.g. using [Required] attribute for required fields, using an IValidator for more elaborate field validations
and overriding ValidateBusinessRules to validate the business rules)?

Coordinator
Dec 3, 2011 at 2:15 PM

You are exactly right. Catel gathers all different kinds of validations into the same validation context and exposes them through all interfaces (IDataErrorInfo, IDataWarningInfo (Catel only interface), INotifyDataErrorInfo, INotifyDataWarningInfo (Catel only interface)). Thus, simple said, no matter what kind of validation you use, it is gathered into the ValidationContext. The cool thing is that you can also hook into the validation process after all validation is done by all validation methods using the Validated event or the IValidator.OnValidated method.

You can also combine, thus say you have a model that includes all required fields via attributes, but you also want to add a warning in case the MiddleName property is empty, you can add additional validation in the view model. Even cooler, you can *remove* or modify validation results from a model (wouldn't use that much, but it shows the extreme power of the validation mechanism in Catel).

Developer
Dec 7, 2011 at 1:59 PM

Sorry, I was out for a week. I will try to use this feature on my code and notify the results as soon as possible.

Dec 7, 2011 at 2:05 PM

Alex, note that Geert has released version 2.4, which includes this feature...

Developer
Dec 7, 2011 at 2:11 PM
I noticed, I will review the feature using the current release.
Thanks
Developer
Dec 8, 2011 at 1:45 PM
Edited Dec 8, 2011 at 2:20 PM

Outstanding.

I just add an extension method to the validation context in order to reach my goal.

public static class ValidationContextExtension
{
     
        public static IValidationSummary GetValidationSummary(this ValidationContext context, string tag)
        {
            List<IFieldValidationResult> fieldErrors = context.GetFieldErrors((object)tag);
            List<IFieldValidationResult> fieldWarnings = context.GetFieldWarnings((object)tag);
         
            List<IBusinessRuleValidationResult> businessRuleErrors = context.GetBusinessRuleErrors(tag);
            List<IBusinessRuleValidationResult> businessRuleWarnings = context.GetBusinessRuleWarnings(tag);
         
            return new ValidationSummary(fieldErrors, fieldWarnings, businessRuleErrors, businessRuleWarnings);
        }

       // Partial implementation of IValidationSummary interface, the other properties implementation was removed for simplification
       private class ValidationSummary : IValidationSummary
       {
           bool IValidationSummary.HasErrors
            {
                get
                {
                    return this.FieldErrors.Count > 0 || this.BusinessRuleErrors.Count > 0;
                }
            }        
         // {...}
}
}

// Partial definition of IValidationSummary interface, the other properties was removed for simplification

public
interface IValidationSummary
{
ReadOnlyCollaction<IFieldValidatioResult> FieldWarnings { get; }

ReadOnlyCollaction<IFieldValidatioResult> FieldErrors { get; }

bool
HasErrors { get; }

bool
HasWarnings { get; }

// {...}
 }

Catel use the term validation result for a single field o business rule, so I introduce the validation summary in order to avoid names collisions or term confusion.

My view models implementation go back to inherits from ViewModelBase (ViewModelBasePlusValidation was removed), thanks to move the validation work to another place (using external validator feature), and look like this.

public class Person : ViewModelBase
{
       [ValidationSummaryOf("Tag")]
       IValidationSummary PersonValidationSummary { ... }

       public Command CommitCommand { get; set; }
       
       private bool CanExecuteCommitCommand()
       {
            return PersonValidationSummary != null && !PersonValidationSummary.HasErrors;
       }
}
Coordinator
Dec 8, 2011 at 2:00 PM

Great to hear that the feature is implemented as you need it and thanks for the additional information, it can be very useful to others! If you have any other feature requests (smaller ones are allowed as well ;)), don't hesitate to let us know!

Developer
Dec 8, 2011 at 2:32 PM

I just have an "ultra" small one (for a while). 

The inclusion of validation summary concept as part of Catel.

Think about it ;).

Coordinator
Dec 8, 2011 at 2:32 PM

Can you submit the code { ... } as well? Then I will consider it ;)

Developer
Dec 8, 2011 at 2:57 PM

   I was really removed for simplification, it's no nuclear physics ;).

   I'm really interesting on the result grouped by the tag value, so I just implement the method that retrieve the summary from the tag.

    /// <summary>
    /// The validation summary interface.
    /// </summary>
    public interface IValidationSummary
    {
        #region Public Properties

        /// <summary>
        /// Gets BusinessRuleErrors.
        /// </summary>
        ReadOnlyCollection<IBusinessRuleValidationResult> BusinessRuleErrors { get; }

        /// <summary>
        /// Gets BusinessWarnings.
        /// </summary>
        ReadOnlyCollection<IBusinessRuleValidationResult> BusinessWarnings { get; }

        /// <summary>
        /// Gets FieldErrors.
        /// </summary>
        ReadOnlyCollection<IFieldValidationResult> FieldErrors { get; }

        /// <summary>
        /// Gets FieldWarnings.
        /// </summary>
        ReadOnlyCollection<IFieldValidationResult> FieldWarnings { get; }

        /// <summary>
        /// Gets a value indicating whether HasBusinessRuleErrors.
        /// </summary>
        bool HasBusinessRuleErrors { get; }

        /// <summary>
        /// Gets a value indicating whether HasBusinessRuleWarnings.
        /// </summary>
        bool HasBusinessRuleWarnings { get; }

        /// <summary>
        /// Gets a value indicating whether HasErrors.
        /// </summary>
        bool HasErrors { get; }

        /// <summary>
        /// Gets a value indicating whether HasFieldErrors.
        /// </summary>
        bool HasFieldErrors { get; }

        /// <summary>
        /// Gets a value indicating whether HasFieldWarnings.
        /// </summary>
        bool HasFieldWarnings { get; }

        /// <summary>
        /// Gets a value indicating whether HasWarnings.
        /// </summary>
        bool HasWarnings { get; }

        #endregion
    }


    /// <summary>
    /// The validation context extension.
    /// </summary>
    public static class ValidationContextExtension
    {
        #region Public Methods

        /// <summary>
        /// Gets a validation summary.
        /// </summary>
        /// <param name="context">
        /// The validation context.
        /// </param>
        /// <param name="tag">
        /// The tag.
        /// </param>
        /// <returns>
        /// An instance of <see cref="IValidationSummary"/>
        /// </returns>
        public static IValidationSummary GetValidationSummary(this ValidationContext context, string tag)
        {
            List<IFieldValidationResult> fieldErrors = context.GetFieldErrors((object)tag);
            List<IFieldValidationResult> fieldWarnings = context.GetFieldWarnings((object)tag);
            List<IBusinessRuleValidationResult> businessRuleErrors = context.GetBusinessRuleErrors(tag);
            List<IBusinessRuleValidationResult> businessRuleWarnings = context.GetBusinessRuleWarnings(tag);
            return new ValidationSummary(fieldErrors, fieldWarnings, businessRuleErrors, businessRuleWarnings);
        }

        #endregion

        /// <summary>
        /// The validation summary.
        /// </summary>
        private class ValidationSummary : IValidationSummary
        {
            #region Constants and Fields

            /// <summary>
            /// The business rule errors.
            /// </summary>
            private readonly List<IBusinessRuleValidationResult> businessRuleErrors;

            /// <summary>
            /// The business rule warnings.
            /// </summary>
            private readonly List<IBusinessRuleValidationResult> businessRuleWarnings;

            /// <summary>
            /// The field errors.
            /// </summary>
            private readonly List<IFieldValidationResult> fieldErrors;

            /// <summary>
            /// The field warnings.
            /// </summary>
            private readonly List<IFieldValidationResult> fieldWarnings;

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="ValidationSummary"/> class.
            /// </summary>
            /// <param name="fieldErrors">
            /// The field errors.
            /// </param>
            /// <param name="fieldWarnings">
            /// The field warnings.
            /// </param>
            /// <param name="businessRuleErrors">
            /// The business rule errors.
            /// </param>
            /// <param name="businessRuleWarnings">
            /// The business rule warnings.
            /// </param>
            public ValidationSummary(
                List<IFieldValidationResult> fieldErrors,
                List<IFieldValidationResult> fieldWarnings,
                List<IBusinessRuleValidationResult> businessRuleErrors,
                List<IBusinessRuleValidationResult> businessRuleWarnings)
            {
                this.fieldErrors = fieldErrors;
                this.fieldWarnings = fieldWarnings;
                this.businessRuleErrors = businessRuleErrors;
                this.businessRuleWarnings = businessRuleWarnings;
            }

            #endregion

            #region Explicit Interface Properties

            /// <summary>
            /// Gets BusinessRuleErrors.
            /// </summary>
            ReadOnlyCollection<IBusinessRuleValidationResult> IValidationSummary.BusinessRuleErrors
            {
                get
                {
                    return this.businessRuleErrors.AsReadOnly();
                }
            }

            /// <summary>
            /// Gets BusinessWarnings.
            /// </summary>
            ReadOnlyCollection<IBusinessRuleValidationResult> IValidationSummary.BusinessWarnings
            {
                get
                {
                    return this.businessRuleErrors.AsReadOnly();
                }
            }

            /// <summary>
            /// Gets FieldErrors.
            /// </summary>
            ReadOnlyCollection<IFieldValidationResult> IValidationSummary.FieldErrors
            {
                get
                {
                    return this.fieldErrors.AsReadOnly();
                }
            }

            /// <summary>
            /// Gets FieldWarnings.
            /// </summary>
            ReadOnlyCollection<IFieldValidationResult> IValidationSummary.FieldWarnings
            {
                get
                {
                    return this.fieldWarnings.AsReadOnly();
                }
            }

            /// <summary>
            /// Gets a value indicating whether HasBusinessRuleErrors.
            /// </summary>
            bool IValidationSummary.HasBusinessRuleErrors
            {
                get
                {
                    return this.businessRuleErrors.Count > 0;
                }
            }

            /// <summary>
            /// Gets a value indicating whether HasBusinessRuleWarnings.
            /// </summary>
            bool IValidationSummary.HasBusinessRuleWarnings
            {
                get
                {
                    return this.businessRuleWarnings.Count > 0;
                }
            }

            /// <summary>
            /// Gets a value indicating whether IsValid.
            /// </summary>
            bool IValidationSummary.HasErrors
            {
                get
                {
                    return (this as IValidationSummary).HasFieldErrors || (this as IValidationSummary).HasBusinessRuleErrors;
                }
            }

            /// <summary>
            /// Gets a value indicating whether HasFieldErrors.
            /// </summary>
            bool IValidationSummary.HasFieldErrors
            {
                get
                {
                    return this.fieldErrors.Count > 0;
                }
            }

            /// <summary>
            /// Gets a value indicating whether HasFieldWarnings.
            /// </summary>
            bool IValidationSummary.HasFieldWarnings
            {
                get
                {
                    return this.fieldWarnings.Count > 0;
                }
            }

            /// <summary>
            /// Gets a value indicating whether HasWarnings.
            /// </summary>
            bool IValidationSummary.HasWarnings
            {
                get
                {
                    return (this as IValidationSummary).HasFieldWarnings || (this as IValidationSummary).HasBusinessRuleWarnings;
                }
            }

            #endregion
        }
    }

Coordinator
Dec 8, 2011 at 5:25 PM

Implemented! I am currently regenerating the validation (takes more than half an hour, so it comes a bit later), but the beta files are updated:

http://dl.dropbox.com/u/8455721/Catel%20beta.zip

Developer
Dec 9, 2011 at 7:32 PM

I'm working on my project with FluentValidation and  I was lead to write a composite validator, due by one view model could have associated more than one validator, at least from my perspective of the problem.

Notice that I have to map the Catel concepts with the FluentValidation ones (warnings, business rules, field errors).

public sealed class CompositeValidator : IValidator
{
        private readonly List<IValidator> validators;

        public void Add(IValidator validator)
        {
            Argument.IsNotNull("validator", validator);
            this.validators.Add(validator);
        }

       // The others method implementations was removed for simplification
       public void ValidateFields(object instance, List<IFieldValidationResult> validationResults)
        {
            foreach (IValidator validator in this.validators)
            {
                validator.ValidateFields(instance, validationResults);
            }
        }
}


In order to inject the summary at the end of the validation process I also introduce The validation summary of attribute.

 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
 public class ValidationSummaryOfAttribute : Attribute
 {
      public ValidationSummaryOfAttribute(string tag)
      {
          this.Tag = tag;
      }

public string Tag { get; private set; } }

The next code can be introduced at the end of the validation process, it works for me, may be works for Catel as well.
     
 public override void AfterValidation(
            DataObjectBase instance,
            List<IFieldValidationResult> fieldValidationResults,
            List<IBusinessRuleValidationResult> businessRuleValidationResults)
        {
            IValidationContext validationContext = instance.ValidationContext;
            if (this.validationSummaryPropertyInfo != null)
            {
                this.validationSummaryPropertyInfo.SetValue(
                    instance, validationContext.GetValidationSummary(this.validatorDescriptionAttribute.Tag), null);
            }
        }
Finally just like a FluentValidation provider example, I implement one that look for class of FluentValidation with the targetType as generic paramenter on the targetType assembly.
        public override IValidator GetValidator(Type targetType)
        {
            // NOTE: This code can be improved.
            IValidator validator;
            if (this.cache.ContainsKey(targetType))
            {
                validator = this.cache[targetType];
            }
            else
            {
                // PATCH: Validator of a viewmodel must be in the same assembly of the view model, for a while (perfomance issue).
                Assembly assembly = targetType.Assembly;
                Type[] exportedTypes = assembly.GetExportedTypes();
                var validatorTypes = new List<Type>();
                foreach (Type exportedType in exportedTypes)
                {
                    if (typeof(FluentValidation.IValidator).IsAssignableFrom(exportedType))
                    {
                        Type currentType = exportedType;
                        bool found = false;
                        while (!found && currentType != typeof(object))
                        {
                            if (currentType != null)
                            {
                                found = currentType.IsGenericType
                                        && currentType.GetGenericArguments().ToList().Contains(targetType);
                                if (!found)
                                {
                                    currentType = currentType.BaseType;
                                }
                            }
                        }

                        if (found)
                        {
                            validatorTypes.Add(exportedType);
                        }
                    }
                }

                validator = FluentValidatorToCatelValidatorAdapter.From(validatorTypes, targetType);
            }

            return validator;
        }
Coordinator
Dec 10, 2011 at 3:20 PM

Very nice implementation, thanks for sharing!

Developer
Dec 12, 2011 at 2:20 PM

It's hard convince you for the inclusion of a feature into the toolkit. ( was a joke ;) ).

What about if the ValidationSummaryOfAttribute was renamed to ValidationToViewModelAttribute? It would be at the same level of abstraction of ViewToViewModel and ViewModelToModel attributes, but from the point of view of the validation process.

[ValidationToViewModel("PersonValidationTag")]
public IValidationSummary PersonValidationSummary { get { ... } set { ... } }

It's allow you the "auto-injection"  of the validation summary at the end of the validation process.

Think about it.

Coordinator
Dec 12, 2011 at 5:51 PM

This way, I can never "finish" catel ;)

I created a work item:

http://catel.codeplex.com/workitem/7047

Developer
Dec 13, 2011 at 2:09 PM

Great

I'm waiting for 2.5 release. 

For me, the feature will be completed as I dreamed it ;).

Thanks.

---------------------------------------------------------

I drop here my default initialization of the commands.  It could be useful or not. It can be enriched with complex overloads that tells about NotAllowWarnigns, AllowBusinessErrors, etc.

Here is the simplest implementation.

public static class CommandHelper
{
    public static Command CreateCommand(Action execute, Expression<Func<IValidationSummary>> validationSummaryPropertyExpression)
   {
        Func<IValidationSummary> property = propertyExpression.Compile();
        var command = new Command(execute, () => {
             IValidationSummary validationSummary = property.Invoke();
             return validationSummary != null && !validationSummary.HasErrors;
       });

      return command;
   }
}
this.AddPersonCommand = CommandHelper.Create(this.ExecuteAddPersonCommand, () => this.CanExecuteAddPersonCommandValiationSummary);
Coordinator
Dec 15, 2011 at 7:11 PM

It's implemented. As always, you can get the latest beta here:

http://dl.dropbox.com/u/8455721/Catel%20beta.zip

Developer
Dec 19, 2011 at 2:19 PM

Doesn't bring yourself to introduce the "can execute command auto initialization" feature?

I'm really using it and works great for me. Validations and CanExecute should live together in perfect harmony. ( Just like Ebony and Ivory on the piano :) )

The implementation above may be not the prettiest one, but depicts the idea.

There are a constructor of the Command class that have a tag parametter. What do you use it for?

 

Coordinator
Dec 19, 2011 at 6:32 PM

I will try to implement the method, but you keep adding more ideas and I can't keep up with them :)

The tag is used if you want to customize the button. This can be useful for Auditing or Authentication.

Coordinator
Dec 20, 2011 at 5:14 PM

Ok, I have added file containers for both CommandHelpers as CommandExtensions. For now, I have implemented the CreateCommand helper methods to allow additional validation. If you have more ideas, let them come :)

Coordinator
Dec 20, 2011 at 5:44 PM

See http://blog.catenalogic.com/post/2011/12/20/Catel-Hooking-a-command-to-validation-automatically-in-MVVM.aspx

Developer
Dec 21, 2011 at 3:03 PM

The method  "Command<T> CreateCommand<T>(...)" for parametrized command is missing on CommandHelper.

Coordinator
Dec 21, 2011 at 5:27 PM

The Command<TExecuteParameter> is also added.