Validação em Aplicativos Empresariais

Observação

Este eBook 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 de quaisquer erros de validação realçando 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.

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 do modelo de exibição chamando o Validate método da ValidatableObject<T> instância , que recupera as regras de validação e as executa na ValidatableObject<T>Value propriedade . Quaisquer erros de validação são colocados na Errors propriedade da ValidatableObject<T> instância e a IsValid propriedade da ValidatableObject<T> instância é atualizada para indicar se a 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 booleanCheck 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 da senha inseridos pelo usuário no LoginView ao 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 um boolean que indica 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 um boolean que indica 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 regras de validação devem ser adicionadas à Validations 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.

Disparando validação

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 for alterada.

Disparando 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 Validar 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 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.

Disparando 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 no Entry, que é gerado quando o texto no Entry é alterado. 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 de quaisquer erros de validação realçando 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.

Exibindo erros de validação durante o logon

Figura 6-2: Exibindo erros de validação durante o logon

Realçando um controle que contém dados inválidos

O LineColorBehavior comportamento anexado é usado para realçar Entry os controles em que ocorreram erros de validação. O exemplo de código a seguir mostra como o LineColorBehavior comportamento anexado é anexado 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 seguinte exemplo de código:

<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 anexadas e LineColor do LineColorBehavior comportamento anexado no Entry controle . Para mais informações sobre estilos, confira Estilos.

Quando o valor da ApplyLineColor propriedade anexada é definido ou é alterado, o LineColorBehavior comportamento anexado executa o OnApplyLineColorChanged método , que é mostrado no exemplo de 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 ApplyLineColor propriedade anexada 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 seguinte exemplo de código:

public class EntryLineColorEffect : RoutingEffect  
{  
    public EntryLineColorEffect() : base("eShopOnContainers.EntryLineColorEffect")  
    {  
    }  
}

A RoutingEffect classe representa um efeito independente de plataforma que encapsula 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 de 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 a alterações de propriedade associáveis 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 forem inseridos no Entry controle, ele aplicará uma linha preta à parte inferior do controle para indicar que não há erro de validação. A Figura 6-3 mostra um exemplo disso.

Linha preta indicando nenhum erro de validação

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 LineColorBehavior comportamento anexado para vermelho. A Figura 6-4 mostra um exemplo disso.

Linha vermelha indicando erro de validação

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, ele será alterado 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 Label um se associa à 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 realçando 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 do modelo de exibição chamando o Validate método da ValidatableObject<T> instância , que recupera as regras de 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 ValidatableObject<T> instância é atualizada para indicar se a validação foi bem-sucedida ou falhou.