[Xamarin] Fluent API for Xamarin.Forms validation using Behaviors

  1. Home
  2. /
  3. Technologies
  4. /
  5. [Xamarin] Fluent API for Xamarin.Forms validation using Behaviors

[Xamarin] Fluent API for Xamarin.Forms validation using Behaviors

Posted in : Technologies on by : zquanghoangz Comments:

From my old post: https://zquanghoangz.blogspot.sg/2016/03/xamarinforms-validation-using-behaviors.html

 

It has a lot of attentions from Xamarin developer.

I decide to improve the code readability by using Fluent API, that is modern application development this time.

The change will focus on Validate function. The function before changed will look like this:

private void Validate(Entry entry, string newTextValue) { if (!entry.IsVisible || !entry.IsEnabled) { SetDefaultValidate(entry); return; } if (IsCheckEmpty) { IsValid = ValidatorsFactory.IsValidEmpty(newTextValue); Message = Messages.FieldCannotBlank; if (!IsValid) { entry.TextColor = Color.Red; return; } } if (IsCheckEmail) { IsValid = ValidatorsFactory.IsValidEmail(newTextValue); Message = Messages.EmailIncorrectFormat; if (!IsValid) { entry.TextColor = Color.Red; return; } } if (IsCheckNumber) { IsValid = ValidatorsFactory.IsValidNumber(newTextValue); Message = Messages.PleaseInputNumber; if (!IsValid) { entry.TextColor = Color.Red; return; } } if (IsCheckTelephone) { IsValid = ValidatorsFactory.IsValidTelephone(newTextValue); Message = Messages.TelephoneIncorrectFormat; if (!IsValid) { entry.TextColor = Color.Red; return; } } if (MinLength > 0) { IsValid = ValidatorsFactory.IsValidMinLength(newTextValue, MinLength); Message = Messages.MinimizeLengthIs + MinLength; if (!IsValid) { entry.TextColor = Color.Red; return; } } if (MaxLength > 0) { IsValid = ValidatorsFactory.IsValidMaxLength(newTextValue, MaxLength); Message = Messages.MaximizeLengthIs + MaxLength; if (!IsValid) { entry.TextColor = Color.Red; return; } } if (MinValue > 0) { IsValid = ValidatorsFactory.IsValidMinValue(newTextValue, MinValue); Message = Messages.MinimizeLengthIs + MinValue; if (!IsValid) { entry.TextColor = Color.Red; return; } } if (MaxValue > 0) { IsValid = ValidatorsFactory.IsValidMaxValue(newTextValue, MaxValue); Message = Messages.MinimizeValueIs + MaxValue; if (!IsValid) { entry.TextColor = Color.Red; return; } } //TODO: add more validation //Default SetDefaultValidate(entry); }
EntryValidatorBehavior - old

After applied Fluent API:

private void Validate(Entry entry, string newTextValue) { if (!entry.IsVisible || !entry.IsEnabled) { SetDefaultValidate(entry); return; } this.When(x => x.IsCheckEmpty) .ValidateBy(() => ValidatorsFactory.IsValidEmpty(newTextValue)) .WithMessage(Messages.FieldCannotBlank) .When(this, x => x.IsCheckEmail) .ValidateBy(() => ValidatorsFactory.IsValidEmail(newTextValue)) .WithMessage(Messages.EmailIncorrectFormat) .When(this, x => x.IsCheckNumber) .ValidateBy(() => ValidatorsFactory.IsValidNumber(newTextValue)) .WithMessage(Messages.PleaseInputNumber) .When(this, x => x.IsCheckTelephone) .ValidateBy(() => ValidatorsFactory.IsValidTelephone(newTextValue)) .WithMessage(Messages.TelephoneIncorrectFormat) .When(this, x => x.MinLength > 0) .ValidateBy(() => ValidatorsFactory.IsValidMinLength(newTextValue, MinLength)) .WithMessage(Messages.MinimizeLengthIs + MinLength) .When(this, x => x.MaxLength > 0) .ValidateBy(() => ValidatorsFactory.IsValidMaxLength(newTextValue, MaxLength)) .WithMessage(Messages.MaximizeLengthIs + MaxLength) .When(this, x => x.MinValue > 0) .ValidateBy(() => ValidatorsFactory.IsValidMinValue(newTextValue, MinValue)) .WithMessage(Messages.MinimizeValueIs + MinValue) .When(this, x => x.MaxValue > 0) .ValidateBy(() => ValidatorsFactory.IsValidMaxValue(newTextValue, MaxValue)) .WithMessage(Messages.MaximizeValueIs + MaxValue) .ApplyResult<EntryValidatorBehavior, Entry>(this); if (!IsValid) { entry.TextColor = Color.Red; return; } //Default SetDefaultValidate(entry); }
EntryValidatorBehavior

Is this more readable for you?

In this post, I will go detail how to code Fluent API for the validation.

The Fluent API design looks like:

FluentAPI diagram

First thing is, implement extension method for When:

public static class BehaviorsBehaviorsExtensions { public static FluentValidation When<T> (this T subject, Expression<Func<T, bool>> expressionProperty) { Func<T, bool> func = expressionProperty.Compile(); bool value = func(subject); return new FluentValidation(value); } public static FluentValidation When<TValidator> (this ValidationCollected validationCollected, TValidator subject, Expression<Func<TValidator, bool>> expressionProperty) { Func<TValidator, bool> func = expressionProperty.Compile(); bool value = func(subject); return new FluentValidation(validationCollected, value); } }
BehaviorsBehaviorsExtensions

We will do validate when the condition is true, the condition is an expression function of T, When function will get the value to create new FluentValidation instance.

With FluentValidation we can send manyValidate functions and only one message in case not valid. This is how I implement it:

public class FluentValidation { private ValidationCollected _validationCollected; private readonly bool _hasValidation = false; private string _message = string.Empty; private readonly List<Func<bool>> _validateFuncs; public FluentValidation(bool hasValidation) { _hasValidation = hasValidation; _validationCollected = new ValidationCollected(); _validateFuncs = new List<Func<bool>>(); } public FluentValidation(ValidationCollected validationCollected, bool hasValidation) { _hasValidation = hasValidation; _validationCollected = validationCollected; _validateFuncs = new List<Func<bool>>(); } public FluentValidation ValidateBy(Func<bool> func) { _validateFuncs.Add(func); return this; } public ValidationCollected WithMessage(string message) { _message = message; //collect validation if (_validationCollected == null) { _validationCollected = new ValidationCollected(); } //Only store invalid values, in-case want to get all message _validationCollected.Add(_hasValidation, _validateFuncs, _message); return _validationCollected; } }
FluentValidation

The ValidationCollected class will store all invalid case, to perform the result. We have 2 results here, the first is Message for the first invalid case, the second is Message for all the invalid cases.

That class here:

public class ValidationCollected { private readonly List<ValidationObject> _validationObjects; public ValidationCollected() { _validationObjects = new List<ValidationObject>(); } public T ApplyResult<T, TCtrl>(T validatorBehavior) where TCtrl : BindableObject where T : ValidatorBehavior<TCtrl> { var validationObject = _validationObjects.FirstOrDefault(x => !x.IsValid); if (validationObject != null) { validatorBehavior.NoValided(validationObject.Message); } else { validatorBehavior.Valided(); } //NOTE: Do set default valid value if needed return validatorBehavior; } public T ApplyAllResults<T, TCtrl>(T validatorBehavior) where TCtrl : BindableObject where T : ValidatorBehavior<TCtrl> { if (_validationObjects != null && _validationObjects.Any()) { var message = string.Join(", ", _validationObjects.Select(x => x.Message)); validatorBehavior.NoValided(message); } else { validatorBehavior.Valided(); } //NOTE: Do set default valid value if needed return validatorBehavior; } public void Add(bool hasValidation, List<Func<bool>> validateFuncs, string message) { _validationObjects.Add(new ValidationObject { HasValidation = hasValidation, ValidateFuncs = validateFuncs, Message = message }); } private class ValidationObject { public bool IsValid => DoValidate(); public bool HasValidation { get; set; } public List<Func<bool>> ValidateFuncs { get; set; } public string Message { get; set; } private bool DoValidate() { if (!HasValidation) { return true; } var isValid = true; foreach (var func in ValidateFuncs) { isValid = isValid && func(); } return isValid; } } }
ValidationCollected

This is the demo:

screenshoot

 

Full source code demo can be found here: https://github.com/zquanghoangz/Hoang-s-Behaviors-Validation

*Please note: source code will update in the future, sometimes it will not match with this post.

Thank for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *