Validation

There are exists two approaches to validate data in MugenMvvm:

  • validation by using data annotations;
  • validation by using validators classes.

All platforms support validation by using validators. If validation by using data annotations is supported by platform it is supported by MugenMvvm. Here we describe only usage of validators.

📘

Platform and validation approaches

Presented below is supported validation approaches for each mobile platform:
Android

  • validation by using data annotations;
  • validation by using validators classes.

iOS

  • validation by using data annotations;
  • validation by using validators classes.

UWP

  • validation by using validators classes.

Xamarin.Forms

  • validation by using validators classes.

Validation by Using Validators

Validation in MugenMvvm is based on viewmodel and bindings.
In order to validate your model you can inherit ValidatableViewModel. This viewmodel implements INotifyDataErrorInfo interface to notificate bindings about errors. But in practice, much more often you will validate your model if you edit it. In this case, you have to inherit EditableViewModel because as long as it provides methods to work with model, it also inherits ValidatableViewModel.

1920

ValidatableViewModel hierarchy

1920

EditableViewModel hierarchy

In order to validate your model successfully one can to follow the next steps:

  1. Inherit viewmodel by ValidatableViewModel or EditableViewModel.
  2. Inherit class ValidatorBase.
  3. Register your validator on IValidatorProvider.
  4. Add control to show validation errors and use bindings in order to show errors of validations.

Let's look at example. We will create some sample app on Android. It will takes lastname and firstname from user, validate them and show hello message.

  1. Let's create a new Android application "HelloValidation":
953
  1. Add new PCL project "Core", remove "Class1.cs" from project:
1920
  1. Add reference "MugenMvvmToolkit" via NuGet to "Core" project:
1920
  1. Add "Models" folder into "Core" project and add "User.cs" into it with the next content:
using MugenMvvmToolkit.Models;

namespace Core.Models
{
  public class User : NotifyPropertyChangedBase
  {
    private string _lastname = null;
    private string _firstname = null;        

    public string Lastname
    {
      get { return _lastname; }
      set
      {
        if (Equals(Lastname, value)) return;
        if (value == "")
          _lastname = null;
        else
          _lastname = value;
        OnPropertyChanged();
      }
    }

    public string Firstname
    {
      get { return _firstname; }
      set
      {
        if (Equals(Firstname, value)) return;
        if (value == "")
          _firstname = null;
        else
          _firstname = value;
        OnPropertyChanged();
      }
    }
  }
}
  1. Add "ValidationConstants.cs" into "Core" project with the next content:
using MugenMvvmToolkit.Models;

namespace Core
{
    public static class ValidationConstants
    {
        public static readonly DataConstant<string> IsNeedToValidate 
          = DataConstant.Create(() => IsNeedToValidate, true);
    }
}

This allow to disable\enable validation whenever needed.

  1. Add "ViewModels" folder into "Core" project and "MainViewModel.cs" with the next content:
using Core.Models;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.ViewModels;
using System.Windows.Input;

namespace Core.ViewModels
{
    public class MainViewModel : EditableViewModel<User>
    {
        private GridViewModel<User> _userGridViewModel;

        public MainViewModel()
        {
            AddUserCommand = new RelayCommand(AddUser, CanAddUser, this);            
        }

        public ICommand AddUserCommand { get; private set; }
        
        public string Lastname
        {
            get { return Entity.Lastname; }
            set
            {
                if (Equals(Entity.Lastname, value)) return;
                Entity.Lastname = value;
                OnPropertyChanged();
            }
        }

        public string Firstname
        {
            get { return Entity.Firstname; }
            set
            {
                if (Equals(Entity.Firstname, value)) return;
                Entity.Firstname = value;
                OnPropertyChanged();
            }
        }

        public GridViewModel<User> UserGridViewModel
        {
            get { return _userGridViewModel; }
            private set
            {
                if (Equals(value, _userGridViewModel)) return;
                _userGridViewModel = value;
                OnPropertyChanged();
            }
        }

        protected override void OnInitialized()
        {
            UserGridViewModel = GetViewModel<GridViewModel<User>>();            
            InitializeNewUser();
        }

        private async void AddUser()
        {
          // allow validation
            Settings.Metadata.AddOrUpdate(ValidationConstants.IsNeedToValidate, string.Empty);
            await ValidateAsync();

            if (IsValid)
            {
                ApplyChanges();
                var entity = Entity;
                UserGridViewModel.ItemsSource.Add(entity);
                UserGridViewModel.SelectedItem = entity;
                InitializeNewUser();
            }
        }

        private bool CanAddUser()
        {
            return IsValid;
        }

        private void InitializeNewUser()
        {
          // disable validation becuase it is new entity
            Settings.Metadata.Remove(ValidationConstants.IsNeedToValidate);
            var user = new User();
            InitializeEntity(user, true);
        }
    }
}

MainViewModel inherits EditableViewModel hence it can be validated against User type.
Inside the constructor AddUserCommand is initialized.
Please note that properties Firstname and Lastname are mapped on appropriate Entity properties.
CanAddUser method tracks IsValid property of ValidatableViewModel hence it will change the enable/disable state of button that allows to add new user.
AddUser method applies changes to Entity and add the entity into list view in order to show this to end-user.
Inside OnInitialized method UserGridViewModel is initialized as far as InitializeNewUser is invoked in order to init Entity property of viewmodel.
6. Add "Validators" folder into "Core" project and "UserValidator.cs" with the next content:

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Core.Models;
using MugenMvvmToolkit.Infrastructure.Validation;

namespace Core.Validators
{
    public class UserValidator : ValidatorBase<User>
    {
        #region Overrides of ValidatorBase

        protected override Task<IDictionary<string, IEnumerable>> ValidateInternalAsync(string propertyName, CancellationToken token)
        {
            if (!Context.ValidationMetadata.Contains(ValidationConstants.IsNeedToValidate))
                return EmptyResult;

            if (propertyName == null || propertyName == nameof(Instance.Firstname))
            {
                if (string.IsNullOrEmpty(Instance.Firstname))
                    return FromResult(new Dictionary<string, IEnumerable>
                                {
                                  { nameof(Instance.Firstname), "First name cannot be empty!" }
                                });
                if (!char.IsUpper(Instance.Firstname[0]))
                {
                    return FromResult(new Dictionary<string, IEnumerable>
                                {
                                  { nameof(Instance.Firstname), "First letter of first name have to be in upper case!" }
                                });
                }
            }
            if (propertyName == null || propertyName == nameof(Instance.Lastname))
            {
                if (string.IsNullOrEmpty(Instance.Lastname))
                    return FromResult(new Dictionary<string, IEnumerable>
                                {
                                  { nameof(Instance.Lastname), "Last name cannot be empty!" }
                                });
                if (!char.IsUpper(Instance.Lastname[0]))
                {
                    return FromResult(new Dictionary<string, IEnumerable>
                                {
                                  { nameof(Instance.Lastname), "First letter of last name have to be in upper case!" }
                                });
                }
            }
            return EmptyResult;
        }

        protected override Task<IDictionary<string, IEnumerable>> ValidateInternalAsync(CancellationToken token)
        {
            return ValidateInternalAsync(null, token);
        }

        #endregion
    }
}

In this validator we are checking that last and first names are not empty and are starting from letter in upper case. The errors will appear inside text editors directly.
7. Add "App.cs" into "Core" with next content:

using System;
using Core.ViewModels;
using MugenMvvmToolkit;

namespace Core
{
  public class App : MvvmApplication
  {
    public override Type GetStartViewModelType()
    {
      return typeof(MainViewModel);
    }
  }
}

8, Add "MainModule.cs" into "Core" and register UserValidator by using IValidatorProvider:

using MugenMvvmToolkit;
using MugenMvvmToolkit.Interfaces.Validation;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Modules;
using Core.Validators;

namespace Core
{
    public class PortableModule : ModuleBase
    {
        #region Constructors

        public PortableModule()
            : base(false, LoadMode.All)
        {
        }

        #endregion

        #region Overrides of ModuleBase

        /// <summary>
        ///     Loads the current module.
        /// </summary>
        protected override bool LoadInternal()
        {
            var validatorProvider = IocContainer.Get<IValidatorProvider>();
            //NOTE: Registering validator.
            validatorProvider.Register<UserValidator>();
            return true;
        }

        /// <summary>
        ///     Unloads the current module.
        /// </summary>
        protected override void UnloadInternal()
        {
        }

        #endregion
    }
}
  1. Add reference "MugenMvvmToolkit.StarterPack" via NuGet to "HelloValidation" project:
1680

Delete "MainActivity.cs" from "HelloValidation".
10. Add reference "Core" via NuGet to "HelloValidation" project:

850
  1. Let's create our Android layout. Replace the content of <codeMain.axml byt the next one:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              xmlns:pkg="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

  <TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="First Name" />
  <EditText android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            pkg:Bind="Text Firstname, Mode=TwoWay" />
  <TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textColor="#f00"
            pkg:Bind="Text $GetErrors(Firstname).FirstOrDefault(); Visible $GetErrors(Firstname).Any()" />

  <TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Last Name" />
  <EditText android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            pkg:Bind="Text Lastname, Mode=TwoWay, Validate=True" />

  <LinearLayout
      android:layout_width="fill_parent"
      android:layout_height="wrap_content">
    <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:text="Add user"
            pkg:Bind="Click AddUserCommand" />
  </LinearLayout>
  <ListView
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      pkg:ItemTemplate="@layout/_userlistitemtemplate"
      pkg:Bind="DataContext UserGridViewModel; ItemsSource ItemsSource; SelectedItem SelectedItem, Mode=TwoWay" />
</LinearLayout>

Here we added two editors to edit the last name and the first name of an user.

TODO: write about 2 approaches of validation ( Validate=True and binding to
$GetErrors(Firstname).FirstOrDefault();)

In order to enable validation Validate was initialized as true inside last name binding.
Button's Click is binded on AddUserCommand command.
Also ListView to visualize adding of new user was added and we have to add item template to have this worked out:

<?xml version="1.0" encoding="utf-8" ?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:pkg="http://schemas.android.com/apk/res-auto"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:orientation="horizontal">

  <TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            pkg:Bind="Text Lastname" />

  <TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            pkg:Bind="Text Firstname" />

</LinearLayout>
  1. Application is ready. Run it and test:
300

Source code of this application is placed on github.

Two Approaches to Show Validation Errors

There are exists two approaches to show validation errors.

The first one is use GetErrors() method and it's overloads to work with validaiton errors. When it is used In pair with MugenMvvm binding engine it becomes powerful and flexible way to visualize validation errors.
Let's take a look on GetErrors overloads:

  • GetErrors - returns errors for all validated properties.
  • GetErrors(propertyName) - returns errors for passed property name.
  • GetErrors(propertyName1, propertyName2, ...) - returns errors for each of passed property name (propertyName1, propertyName2, ..., propertyNameN).

Preseneted below is an example of how GetErrors method can be used in real Android app:

<EditText android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            pkg:Bind="Text Firstname, Mode=TwoWay" />
<TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textColor="#f00"
            pkg:Bind="Text $GetErrors(Firstname).FirstOrDefault(); Visible $GetErrors(Firstname).Any()" />

Here we added TextView that will show first error in list if it is not empty. As you can see, it is pretty easy with MugenMvvm binding engine.
Also, it is possible to get validation errors by contol name. Presented below is an example in which we display validation errors of et1 control:

<EditText android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:id="@+id/et1"
            pkg:Bind="Text Firstname, Mode=TwoWay" />
<TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textColor="#f00"
            pkg:Bind="Text $El(et1).Errors.FirstOrDefault(); Visible $El(et1).Errors.Any()" />

The second one is use platform specific approaches. For instance, on Android platfrom if you create text view as follows:

<EditText android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            pkg:Bind="Text Lastname, Mode=TwoWay, Validate=True" />

After that, validation errors will be shown as follows:

584

This approach is available on all platfroms. It is abstracted by IBindingErrorProvider interface.