Provisioning for Asynchronous or Synchronous Validation

,

The validation infrastructure contained in the ViewModelBase and the DataErrorNotifier classes caters for both asynchronous and synchronous validation models. When using this system, validation code should reside in one of two overridable methods: BeginValidation or GetPropertyErrors.

BeginValidation uses an asynchronous event driven validation model, and GetPropertyErrors provides for a simpler synchronous model for when asynchronous processing is not required.

DataValidationError Class

When using either the synchronous or asynchronous validation models, validation errors are represented using a custom class named DataValidationError. DataValidationError has an ErrorMessage property of type string, and an Id property of type int. The purpose of the Id property is that it allows you to use the same error message for multiple fields.

Provisioning for Synchronous Validation

The ViewModelBase.GetPropertyErrors method allows your viewmodel to perform input validation using a simple synchronous approach. The GetPropertyErrors method is overridden to validate specific properties in your viewmodel. The method returns all errors for the specified property as an IEnumerable<DataValidationError>.

In the following example, the GetPropertyErrors method has been overridden in a viewmodel containing two properties, neither of which is allowed to be null or to consist of entirely whitespace:

protected override IEnumerable<DataValidationError> GetPropertyErrors(
    string propertyName, object value)
{
    if (!string.IsNullOrWhiteSpace((string)value))
    {
        yield break;
    }

    switch (propertyName)
    {
        case "ValidatedString1":
            yield return new DataValidationError(1,
                "ValidatedString1 is required.");
            break;
        case "ValidatedString2":
            yield return new DataValidationError(2,
                "ValidatedString2 is required.");
            break;
    }
    yield break;
}

If an exception is raised during the execution of this method, it is handled by the ViewModelBase class. Exception handling is discussed further in the next section.

Provisioning for Asynchronous Validation

The BeginValidation method of the ViewModelBase class is the main extensibility point for providing asynchronous validation. This method should be overridden in subclasses to perform composite validation or validation of property values using, for example, WCF services.

The ViewModelBase class implementation of the BeginValidation method calls the virtual GetPropertyErrors method, which as you saw in the preceding section, retrieves the list of errors for a specified property. See the following excerpt:

public virtual void BeginValidation(string memberName, object value)
{
    IEnumerable<DataValidationError> errors;
    try
    {
        errors = GetPropertyErrors(memberName, value);
    }
    catch (Exception ex)
    {
        OnValidationComplete(
            new ValidationCompleteEventArgs(memberName, ex));
        return;
    }
    OnValidationComplete(
            new ValidationCompleteEventArgs(memberName, errors));
}

The base implementation of the BeginValidation method executes synchronously, retrieving the list of errors and raising the ValidationComplete event.


Note

When the BeginValidation method has finished validating, it is imperative that the ValidationComplete event is raised; otherwise, the DataErrorNotifier will not raise the INotifyDataErrorInfo.ErrorsChanged event, which prevents the UI from signaling that a validation error exists.


The following excerpt provides an example implementation of the BeginValidation method:

public override void BeginValidation(string memberName, object value)
{
    try
    {
        if (string.IsNullOrEmpty((string)value))
        {
            List<DataValidationError> errors
                = new List<DataValidationError>();
            DataValidationError error = null;
            switch (memberName)
            {
                case "ValidatedString1":
                    error = new DataValidationError(1,
                        "ValidatedString1 is required.");
                    break;
                case "ValidatedString2":
                    error = new DataValidationError(2,
                        "ValidatedString2 is required.");
                    break;
            }

            if (error != null)
            {
                errors.Add(error);
            }
            OnValidationComplete(
                new ValidationCompleteEventArgs(memberName, errors));
        }
        else
        {
            OnValidationComplete(
                new ValidationCompleteEventArgs(memberName));
        }
    }
    catch (Exception ex)
    {
        OnValidationComplete(
            new ValidationCompleteEventArgs(memberName, ex));
    }
}

The ViewModelBase class contains a string keyed Dictionary of errors for each property, which is of type Dictionary<string, List<DataValidationError>>.

Decoupling Validation

The DataErrorNotifier instance relies on an IValidateData object to perform the actual semantic validation of viewmodel property values. Thus, the strategy for validating the viewmodel can be decoupled from the viewmodel itself. In most cases, however, the viewmodel implements IValidateData.

Validating Properties as They Change

The DataErrorNotifier class subscribes to the viewmodel’s PropertyChanged event so that when a property is changed, it is also validated. See the following excerpt:

public DataErrorNotifier(
    INotifyPropertyChanged owner, IValidateData validator)
{
    this.validator
             = ArgumentValidator.AssertNotNull(validator, "validator");
    ArgumentValidator.AssertNotNull(owner, "owner");

    validator.ValidationComplete += validator_ValidationComplete;
    owner.PropertyChanged += HandleOwnerPropertyChanged;
}

void HandleOwnerPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e == null || e.PropertyName == null)
    {
        return;
    }
    BeginGetPropertyErrorsFromValidator(e.PropertyName);
}

When a viewmodel property changes, a potentially asynchronous validation operation is commenced from the BeginGetPropertyErrorsFromValidator method, shown in the following excerpt:

void BeginGetPropertyErrorsFromValidator(string propertyName)
{
    Func<object> propertyFunc;
    lock (propertyDictionaryLock)
    {
        if (!propertyDictionary.TryGetValue(propertyName, out propertyFunc))
        {
            /* No property registered with that name. */
            return;
        }
    }
    validator.BeginValidation(propertyName, propertyFunc());
}

The BeginGetPropertyErrorsFromValidator method performs the following tasks:

Image The property Func is retrieved from the property Dictionary.

Image The property value is retrieved by invoking the property Func.

Image The BeginValidation method of the viewmodel (implementing IValidateData) is called using the property name and the property value.

When the IValidateData finishes validating a property value, it raises the IValidateData.ValidationComplete event. This event is handled by the HandleValidationComplete method.

The first thing that the HandleValidationComplete method does is update the list of validation errors for the specified property. It does this by calling the SetPropertyErrors method, which attempts to retrieve the list of errors for that property. If the specified list of errors is different from the existing list of known errors for that property, the list of known errors is updated (or removed if empty) and the ErrorsChanged event is raised:

public void SetPropertyErrors(
    string propertyName, IEnumerable<DataValidationError> dataErrors)
{
    ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");

    List<DataValidationError> list;
    bool raiseEvent = false;
    lock (errorsLock)
    {
        bool created = false;

        int paramErrorCount = dataErrors == null ? 0 : dataErrors.Count();
        if ((errorsField == null || errorsField.Count < 1)
            && paramErrorCount < 1)
        {
            return;
        }

        if (errorsField == null)
        {
            errorsField
                = new Dictionary<string, List<DataValidationError>>();
            created = true;
        }
        bool listFound = false;
        if (created ||
            !(listFound = errorsField.TryGetValue(propertyName, out list)))
        {
            list = new List<DataValidationError>();
        }

        if (paramErrorCount < 1)
        {
            if (listFound)
            {
                errorsField.Remove(propertyName);
                raiseEvent = true;
            }
        }
        else
        {
            var tempList = new List<DataValidationError>();
            foreach (var dataError in dataErrors)
            {
                if (created || list.SingleOrDefault(
                    e => e.Id == dataError.Id) == null)
                {
                    tempList.Add(dataError);
                    raiseEvent = true;
                }
            }
            list.AddRange(tempList);
            errorsField[propertyName] = list;
        }
    }

    if (raiseEvent)
    {
        OnErrorsChanged(propertyName);
    }
}

The OnErrorsChanged method raises the ErrorsChanged event on the UI thread using the app’s Dispatcher. An extension method is used to ensure that the method is invoked only if the thread is not the UI thread; otherwise, the delegate is called from the current thread.

protected virtual void OnErrorsChanged(string property)
{
    Dispatcher dispatcher = Deployment.Current.Dispatcher;
    dispatcher.InvokeIfRequired(
        delegate
        {
            ErrorsChanged.Raise(this,
                new DataErrorsChangedEventArgs(property));
        });
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.87.83