WPF Validations using DataAnnotation and INotifyDataErrorInfo

DataAnnotations and INotifyDataErrorInfo provides an easy and cleaner way to implement validations in WPF ViewModels. Unlike IDataErrorInfo, INotifyDataErrorInfo allows you to raise multiple errors (there are many more features which makes it more interesting than IDataErrorInfo including making it possible to use asynchronous validations).

This being a hands-on demonstration, we will keep the theoritical part to the minimum and hit the code at the earliest. We will begin by writing our little Model class, which would be used for demonstration.

public class Model
{
    [Required(ErrorMessage = "Name cannot be empty")]
    [StringLength(9, MinimumLength = 3, ErrorMessage = "Name should have min 3 characters and max 9")]
    public string Name { get; set; }
    [Required]
    [Range(0,100,ErrorMessage = "Age should be between 1 and 100")]
    public int Age { get; set; }
}


Not too much to explain there. We have used the DataAnnotationAttribute to specify requirements of each property. We will now define a View to display this model. We will get to the ViewModel last, because thats where the trick lies.

<Grid>
    <StackPanel>
        <Grid Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="3*"/>
                <ColumnDefinition Width="7*"/>
            </Grid.ColumnDefinitions>

            <Label Content="Name"/>
            <TextBox Grid.Column="1" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" >
                <Validation.ErrorTemplate>
                    <ControlTemplate>
                        <StackPanel>
                            <AdornedElementPlaceholder x:Name="textBox"/>
                            <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
                        </StackPanel>
                    </ControlTemplate>
                </Validation.ErrorTemplate>
            </TextBox>
        </Grid>

        <Grid Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="3*"/>
                <ColumnDefinition Width="7*"/>
            </Grid.ColumnDefinitions>

The most significant part here is of course the property ValidatesOnNotifyDataErrors being set to true. Additionally I have also used ErrorTemplate to display the error but that is more of an implementation detail for this particular demo, you could opt for the default template or choose another that suits your needs.

The next task obiovusly would be to implement the INotifyDataErrorInfo property in your ViewModel. The INotifyDataErrorInfo is defined as following.

 public interface INotifyDataErrorInfo
 {
       bool HasErrors { get; }
       event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
       IEnumerable GetErrors(string? propertyName);
 }


As noticed, the interface comprises mainly of a property,method and event.

  • HasErrors : Indicates a value whether the current entity has Validation errors. This is a read-only property.
  • GetErrors : A Method which retrieves all the validation errors for the given Property. The property name is passed via parameter.
  • ErrorsChanged : This event would be raised each time the Validation errors has changed for a particular property or for the Entity as a whole.

Let us go ahead and implement our view model. We will use a dictionary to store the errors.

private IDictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

The next step is to create our Properties.

private Model _model = new ();

public string Title => "Data Annotation and INotifyDataErrorInfo";
public string Name
{
    get => _model.Name;
    set
    {
        if (Equals(_model.Name, value)) return;

        _model.Name = value;
        NotifyOfPropertyChange();
        Validate(value);
    }
}


public int Age
{
    get => _model.Age;
    set
    {
        if (_model.Age == value) return;

        _model.Age = value;
        NotifyOfPropertyChange();
        Validate(value);
    }
}

Notice a method Validate is being invoked each time a property changes. This method is responsible for validating the property and maintaining the dictionary of errors.

The implementation of Validate looks like following.

private void Validate(object val, [CallerMemberName] string propertyName = null)
{
    if (_errors.ContainsKey(propertyName)) _errors.Remove(propertyName);

    ValidationContext context = new ValidationContext(_model) { MemberName = propertyName };
    List<ValidationResult> results = new();

    if (!Validator.TryValidateProperty(val, context, results))
    {
        _errors[propertyName] = results.Select(x => x.ErrorMessage).ToList();
    }

    ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}

What remains now is to impement our interface, which is pretty straightforward.

public bool HasErrors => _errors.Any();

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

public IEnumerable GetErrors(string propertyName)
{
    return _errors.ContainsKey(propertyName) ? _errors[propertyName] : null;
}

The complete ViewModel looks like following.

public class DataAnnotionViewModel : ViewModelBase, INotifyDataErrorInfo
{
    private IDictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
    private Model _model = new ();

    public string Title => "Data Annotation and INotifyDataErrorInfo";
    public string Name
    {
        get => _model.Name;
        set
        {
            if (Equals(_model.Name, value)) return;

            _model.Name = value;
            NotifyOfPropertyChange();
            Validate(value);
        }
    }


    public int Age
    {
        get => _model.Age;
        set
        {
            if (_model.Age == value) return;

            _model.Age = value;
            NotifyOfPropertyChange();
            Validate(value);
        }
    }

    private void Validate(object val, [CallerMemberName] string propertyName = null)
    {
        if (_errors.ContainsKey(propertyName)) _errors.Remove(propertyName);

        ValidationContext context = new ValidationContext(_model) { MemberName = propertyName };
        List<ValidationResult> results = new();

        if (!Validator.TryValidateProperty(val, context, results))
        {
            _errors[propertyName] = results.Select(x => x.ErrorMessage).ToList();
        }

        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }

    public bool HasErrors => _errors.Any();

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public IEnumerable GetErrors(string propertyName)
    {
        return _errors.ContainsKey(propertyName) ? _errors[propertyName] : null;
    }
}

That’s all you need for Validating your ViewModels using DataAnnotationAttribute and INotifyDataErrorInfo. The complete code sample is available in my Github here.

API based Custom FormValidation using SemanticUI/Jquery

In the last post, we discussed on how to implement form validation and subsequent submit using the Semantic UI and JQuery. In this follow up post, we discuss how we can enhance the validation capabilities further, by having a custom validation that depends on an external API call.

For the sake of example, let’s focus on an mock User Sign Up page, where we would be checking if the username exists prior to form submit.

Let’s write down our basic form first.

<form class="ui form" method='post' id="RegisterUser">
  <h4 class="ui dividing header">User Sign Up</h4>
  <div class="field">
    <div class="two fields">
      <div class="eight wide field">
        <label>UserName</label>
        <input type="text" placeholder="Username" id="username">
      </div>
    </div>
  </div>
  <button class="ui button" tabindex="0">Sign Up</button>
  <div class="ui hidden negative message" id="formresult"></div>
  <div class="ui error message" id="formvalidation"></div>
</form>

Now, let’s create our Validation Script.

var formvalidationrules = {
    on: 'submit',
    fields: {
      username: {
        identifier: 'username',
        rules: [{
          type: 'custom',
          prompt: 'username already exists'
        }]
      },
    },
    onSuccess: function(event) {
      event.preventDefault();
    }
  };

  $('#RegisterUser').form(formvalidationrules);

As you can see, the rule type has been specified as ‘custom’. The next obvious task is to write down the method to do the custom validation itself.

$.fn.form.settings.rules.custom = function() {
    var result;
    $.ajax({
      type: 'POST',
      url: '/echo/html/',
      async: false,
      data: {
        'html': $('#username').val()
      },
      success: function(data) {
        result = !(data == 'test');

      },
    });
    return result;

  };

Couple of things to note here.
a) The rule method has been added to form.settings.rules.
b) Now this is very important, your ajax calls needs to be synchronous.

That’s it, you are ready to roll. Check out the script in action in Fiddle Link

Form Validation and Submit using Semantic UI and JQuery

Bootstrap, despite all the effort to make it easily understandable, always relied heavily on its short hand notations for css classes. This seemed okay, until Semantic UI entered the race with its near perfect usage of natural language. The fact that it is well integrated with jQuery makes it a good choice for web developers.

One of the most common tasks in any web development has to do with form. The following code illustrates how to validate the form and submit it using the Semantic UI APIs.

 

                <form class="ui large form" method="POST" role="form" id="ValidateUser">
                    <section class="ui stacked segment">
                        <div class="field">
                            <div class="ui left icon input">
                                <i class="user icon"></i>
                                @Html.TextBoxFor(a => a.UserName, new { placeholder = "UserName",id="username" })
                                @Html.ValidationMessageFor(a => a.UserName)
                            </div>
                        </div>
                        <div class="field">
                            <div class="ui left icon input">
                                <i class="lock icon"></i>
                                @Html.PasswordFor(a => a.Password, new { placeholder = "Password",id="password" })
                                @Html.ValidationMessageFor(a => a.Password)
                            </div>
                        </div>

                        <input id="submitbutton" class="ui submit fluid large green button" type="submit" value="Login"/> 
                        <!-- <div class="ui blue submit button">Submit</div> -->
                        <div class="ui hidden negative message" id="formresult"></div>
                        <div class="ui error message" id="formvalidation"></div>
                    </section>
                </form>
$(document).ready(function () {

            var urllink = '@Url.Action("Login", "Validation")';


            $('#ValidateUser').form(
                                     {
                                         on: 'blur',
                                         fields: {
                                             username: {
                                                 identifier: 'username',
                                                 rules: [{
                                                     type: 'empty',
                                                     prompt: 'Username cannot be empty'
                                                 }]
                                             },
                                             password: {
                                                 identifier: 'password',
                                                 rules: [{
                                                     type: 'empty',
                                                     prompt: 'Password cannot be emtpy'
                                                 }]
                                             }
                                         },
                                         onSuccess: function (event) {
                                             $('#formresult').hide();
                                             $('#formresult').text('');
                                             event.preventDefault();
                                             return false;
                                         }

                                     }
                                   )
                                 .api({
                                     url: urllink,
                                     method:'POST',
                                     serializeForm: true,
                                     data: new FormData(this),
                                     onSuccess: function (result) {
                                        $('#formresult').show();
                                        if (result.Success) {
                                            window.location = "/Dashboard/Dashboard";
                                        }
                                        else {

                                            $('#formresult').append(result.Msg);
                                        }
                                        return false;
                                    }
                    });


        });