We released an eBook this past May on Enterprise Application Patterns using Xamarin.Forms. The eBook focuses on core patterns and architectural guidance for developing Xamarin.Forms enterprise apps that are easier to test, maintain, and evolve. Guidance is provided on how to implement the Model-View-ViewModel (MVVM) pattern, dependency injection, navigation, validation, and configuration management, while maintaining loose coupling. In addition, there’s also guidance on performing authentication and authorization with IdentityServer, accessing remote data from containerized microservices, and unit testing.
This blog post explores validation in Xamarin.Forms enterprise apps. There are, of course, many approaches that can be taken to validation. What’s presented here is the validation approach taken in the eShopOnContainers mobile app, which is extensible, easily unit testable, and supports data binding and property change notification.
Introduction to Validation
Any app that accepts input from users should ensure that the input is valid. An app could, for example, check for input that contains only characters in a particular range, is of a certain length, or matches a particular format. Without validation, a user can supply data that causes the app to fail. Validation enforces business rules and prevents an attacker from injecting malicious data.
In the context of the Model-ViewModel-Model (MVVM) pattern, a view model or model will often be required to perform data validation and signal any validation errors to the view so that the user can correct them. The eShopOnContainers mobile app performs synchronous client-side validation of view model properties and notifies the user of any validation errors by highlighting the control that contains the invalid data, and by displaying error messages that inform the user of why the data is invalid. The following diagram shows the classes involved in performing validation in the eShopOnContainers mobile app:
View model properties that require validation are of type ValidatableObject<T>
, and each ValidatableObject<T>
instance has validation rules added to its Validations
property. Validation is invoked from the view model by calling the Validate
method of the ValidatableObject<T>
instance, which retrieves the validation rules and executes them against the ValidatableObject<T> Value
property. Any validation errors are placed into the Errors
property of the ValidatableObject<T>
instance, and the IsValid
property of the ValidatableObject<T>
instance is updated to indicate whether validation succeeded or failed.
Property change notification is provided by the ExtendedBindableObject
class, and so an Entry
control can bind to the IsValid
property of the ValidatableObject<T>
instance in the view model class to be notified of whether or not the entered data is valid.
Specifying Validation Rules
Validation rules are specified by creating a class that derives from the IValidationRule<T>
interface, which is shown in the following code example:
public interface IValidationRule<T> {     string ValidationMessage { get; set; }     bool Check(T value); }
This interface specifies that a validation rule class must provide a boolean Check
method that is used to perform the required validation, and a ValidationMessage
property whose value is the validation error message that will be displayed if validation fails.
The following code example shows the IsNotNullOrEmptyRule<T>
validation rule, which is used to perform validation of the username and password entered by the user on the LoginView
when the eShopOnContainers mobile app is configured to use mock services:
public class IsNotNullOrEmptyRule<T> : IValidationRule<T> {     public string ValidationMessage { get; set; }     public bool Check(T value)     {         if (value == null)         {             return false;         }         var str = value as string;         return !string.IsNullOrWhiteSpace(str);     } }
The Check
method returns a boolean
indicating whether the value argument is null
, empty, or consists only of whitespace characters.
Adding Validation Rules to a Property
In the eShopOnContainers mobile app, view model properties that require validation are declared to be of type ValidatableObject<T>
, where T
is the type of the data to be validated. The following code example shows an example of one such property:
public ValidatableObject<string> UserName {     get     {         return _userName;     }     set     {         _userName = value;         RaisePropertyChanged(() => UserName);     } }
For validation to occur, validation rules must be added to the Validations
collection of the ValidatableObject<T>
instance, as demonstrated in the following code example:
private void AddValidations() {     _userName.Validations.Add(new IsNotNullOrEmptyRule<string>     {         ValidationMessage = "A username is required."     }); }
This method adds the IsNotNullOrEmptyRule<T>
validation rule to the Validations
collection of the ValidatableObject<T>
instance, including a value for the ValidationMessage
property, which specifies the validation error message that will be displayed if validation fails.
Triggering Validation
Validation can be triggered manually for a view model property. For example, this occurs in the eShopOnContainers mobile app when the user taps the Login button on the LoginView
, when using mock services. The command delegate calls the MockSignInAsync
method in the LoginViewModel
, which invokes validation by executing the Validate
method, which in turn invokes the ValidateUserName
method:
private bool ValidateUserName() {     return _userName.Validate(); }
The ValidateUserName
method performs validation of the username entered by the user on the LoginView
, by invoking the Validate
method on the ValidatableObject<T>
instance. The following code example shows the Validate
method from the ValidatableObject<T>
class:
public bool Validate() {     Errors.Clear();     IEnumerable<string> errors = _validations         .Where(v => !v.Check(Value))         .Select(v => v.ValidationMessage);     Errors = errors.ToList();     IsValid = !Errors.Any();     return this.IsValid; }
This method clears the Errors
collection, and then retrieves any validation rules that were added to the object’s Validations
collection. The Check
method for each retrieved validation rule is executed, and the ValidationMessage
property value for any validation rule that fails to validate the data is added to the Errors
collection of the ValidatableObject<T>
instance. Finally, the IsValid
property is set, and its value is returned to the calling method, indicating whether validation succeeded or failed.
Validation is also automatically triggered whenever a bound property changes. For more information, see Triggering Validation when Properties Change.
Displaying Validation Errors
The eShopOnContainers mobile app notifies the user of any validation errors by highlighting the control that contains the invalid data with a red line, and by displaying an error message that informs the user why the data is invalid below the control containing the invalid data. The following screenshot shows part of the LoginView
in the eShopOnContainers mobile app when a validation error is present:
Highlighting a Control that Contains Invalid Data
The LineColorBehavior
attached behavior is used to highlight Entry
controls where validation errors have occurred. The following code example shows how the LineColorBehavior
attached behavior is attached to an Entry
control:
<Entry Text="{Binding UserName.Value, Mode=TwoWay}">     <Entry.Style>         <OnPlatform x:TypeArguments="Style"           iOS="{StaticResource EntryStyle}"           Android="{StaticResource EntryStyle}"           WinPhone="{StaticResource UwpEntryStyle}"/>     </Entry.Style>     ... </Entry>
The Entry
control consumes an explicit style, which is shown in the following code example:
<Style x:Key="EntryStyle"        TargetType="{x:Type Entry}">     ...     <Setter Property="behaviors:LineColorBehavior.ApplyLineColor"             Value="True" />     <Setter Property="behaviors:LineColorBehavior.LineColor"             Value="{StaticResource BlackColor}" />     ... </Style>
This style sets the ApplyLineColor
and LineColor
attached properties of the LineColorBehavior
attached behavior on the Entry
control. When the value of the ApplyLineColor
attached property is set, or changes, the LineColorBehavior
attached behavior executes the OnApplyLineColorChanged
method, which adds or removes the EntryLineColorEffect
class to the Entry
‘s Effects
collection. For more information about the EntryLineColorEffect
class, see Highlighting a Control that Contains Invalid Data.
The Entry
control also has a DataTrigger
added to its Triggers
collection. The following code example shows the DataTrigger
:
<Entry Text="{Binding UserName.Value, Mode=TwoWay}">     ...     <Entry.Triggers>         <DataTrigger             TargetType="Entry"             Binding="{Binding UserName.IsValid}"             Value="False">             <Setter Property="behaviors:LineColorBehavior.LineColor"                     Value="{StaticResource ErrorColor}" />         </DataTrigger>     </Entry.Triggers> </Entry>
This DataTrigger
monitors the UserName.IsValid
property, and if it’s value becomes false
, it executes the Setter
, which changes the LineColor
attached property of the LineColorBehavior
attached behavior to red.
Displaying Error Messages
The UI displays validation error messages in Label
controls below each control whose data failed validation. The following code example shows the Label
that displays a validation error message if the user has not entered a valid username:
<Label Text="{Binding UserName.Errors, Converter={StaticResource FirstValidationErrorConverter}"        Style="{StaticResource ValidationErrorLabelStyle}" />
Each Label
binds to the Errors
property of the view model object that’s being validated. The Errors
property is provided by the ValidatableObject<T>
class, and is of type List<string>
. Because the Errors
property can contain multiple validation errors, the FirstValidationErrorConverter
instance is used to retrieve the first error from the collection for display.
Summary
Enterprise Application Patterns using Xamarin.Forms focuses on core patterns and architectural guidance for developing Xamarin.Forms Enterprise Apps that are easier to test, maintain, and evolve. The eBook also ships with a sample application, the eShopOnContainers mobile app, which performs synchronous client-side validation of view model properties and notifies the user of any validation errors by highlighting the control that contains the invalid data, and by displaying error messages that inform the user why the data is invalid.
0 comments