Validação em aplicativos empresariais
Observação
Este e-book foi publicado na primavera de 2017 e não foi atualizado desde então. Há muito no livro que permanece valioso, mas parte do material está desatualizado.
Todo aplicativo que aceitar a entrada dos usuários precisará garantir que a entrada seja válida. Um aplicativo pode, por exemplo, verificar se a entrada contém apenas caracteres em determinado intervalo, tem determinado comprimento ou corresponde a um formato específico. Sem validação, um usuário pode fornecer dados que fazem com que o aplicativo falhe. A validação impõe regras de negócios e impede que um invasor injete dados mal-intencionados.
No contexto do padrão MVVM (Model-View-ViewModel), um modelo ou modelo de exibição geralmente será exigido para executar a validação de dados e sinalizar eventuais erros de validação para a exibição a fim de que o usuário possa corrigi-los. O aplicativo móvel eShopOnContainers executa a validação síncrona do lado do cliente das propriedades do modelo de exibição e notifica o usuário sobre quaisquer erros de validação, destacando o controle que contém os dados inválidos e exibindo mensagens de erro que informam ao usuário por que os dados são inválidos. A Figura 6-1 mostra as classes envolvidas na execução da validação no aplicativo móvel eShopOnContainers.
Figura 6-1: Classes de validação no aplicativo móvel eShopOnContainers
As propriedades do modelo de exibição que exigem validação são do tipo ValidatableObject<T>
e cada instância ValidatableObject<T>
tem regras de validação adicionadas à sua propriedade Validations
. A validação é invocada a partir do modelo de exibição chamando o Validate
método da instância, que recupera as regras de ValidatableObject<T>
validação e as executa na ValidatableObject<T>
Value
propriedade. Todos os erros de validação são colocados na Errors
propriedade da ValidatableObject<T>
instância, e a IsValid
propriedade da instância é atualizada para indicar se a ValidatableObject<T>
validação foi bem-sucedida ou falhou.
A notificação de alteração de propriedade é fornecida pela classe ExtendedBindableObject
e, portanto, um controle Entry
pode associar-se à propriedade IsValid
da instância ValidatableObject<T>
na classe de modelo de exibição para ser notificado sobre a validade dos dados inseridos.
Especificando regras de validação
As regras de validação são especificadas pela criação de uma classe que deriva da interface IValidationRule<T>
, que é mostrada no exemplo de código abaixo:
public interface IValidationRule<T>
{
string ValidationMessage { get; set; }
bool Check(T value);
}
Essa interface especifica que uma classe de regra de validação deve fornecer um boolean
Check
método usado para executar a validação necessária e uma ValidationMessage
propriedade cujo valor é a mensagem de erro de validação que será exibida se a validação falhar.
O exemplo de código a seguir mostra a IsNotNullOrEmptyRule<T>
regra de validação, que é usada para executar a validação do nome de usuário e senha inseridos pelo usuário ao LoginView
usar serviços fictícios no aplicativo móvel eShopOnContainers:
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);
}
}
O Check
método retorna uma boolean
indicação se o argumento value é null
, vazio ou consiste apenas em caracteres de espaço em branco.
Embora não seja usado pelo aplicativo móvel eShopOnContainers, o exemplo de código a seguir mostra uma regra de validação para validar endereços de email:
public class EmailRule<T> : IValidationRule<T>
{
public string ValidationMessage { get; set; }
public bool Check(T value)
{
if (value == null)
{
return false;
}
var str = value as string;
Regex regex = new Regex(@"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$");
Match match = regex.Match(str);
return match.Success;
}
}
O Check
método retorna uma boolean
indicação se o argumento value é ou não um endereço de email válido. Isso é feito pela pesquisa do argumento de valor para a primeira ocorrência do padrão de expressão regular especificado no construtor Regex
. Se o padrão de expressão regular foi encontrado na cadeia de caracteres de entrada pode ser determinado verificando o valor da Match
propriedade do Success
objeto.
Observação
Às vezes, a validação de propriedade pode envolver propriedades dependentes. Um exemplo de propriedades dependentes é quando o conjunto de valores válidos para a propriedade A depende do valor específico que foi definido na propriedade B. A verificação de se o valor da propriedade A é um dos valores permitidos envolveria a recuperação do valor da propriedade B. Além disso, quando o valor da propriedade B for alterado, a propriedade A precisará ser revalidada.
Adicionando regras de validação a uma propriedade
No aplicativo móvel eShopOnContainers, as propriedades do modelo de exibição que exigem validação são declaradas como do tipo ValidatableObject<T>
, em que T
é o tipo dos dados a serem validados. O exemplo de código abaixo mostra um exemplo de duas dessas propriedades:
public ValidatableObject<string> UserName
{
get
{
return _userName;
}
set
{
_userName = value;
RaisePropertyChanged(() => UserName);
}
}
public ValidatableObject<string> Password
{
get
{
return _password;
}
set
{
_password = value;
RaisePropertyChanged(() => Password);
}
}
Para que a validação ocorra, as Validations
regras de validação devem ser adicionadas à coleção de cada ValidatableObject<T>
instância, conforme demonstrado no exemplo de código a seguir:
private void AddValidations()
{
_userName.Validations.Add(new IsNotNullOrEmptyRule<string>
{
ValidationMessage = "A username is required."
});
_password.Validations.Add(new IsNotNullOrEmptyRule<string>
{
ValidationMessage = "A password is required."
});
}
Esse método adiciona a regra de validação IsNotNullOrEmptyRule<T>
à coleção Validations
de cada instância ValidatableObject<T>
, especificando valores para a propriedade ValidationMessage
da regra de validação, que especifica a mensagem de erro de validação exibida se a validação falhar.
Validação de acionamento
A abordagem de validação usada no aplicativo móvel eShopOnContainers pode disparar manualmente a validação de uma propriedade e disparar automaticamente a validação quando uma propriedade é alterada.
Acionando a validação manualmente
A validação pode ser disparada manualmente para uma propriedade de modelo de exibição. Por exemplo, isso ocorre no aplicativo móvel eShopOnContainers quando o usuário toca no botão Logon no LoginView
, ao usar serviços fictícios. O delegado de comando chama o método MockSignInAsync
em LoginViewModel
, que invoca a validação executando o método Validate
, mostrado no exemplo de código abaixo:
private bool Validate()
{
bool isValidUser = ValidateUserName();
bool isValidPassword = ValidatePassword();
return isValidUser && isValidPassword;
}
private bool ValidateUserName()
{
return _userName.Validate();
}
private bool ValidatePassword()
{
return _password.Validate();
}
O Validate
método executa a validação do nome de usuário e da senha inseridos pelo usuário no LoginView
, invocando o método Validate em cada ValidatableObject<T>
instância. O exemplo de código a seguir mostra o método Validate da ValidatableObject<T>
classe:
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;
}
Esse método limpa a Errors
coleção e, em seguida, recupera todas as regras de validação que foram adicionadas à coleção do Validations
objeto. O método Check
para cada regra de validação recuperada é executado e o valor da propriedade ValidationMessage
para qualquer regra de validação que não valida os dados é adicionada à coleção Errors
da instância ValidatableObject<T>
. Por fim, a propriedade IsValid
é definida e seu valor é retornado ao método de chamada, indicando se a validação foi bem-sucedida ou falhou.
Acionando a validação quando as propriedades são alteradas
A validação também pode ser disparada sempre que uma propriedade associada for alterada. Por exemplo, quando uma associação bidirecional na em LoginView
define a propriedade UserName
ou Password
, a validação é disparada. O exemplo de código abaixo demonstra como isso ocorre:
<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
<Entry.Behaviors>
<behaviors:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding ValidateUserNameCommand}" />
</Entry.Behaviors>
...
</Entry>
O controle Entry
associa à propriedade UserName.Value
da instância ValidatableObject<T>
, e a coleção Behaviors
do controle tem uma instância EventToCommandBehavior
adicionada a ela. Esse comportamento executa o ValidateUserNameCommand
em resposta ao evento [TextChanged
] disparado Entry
no , que é gerado quando o texto nas Entry
alterações. Por sua vez, o delegado ValidateUserNameCommand
executa o método ValidateUserName
, que executa o método Validate
na instância ValidatableObject<T>
. Portanto, sempre que o usuário insere um caractere no controle de Entry
como nome de usuário, a validação dos dados inseridos é executada.
Para obter mais informações sobre comportamentos, consulte Implementando comportamentos.
Exibindo erros de validação
O aplicativo móvel eShopOnContainers notifica o usuário sobre quaisquer erros de validação, destacando o controle que contém os dados inválidos com uma linha vermelha e exibindo uma mensagem de erro que informa ao usuário por que os dados são inválidos abaixo do controle que contém os dados inválidos. Quando os dados inválidos são corrigidos, a linha muda para preto e a mensagem de erro é removida. A Figura 6-2 mostra o LoginView no aplicativo móvel eShopOnContainers quando erros de validação estão presentes.
Figura 6-2: Exibindo erros de validação durante o login
Realçando um controle que contém dados inválidos
O LineColorBehavior
comportamento anexado é usado para realçar Entry
controles onde ocorreram erros de validação. O exemplo de código a seguir mostra como o comportamento anexado é anexado LineColorBehavior
a um Entry
controle:
<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
<Entry.Style>
<OnPlatform x:TypeArguments="Style">
<On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
<On Platform="UWP" Value="{StaticResource UwpEntryStyle}" />
</OnPlatform>
</Entry.Style>
...
</Entry>
O Entry
controle consome um estilo explícito, que é mostrado no exemplo de código a seguir:
<Style x:Key="EntryStyle"
TargetType="{x:Type Entry}">
...
<Setter Property="behaviors:LineColorBehavior.ApplyLineColor"
Value="True" />
<Setter Property="behaviors:LineColorBehavior.LineColor"
Value="{StaticResource BlackColor}" />
...
</Style>
Esse estilo define as ApplyLineColor
propriedades e LineColor
anexadas do LineColorBehavior
comportamento anexado no Entry
controle. Para mais informações sobre estilos, confira Estilos.
Quando o valor da propriedade anexada é definido ou alterado, o comportamento anexado LineColorBehavior
executa o OnApplyLineColorChanged
método, que é mostrado no exemplo de ApplyLineColor
código a seguir:
public static class LineColorBehavior
{
...
private static void OnApplyLineColorChanged(
BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as View;
if (view == null)
{
return;
}
bool hasLine = (bool)newValue;
if (hasLine)
{
view.Effects.Add(new EntryLineColorEffect());
}
else
{
var entryLineColorEffectToRemove =
view.Effects.FirstOrDefault(e => e is EntryLineColorEffect);
if (entryLineColorEffectToRemove != null)
{
view.Effects.Remove(entryLineColorEffectToRemove);
}
}
}
}
Os parâmetros para esse método fornecem a instância do controle ao qual o comportamento está anexado e os valores antigos e novos da ApplyLineColor
propriedade anexada. A EntryLineColorEffect
classe será adicionada à coleção do Effects
controle se a propriedade anexada ApplyLineColor
for true
, caso contrário, ela será removida da coleção do Effects
controle. Para obter mais informações sobre comportamentos, consulte Implementando comportamentos.
As EntryLineColorEffect
subclasses da RoutingEffect
classe e são mostradas no exemplo de código a seguir:
public class EntryLineColorEffect : RoutingEffect
{
public EntryLineColorEffect() : base("eShopOnContainers.EntryLineColorEffect")
{
}
}
A RoutingEffect
classe representa um efeito independente de plataforma que envolve um efeito interno específico da plataforma. Isso simplifica o processo de remoção do efeito, porque não há nenhum acesso de tempo de compilação às informações de tipo para um efeito específico da plataforma. O EntryLineColorEffect
chama o construtor da classe base, passando um parâmetro que consiste em uma concatenação do nome do grupo de resolução e a ID exclusiva especificada em cada classe de efeito específica da plataforma.
O exemplo de código a seguir mostra a eShopOnContainers.EntryLineColorEffect
implementação para iOS:
[assembly: ResolutionGroupName("eShopOnContainers")]
[assembly: ExportEffect(typeof(EntryLineColorEffect), "EntryLineColorEffect")]
namespace eShopOnContainers.iOS.Effects
{
public class EntryLineColorEffect : PlatformEffect
{
UITextField control;
protected override void OnAttached()
{
try
{
control = Control as UITextField;
UpdateLineColor();
}
catch (Exception ex)
{
Console.WriteLine("Can't set property on attached control. Error: ", ex.Message);
}
}
protected override void OnDetached()
{
control = null;
}
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
if (args.PropertyName == LineColorBehavior.LineColorProperty.PropertyName ||
args.PropertyName == "Height")
{
Initialize();
UpdateLineColor();
}
}
private void Initialize()
{
var entry = Element as Entry;
if (entry != null)
{
Control.Bounds = new CGRect(0, 0, entry.Width, entry.Height);
}
}
private void UpdateLineColor()
{
BorderLineLayer lineLayer = control.Layer.Sublayers.OfType<BorderLineLayer>()
.FirstOrDefault();
if (lineLayer == null)
{
lineLayer = new BorderLineLayer();
lineLayer.MasksToBounds = true;
lineLayer.BorderWidth = 1.0f;
control.Layer.AddSublayer(lineLayer);
control.BorderStyle = UITextBorderStyle.None;
}
lineLayer.Frame = new CGRect(0f, Control.Frame.Height-1f, Control.Bounds.Width, 1f);
lineLayer.BorderColor = LineColorBehavior.GetLineColor(Element).ToCGColor();
control.TintColor = control.TextColor;
}
private class BorderLineLayer : CALayer
{
}
}
}
O OnAttached
método recupera o controle nativo para o Xamarin.FormsEntry
controle e atualiza a cor da linha chamando o UpdateLineColor
método. A OnElementPropertyChanged
substituição responde às alterações de propriedade associável no Entry
controle atualizando a cor da linha se a propriedade anexada LineColor
for alterada ou a Height
propriedade das Entry
alterações. Para obter mais informações sobre efeitos, confira Efeitos.
Quando dados válidos são inseridos Entry
no controle, ele aplicará uma linha preta na parte inferior do controle, para indicar que não há erro de validação. A Figura 6-3 mostra um exemplo disso.
Figura 6-3: Linha preta indicando nenhum erro de validação
O Entry
controle também tem um DataTrigger
adicionado à sua Triggers
coleção. O exemplo de código a seguir mostra o 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>
Isso DataTrigger
monitora a UserName.IsValid
propriedade e, se seu valor se tornar false
, ele executa o Setter
, que altera a LineColor
propriedade anexada do comportamento anexado LineColorBehavior
para vermelho. A Figura 6-4 mostra um exemplo disso.
Figura 6-4: Linha vermelha indicando erro de validação
A linha no Entry
controle permanecerá vermelha enquanto os dados inseridos forem inválidos, caso contrário, ela mudará para preto para indicar que os dados inseridos são válidos.
Para obter mais informações sobre Gatilhos, consulte Gatilhos.
Exibindo mensagens de erro
A interface do usuário exibe mensagens de erro de validação em controles de rótulo abaixo de cada controle cuja validação de dados falhou. O exemplo de código a seguir mostra o Label
que exibe uma mensagem de erro de validação se o usuário não tiver inserido um nome de usuário válido:
<Label Text="{Binding UserName.Errors, Converter={StaticResource FirstValidationErrorConverter}}"
Style="{StaticResource ValidationErrorLabelStyle}" />
Cada um Label
é associado à Errors
propriedade do objeto de modelo de exibição que está sendo validado. A propriedade Errors
é fornecida pela classe ValidatableObject<T>
e é do tipo List<string>
. Como a propriedade Errors
pode conter vários erros de validação, a instância FirstValidationErrorConverter
é usada para recuperar o primeiro erro da coleção para fins de exibição.
Resumo
O aplicativo móvel eShopOnContainers executa a validação síncrona do lado do cliente das propriedades do modelo de exibição e notifica o usuário sobre quaisquer erros de validação, destacando o controle que contém os dados inválidos e exibindo mensagens de erro que informam ao usuário por que os dados são inválidos.
As propriedades do modelo de exibição que exigem validação são do tipo ValidatableObject<T>
e cada instância ValidatableObject<T>
tem regras de validação adicionadas à sua propriedade Validations
. A validação é invocada a partir do modelo de exibição chamando o Validate
método da instância, que recupera as regras de ValidatableObject<T>
validação e as executa na ValidatableObject<T>
Value
propriedade. Todos os erros de validação são colocados na Errors
propriedade da ValidatableObject<T>
instância, e a IsValid
propriedade da instância é atualizada para indicar se a ValidatableObject<T>
validação foi bem-sucedida ou falhou.