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
.
In order to validate your model successfully one can to follow the next steps:
- Inherit viewmodel by
ValidatableViewModel
orEditableViewModel
. - Inherit class
ValidatorBase
. - Register your validator on
IValidatorProvider
. - 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.
- Let's create a new Android application "HelloValidation":
- Add new PCL project "Core", remove "Class1.cs" from project:
- Add reference "MugenMvvmToolkit" via NuGet to "Core" project:
- 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();
}
}
}
}
- 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.
- 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
}
}
- Add reference "MugenMvvmToolkit.StarterPack" via NuGet to "HelloValidation" project:
Delete "MainActivity.cs" from "HelloValidation".
10. Add reference "Core" via NuGet to "HelloValidation" project:
- 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>
- Application is ready. Run it and test:
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:
This approach is available on all platfroms. It is abstracted by IBindingErrorProvider interface.
Updated less than a minute ago