Asp.Net Validator Assíncrono    

Neste post passado falei como usar Validator com controles HTMLs puros, para facilitar algumas coisas quando se usa Ajax.

Hoje vou falar novamente sobre Validators, mas agora criando um customizado para necessidades que muitas vezes podemos achar no desenvolvimento de sites com Ajax: Validações Assíncronas.

Imagine um formulário de cadastro, totalmente com Ajax, e você gostaria de validar informações de negócio, onde muitas vezes é necessário fazer algum tipo de consulta em banco de dados, etc.

Vamos supor: Você quer saber, num formulário de cadastro de usuário, se o usuário digitado já existe na base de dados, e mostrar o erro no momento que o usuário digitar o usuário e trocar o foco do controle. Veja bem, eu sei que é possível fazer isso com simples Ajax e os eventos onblur do JavaScript, mas a idéia deste post é mostrar como criar novos controles Asp.Net Validator, e aproveitar funcionalidades como o Validator Callout.

Como funciona um Validator?

No post citado acima, expliquei rapidamente como funciona um Validator, agora vou explicar um pouco mais.

Todo Validator herda da classe System.Web.UI.WebControls.BaseValidator, nessa classe existe um método chamado AddAttributesToRender, o qual é responsável por renderizar os atributes que os códigos do lado do cliente usarão para validar as informações de um campo. Sempre que um Validator é implementado, é necessário renderizar atributos específicos de acordo com a função do Validator.

Estes atributos são necessários porque os Validator precisam renderizar as propriedades definidas no Server Side para o navegador do Cliente. Em geral, os Validators realizam suas validações ainda no JavaScript, por isso, o JavaScript precisa conhecer os controles que serão validados, as mensagens de erros, etc. Para isso, o Asp.Net utiliza o método RegisterExpandoAttribute , que pode ser encontrado tanto Page.ClientScript como no ScriptManager. No nosso caso, como estaremos usando este validators sempre com Ajax, vamos utilizar o método RegisterExpandoAttribute do ScriptManager.

Obs.: Para ficar mais correto, poderíamos utilizar um Padrão de Projeto conhecido como Adapter, onde ficaria encapsulado a utilização do método RegisterExpandoAttribute do Page.ClientScript ou do ScritptManager de forma transparente, mas isso vai além do intuito deste post, e para ficar mais simples, estou utilizando diretamente o do ScriptManeger.

E para que serve o método RegisterExpandoAttribute? Este método renderiza propriedades em objetos JavaScripts. Se for ver como o Validator do post anterior foi renderizado, veremos:

var MainContent_reqNome = document.all ? document.all["MainContent_reqNome"] : document.getElementById("MainContent_reqNome");
MainContent_reqNome.controltovalidate = "MainContent_txtNome";
MainContent_reqNome.errormessage = "Preencha o Nome";
MainContent_reqNome.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
MainContent_reqNome.initialvalue = "";

No exemplo acima, MainContent_reqNome é objeto JavaScript que representa o Validator, e controltovalidate, errormessage, evaluationfunction e initialvalue são os ExpandoAttribute.

Criando o AsyncValidator

Para criar Validator Assíncrono, iremos definir duas propriedades no lado do servidor para conseguirmos informações básicas do que o Usuário quer validar. A maioria das demais propriedades de um validator, como ControlToValidate, já estão implementadas na classe base BaseValidator.

	/// 
        /// Método estático na página que fará a validação.
        /// Este método deve estar decorado com o atributo WebMethod
        /// Sua assinatura deve ser:
        /// 
        /// public static bool NomeDoMetodo(string valor){}
        /// 
        /// 
        public string ServerValidationMethod
        {
            get
            {
                if (this.ViewState["ServerValidationMethod"] == null)
                    return "";

                return this.ViewState["ServerValidationMethod"].ToString();
            }
            set { this.ViewState["ServerValidationMethod"] = value; }
        }

        /// 
        /// Caso se deseje alterar o valor do campo definido na propriedade ControlToValidate, define-se um método no javascript com a seguinte assinatura function (sender,args).
        /// O parâmetro args contém a propriedade Value, que contem o valor do campo, que pode ser alterado.
        /// Neste caso, o valor assumido para ser enviado para o server é o valor alterado.
        /// 
        public string CustomValueFunction
        {
            get
            {
                if (this.ViewState["CustomValueFunction"] == null)
                    return "";

                return this.ViewState["CustomValueFunction"].ToString();
            }
            set { this.ViewState["CustomValueFunction"] = value; }
        }

 

O código acima define duas propriedades. Acredito que a propriedade que merece um comentário extra é a ServerValidationMethod. Essa propriedade armazena o nome de um PageMethod que é o responsável por fazer a validação no lado do Server. Por padrão ele recebe a informação que está no campo a ser validado, porém, essa informação pode ser alterada antes de ser enviada ao servidor, pelo método JavaScript definido na propriedade CustomValueFunction.

Para fazer estas informações definidas no servidor ficarem disponíveis no cliente, sobrescrevemos o método citado acima (AddAttributesToRender), e renderizamos as duas proriedades e algumas outras:

protected override void AddAttributesToRender(HtmlTextWriter writer)
        {
            
            base.AddAttributesToRender(writer);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "evaluationfunction", "AsyncValidatorEvaluateIsValid", false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "isvalidating", "0", false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "myclientid", this.ClientID, false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "resultado", "true", false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "servervalidationmethod", ServerValidationMethod, false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "customvaluefunction", CustomValueFunction, false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "ultimovalor", "", false);
            
        }

A maioria dos atributos renderizados são de uso do JavaScript que utilizaremos para fazer a validação, mostrado abaixo. Apenas um merece uma especial atenção: evaluationfunction.

O atributo evaluationfunction é necessário para todos os Validators do Asp.Net WebForm, este atributo armazena o nome do método JavaScript que realiza a validação no lado do cliente. Os scripts ClientSide do WebForm chamam sempre o método definido neste atributo ao executar a validação ao clicar em um botão de submit.. Em um RequiredFieldValidator por exemplo, este atributo contém o nome do método RequiredFieldValidatorEvaluateIsValid, este é o método que verifica se um controle é preenchido ou não.

No nosso caso, como o exemplo mostra, o nosso método responsável por validar o campo, ou seja, chamar o PageMethod criado pelo usuário do Validator responsável pela validação de fato será o AsyncValidatorEvaluateIsValid.

Nosso código JavaScript deve ficar da seguinte forma:

 function AsyncValidatorEvaluateIsValid(val,resultado,userContext,methodName) {
    
    var value = ValidatorGetValue(val.controltovalidate);
    
    //Possíveis valores para isvalidating:
    //0 : Não está validando e deve ir ao server fazer a validação
    //1 : Está na validação, esperando resposta do server, não deve ser executado nada
    //2 : Retornou da validação, deve ser retornado o valor armazenado no retorno
    
    if(val.isvalidating==1){
        return true;
    }
    
    if(val.isvalidating==2){
        val.isvalidating=0;
        return val.resultado; 
    }
    
      
    if(val.customvaluefunction.length>0){
        var args = { Value:value};  
        eval(val.customvaluefunction + "(val, args);");
        value = args.Value;
    }
       

    if(val.servervalidationmethod.length==0) return true;

    val.isvalidating = 1;
    
    eval('PageMethods.'+val.servervalidationmethod+'(value,function (valorRetorno,userContext,methodName){'+
    'val.resultado = valorRetorno;val.isvalid = valorRetorno;val.isvalidating = 2;val.evaluationfunction(val);'+
    '});');
       
     return true;
 
}

O código acima tem toda a lógica necessária para conseguir chamar o PageMethod definido na propriedade do Validator, aguardar o retorno, e informar se o validator foi válido ou não. A lógica não é das melhores, mas ajuda a exemplificar como deve funcionar um validator.

É importante informar que pelo fato de estarmos utilizando um PageMethod, precisamos adicionar um ScriptManager na página e habilitar a utilização de PageMethods, com a propriedade EnablePageMethods definida como true. Naturalmente, esta é só uma das formas de se validar informações assíncronas, foi a escolhida aqui por ser a mais simples.

Apenas com os códigos acima já podemos dizer que o Validator está pronto, mas você pode me perguntar: “Mas se eu quiser distribuir meu Validator, vou ter que distribuir um JavaScript junto?”. A resposta é :”Só se você quiser”.

Para quem não sabe, no .Net é possível colocar arquivos Embedded em DLLs. Para isso, basta colocarmos a propriedade Build Action do arquivo JavaScript como Embedded Resource, desta forma, ao compilar a DLL, o JavaScript será incluído dentro da DLL.

Para carregarmos este arquivo junto no momento do Load do Validator, apenas precisamos sobrescrever o método OnPreRender do Validator:

	protected override void OnPreRender(EventArgs e)
 {
    base.OnPreRender(e);
    ScriptManager.GetCurrent(this.Page).EnablePageMethods = true;
    Page.ClientScript.RegisterClientScriptResource(this.GetType(),
    "AssyncValidator.AsyncValidator.js");

 }

O uso do Validator é como o de qualquer outro, e pode ser usado com ValidatorCallout por exemplo.

Espero que este post possa ajudar você em validações com Web Forms e Ajax.

Até o próximo.

18. agosto 2010 00:50 by Frederico | Comments (1) | Permalink

Utilizando Asp.Net Validator com controles HTML    

Nas postagens deste Blog vou falar muito de Asp.Net, principalmente com uso de Ajax. Vou falar bastante de Jquery e outras tecnologias Client Side. Com isso, vou ter o habito de não usar Asp.Net Server Controls.

Quando se usa muito Java Script, é normal que se pare de usar Server Control progressivamente, pois assim fica mais fácil acessar IDs dos controles com JQuery, por exemplo. Naturalmente, com o Asp.Net 4, muitos desses problemas de IDs são solucionados com a nova propriedade ClientIDMode, que controla como o servidor gerará os IDs, porém, se estamos usando muitos códigos no lado do cliente e estamos comunicando com o servidor via Ajax, o melhor é poupar processamento desnecessário do Asp.Net, usando menos controles Server Side, renderizando assim cada vez mais rápida uma página no navegador do usuário.

Contudo, quando paramos de usar controles do Asp.Net, outros problemas surgem no processo de desenvolvimento, por exemplo o uso dos Validators.

Como funciona um Validator?

Não vou entrar no detalhe de como funciona um Validator, apenas o básico para contextualizar este post.

Adicionamos Validator a Página como abaixo:

 

Em um Validator como o acima, o Asp.Net identifica o controle a ser validado (txtNome) utilizando o método FindControl, passando como parâmetro o valor informando na propriedade ControlToValidate. Para um controle se encontrado por este método, ele deve estar na coleção de controles da página, portanto, se o seu controle não for ServerControl, ele não será encontrado, afinal um simples controle HTML não é adicionado à coleção de controles da página.

Além de utilizar o método FindControl, o Asp.Net também utiliza o método ControlPropertiesValid, que tem a função de, além de saber se o controle é retornado pelo FindControl, saber se as propriedades básicas do Validator estão preenchidas, como a própria propriedade ControlToValidate.

Desta forma, os Validators do Asp.Net, da forma que são atualmente, não serviriam para validar controles de HTML simples, devido a estes dois métodos citados acima funcionarem apenas com ServerControls.

Temos que admitir que os controles de validação do WebForm são de grande ajuda, principalmente se estamos acostumados a utilizar o controle ValidatorCallout do AjaxToolkit, e seria muito interessante que fosse possível utilizar essas facilidades com HTML simples, e assim unir o melhor dos dois mundos (Client e Serve Side).

Quando um Validator é renderizado no HTML, é criado uma variável no JavaScript, com as propriedade que armazenam o ID do controle que deve ser validado e qual função chamar, mais ou menos assim:

var MainContent_reqNome = document.all ? document.all["MainContent_reqNome"] : document.getElementById("MainContent_reqNome");
MainContent_reqNome.controltovalidate = "MainContent_txtNome";
MainContent_reqNome.errormessage = "Preencha o Nome";
MainContent_reqNome.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
MainContent_reqNome.initialvalue = "";

 

Perceba que ele retorna na propriedade controltovalidate da variável JavaScript o valor da propriedade ClientID do controle a ser validado, esse ID pode ser alterado, como dito acima, pela propriedade ClientIDMode

Se testarmos um Validator com um controle HTML, veremos que o ASP.Net retornará a famosa tela amarela de erro, por não conseguir encontrar o controle e, logo, não ter o valor da propriedade ClientID , como abaixo:

Repare que o controle txtNome agora é um simple INPUT do tipo TEXT, ao executarmos recebemos como resposta a seguinte tela:

Ou seja, como o próprio erro diz, não foi possível encontrar o controle com o ID txtNome, pelos motivos explicados acima.

Solução

Para solucionarmos o problema acima, a única coisa que precisamos fazer é alterar os dois métodos citados acima, para que eles consigam “validar” controles Client Side, além de criar uma propriedade para que seja possível identificar se a validação deve ser feita em um controle ServerSide ou ClientSide.

Para alterar este método, devemos herdar das classes dos Validators e realizar um override dos dois métodos. Abaixo segue um exemplo da alteração para o RequiredValidator, mas a alteração para todos é idêntica, inclusive, a solução para os demais pode ser o simples Contrl+V e Control+C desta, só mudando a classe base, lógico.

Segue o código:

using System.Web.UI;
using System.Web;
namespace Validators
{
    public class RequiredFieldValidator : System.Web.UI.WebControls.RequiredFieldValidator
    {
        /// <summary>
        /// Caso esta propriedade seja True, o controle validará simples controles HTML.
        /// </summary>
        public bool ValidateHTMLControl
        {            get;            set;        }
 
        protected override bool ControlPropertiesValid()
        {
            //Verifico se é um controle HTML
            if (!ValidateHTMLControl)
            {
                return base.ControlPropertiesValid();
            }
            else<
            {
                string controlToValidate = this.ControlToValidate;
                if (controlToValidate.Length == 0)
                {
                    throw new HttpException("A propriedade ControlToValidate é obrigatória.");
                }
                return true;
            }
        }

        public override Control FindControl(string id)
        {
            Control control = this.FindControl(id, 0);

            if (ValidateHTMLControl)
            {
                if (control == null)
                {

                    control = new Control();
                    control.ID = id;

                }
            }
            return control;
        }
    }
}

Para validar um controle HTML é necessário definir a propriedade criada como True:

 Perceba que não precisei criar uma nova tagPrefix (como cc1), isso porque alterei o controle que o Asp.Net deve utilizar como asp:RequriedFieldValidator no Web.Config.

Agora qualquer chamada no meu site para este Validator vai chamar o RequiredFieldValidator customizado, que já tem a inteligência de saber diferenciar se deve validar um ServerControl ou um ClientControl, ou seja, mantém a compatibilidade com outras páginas já criadas. A configuração é o seguinte:

 

Perceba que estou realizando o mapeamento de um controle para outro, e essa estratégia pode ser utilizada para qualquer controle Asp.Net, como um GridView, se você quiser substituir todas as referências de um controle no site para outro controle, sem ter a necessidade de mudar o HTML de cada página, adicionando um prefixo cc1, por exemplo, que aponte para o seu controle.

Atenção

Como alteramos o Validator para funcionar com HTML comum, quando estivermos validando um controle HTML, a validação do lado do Server não funcionará, visto que não terá referência no lado servidor para este controle, o que pode levantar erro. Portanto, essa abordagem só é interessante quando estamos usando Ajax para comunicação com o Server na página, sem PostBack.

Conclusão

Como cada vez mais as páginas estão usando tecnologias ClientSide, muitas vezes precisamos conseguir continuar utilizando controles já comuns em outras páginas, para padrões visuais por exemplo, ou mesmo porque não conseguimos aprender ainda outras formas de validações ClientSide, como os próprios plugins do JQuery. Então devemos saber que é possível customizar controles padrões do Asp.Net para mantermos nossa aplicação funcionando, e aos poucos irmos introduzindo novidades tecnológicas do mercado.

 

Espero que este artigo possa ajudas as pessoas que ainda utilizam WebForms, e não pensam em mudar para o padrão MVC do Asp.Net.

Se você gostou ou não deste post, comente, me deixe saber sua opinião!

Abraços,

Frederico

 

8. agosto 2010 07:33 by Frederico | Comments (0) | Permalink

Sobre mim

Minha Imagem

Meu nome é Frederico Batista Emídio, trabalho com desenvolvimento de sistemas profissionalmente a oito anos, porém, já faço sites pessoais a pelo menos dez anos.

Para saber mais clique aqui.

Páginas

Calendário

<<  novembro 2017  >>
seteququsedo
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

Visualizar posts em um Calendário
Sigua @fredemidio

MCP Asp.NET