Criando Controles de Servidor Personalizados com Validação

Neste artigo vamos utilizar as práticas descritas no artigo "Iniciando Expressões Regulares" para criação de controles de servidor personalizados com validação.

Uma boa prática no desenvolvimento de sistemas é o tratamento de erros. Você já deve ter desenvolvido uma página com um controle TextBox, por exemplo, que inclusive estava com uma boa formatação e podia receber somente números, e derrepente quando uma outra pessoa - usuário final - foi utilizar gerou uma mensagem de erro? Você pode até arrastar um controle de validação para o formulário... uma vez... duas vezes... e mais... e mais... até você querer abreviar este trabalho criando um controle personalizado que possua controles de validação para tipos predefinidos, mensagens de erro personalizadas e outras coisas mais..

Um controle de servidor do ASP.NET renderiza (constroe) marcações, assim como script do lado cliente.

Controles personalizados de servidor podem ser criados a partir da herança de um controle existente ou herdado diretamentamente da classe base.

Devemos estar atentos quanto a reutilização do controle que iremos criar. Se deseja usar seu controle personalizado em múltiplos Web sites então desenvolva sua classe em um projeto do tipo Class Library para gerar uma .dll, que neste caso poderá ser compartilhada. Se desejar usar seu controle somente em sua página corrente da Web então adicione sua classe em seu Web site.

Podemos facilmente criar uma herança de um controle existente e adicionarmos mais propriedades, métodos e eventos. Pode-se ainda, sobreescrever métodos e propriedades para apresentar diferentes comportamentos.

Vamos praticar...

  1. Crie um projeto do tipo Class Library (sugestão nome CustomControls);
  2. Adicione uma class ao seu projeto ou utilize a classe gerada dinamicamente (sugestão nome CustomTextBox);
  3. Importe os seguintes namespaces: System.Web.UI; System.Web.UI.WebControls; System.Text.RegularExpressions;
  4. Sua classe deve herdar a classe Control e INamingContainer;
  5. Crie no mesmo namespace um enumerador (enum) com os tipos que serão predefinidos para validação (sugestão nome TypeValidation);
  6. Propriedade ValueType tipo TypeValidation (tipo predefinido);
  7. Crie um objeto tipo RegularExpressionValidator com acesso as propriedades: ValidationExpression tipo String (expressão de validação), ErrorMessage tipo String (mensagem de erro), Display tipo ValidatorDisplay (manipula apresentação do validator) ...;
  8. Crie um objeto tipo TextBox com acesso as propriedades: Text tipo String (Text do controle), override ID tipo String (ID do controle), Width tipo Unit (largura do controle);
  9. Crie uma propriedade do tipo booleana para verificar o preenchimento obrigatorio (sugestão nome IsNotNull);
  10. Sobreescreva o método CreateChildControls com chamada a três métodos que você irá implementar, a saber: CreateTextBox (manipular o controle TextBox), CreateRegularExpressionValidator (manipular o controle RegularExpressionValidator - faz validação baseado em expressões regulares), CreateRequiredFieldValidator (manipular o controle RequiredFieldValidator - resposável por verificar os campos obrigatórios, ou melhor, que obrigatoriamente devem estar preenchidos) e CreateSummary (manipular o controle SummaryValidator - resposável pela apresentação de mensagens);
Recursos Principais
Control Classe base que compartilha propriedades, métodos e eventos para todos os controles de servidor no ASP.NET. A classe Control descreve a classe base para controles, que são componentes com representação visual. Control é a primeira classe que você deriva quando está desenvolvendo controles personalizados no ASP.NET.
INamingContainer Interface que identifica um controle container que cria um novo namespace em um objeto Page hierarquicamente.
CreateChildControls Cria controles filhos.

O objetivo deste componente é trabalhar com expressões regulares predefinidas ou não (RegularExpressionValidator) e validação de campos obrigatorios (RequiredFieldValidator), lembrando que não será necessário nenhum esforço em relação a arrastar o ValidationSummary para sua página para apresentar as mensagens..., pois o código também faz isso dinamicamente. Confira o código abaixo:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
 
namespace CustomControls
{
   /// <summary>
   /// Tipos Predefinidos
   /// </summary>
   public enum TypeExpression : short
   {
      None, Digito, Decimal, Moeda, Email, Telefone
   }
 
   /// <summary>
   /// Classe que apresenta controle TextBox com controle de validação
   /// </summary>
   public class CustomTextBox : Control, INamingContainer
   {
 
       private TypeExpression _valueType;
 
       /// <summary>
       /// Tipos predefinidos
       /// </summary>
       public TypeExpression ValueType
       {
            get { return _valueType; }
            set { _valueType = value; }
       }
 
       /// <summary>
       /// Controle de Validação
       /// </summary>
       RegularExpressionValidator _validator = new RegularExpressionValidator();
 
       /// <summary>
       /// Expressão de Validação
       /// </summary>
       public String ValidationExpression
       {
            get { return _validator.ValidationExpression; }
            set { _validator.ValidationExpression = value; }
        }
 
       /// <summary>
       /// Mensagem de Erro
       /// </summary>
       public String ErrorMessage
       {
            get { return _validator.ErrorMessage; }
            set { _validator.ErrorMessage = value; }
       }
 
       /// <summary>
       /// Manipula apresentação do validator
       /// </summary>
       public ValidatorDisplay  Display
       {
            get { return _validator.Display; }
            set { _validator.Display = value; }
       }
 
       /// <summary>
       /// Controle TextBox
       /// </summary>
       TextBox _textBox = new TextBox();
 
       /// <summary>
       /// Propriedade Text do controle
       /// </summary>
       public String Text
       {
            get { return _textBox.Text; }
            set { _textBox.Text = value; }
       }
 
       /// <summary>
       /// Propriedade ID do controle
       /// </summary>
       public override String ID
       {
          get { return _textBox.ID; }
            set { _textBox.ID = value; }
       }
 
       /// <summary>
       /// Largura do Controle
       /// </summary>
       public Unit Width
       {
            get { return _textBox.Width; }
            set { _textBox.Width = value; }
       }
 
       private Boolean _isNull;
 
       /// <summary>
       /// Preenchimento obrigatorio
       /// </summary>
       public Boolean IsNotNull
       {
            get { return _isNull; }
            set { _isNull = value; }
       }
 
       /// <summary>
       /// Cria controles filhos
       /// </summary>
       protected override void CreateChildControls()
       {
            // manipula TextBox
            CreateTextBox();
 
            // manipula RegularExpressionValidator
            CreateRegularExpressionValidator();
 
            // manipula RequiredFieldValidator
            CreateRequiredFieldValidator();
 
            // manipula SummaryValidator
            CreateSummary();
       }
 
       /// <summary>
       /// Monta TextBox e adiciona controle
       /// </summary>
       private void CreateTextBox()
       {
            // mude a aparência do controle, etc. aqui
            // ex.: _textBox.CssClass = "meuCss";
           
            // adiciona controle TextBox
            Controls.Add(_textBox);
       }
 
       /// <summary>
       /// Monta expressão com o tipo predefinido e adiciona (se expressão informada)
       /// </summary>
       private void CreateRegularExpressionValidator()
       {
            // fixa o controle que será validado
            _validator.ControlToValidate = _textBox.ID;
 
            // monta expressão
            if (String.IsNullOrEmpty(this.ValidationExpression))
            {
                // verifica tipo predefinido
                switch (this.ValueType)
                {
                    case TypeExpression.Digito:
                        if (string.IsNullOrEmpty(this.ErrorMessage))
                            this.ErrorMessage = "Somente dígitos.";
                        this.ValidationExpression = @"^\d+$";
                        break;
                    case TypeExpression.Decimal:
                        if (string.IsNullOrEmpty(this.ErrorMessage))
                            this.ErrorMessage = "Formato decimal inválido.";
                        this.ValidationExpression = @"^[+-]?((\d+|\d{1,3}(\.\d{3})+)(\,\d*)?|\,\d+)$";
                        break;
                    case TypeExpression.Moeda:
                        if (string.IsNullOrEmpty(this.ErrorMessage))
                            this.ErrorMessage = "Formato moeda inválido.";
                        this.ValidationExpression = @"^\d{1,3}(\.\d{3})*\,\d{2}$";
                        break;
                    case TypeExpression.Email:
                        if (string.IsNullOrEmpty(this.ErrorMessage))
                            this.ErrorMessage = "Formato e-mail inválido.";
                        this.ValidationExpression = @"^([\w\-]+\.)*[\w\- ]+@([\w\- ]+\.)+([\w\-]{2,3})$";
                        break;
                    case TypeExpression.Telefone:
                        if (string.IsNullOrEmpty(this.ErrorMessage))
                            this.ErrorMessage = "Formato telefone inválido.";
                        this.ValidationExpression = @"^\(\d{3}\)-\d{4}-\d{4}$";
                        break;
                }
            }
 
            // adiciona controle (se conter expressão)
            if(!String.IsNullOrEmpty(this.ValidationExpression))
                Controls.Add(_validator);
       }
 
       /// <summary>
       /// Monta RequiredFieldValidator e adiciona (se campo obrigatorio)
       /// </summary>
       private void CreateRequiredFieldValidator()
       {
            // se campo obrigatorio
            if (this.IsNotNull)
            {
               // cria RequiredFieldValidator e fixa propriedades
                RequiredFieldValidator _required = new RequiredFieldValidator();
                _required.ControlToValidate = this.ID;
                // ** sugestão: crie uma propriedade para esta
                // mensagem de erro
                _required.ErrorMessage = String.Empty;
                _required.Text = "* obrigatorio";
                _required.Display = ValidatorDisplay.Dynamic;
                _required.ForeColor = System.Drawing.Color.Red;
               
                // adiciona controle
                Controls.Add(_required);
            }
       }
 
       /// <summary>
       /// Monta ValidationSummary e adiciona (se possuir validação)
       /// </summary>
       private void CreateSummary()
       {
            // verifica as propriedades obrigatorias
            if (!String.IsNullOrEmpty(this.ValidationExpression) || this.IsNotNull)
            {
                // contador de verificação dos controles CustomTextBox
                Int16 count = 0;
                // percorre todos os controles da página
                foreach (Control _control in Page.Form.Controls)
                {
                    // se controle for do tipo da classe
                    // personalizada que estamos desenvolvendo
                    if (_control.GetType() == typeof(CustomTextBox))
                    {
                        // se o objeto está sendo acessado pela
                        // primeira vez
                        if (count == 0)
                            // se o controle encontrado é o controle que
                            // provocou a chamada
                            if (_control.ID == this.ID)
                            {
                                // cria Summary e fixa propriedades
                                ValidationSummary _summary = new ValidationSummary();
                                // ** sugestão: preencha esta propriedade
                                // para mensagem comuns
                                //_summary.HeaderText = "Verifique os
                                // campos Obrigatórios.";
                                _summary.ShowMessageBox = true;
                                _summary.ShowSummary = false;
                                _summary.ID = "vsTextBox";
                                _summary.DisplayMode = ValidationSummaryDisplayMode.List;
 
                                // adiciona controles
                                Controls.Add(_summary);
                            }
                        // incrementa à quantidade de controles do
                        // tipo CustomTextBox encontrados
                        count++;
                    }
                }
            }
       }
   }
}

É aconselhável que você adquira a prática de fazer comentários interno e externo, pois facilita na manutenção. Reaproveitamento de código também é bastante relevante, por exemplo, os pontos semelhantes de execução é interessante unir em um só método visando reutilização de código - citamos o código acima que ao invés de criarmos métodos manipulando os tipos predefinidos, implementamos um enumerador bem simples.

Desenvolva o componente sempre pensando em padronização. Por exemplo, verifique se a mensagem de erro do controle de validação será estática, dinâmica ou sem visualização, aparência do controle, etc. e implemente com valores fixos no método CreateChildControls.

Lembre-se que as únicas propriedades que aparecerão serão as da classe Control juntamente com as que foram criadas, por exemplo, a propriedade Row ou TextMode que é específica do controle TextBox não aparecerão, a não ser que você crie essa propriedade e manipule conforme fizemos, por exemplo, com a propriedade ValidationExpression, que é específica do controle RegularExpressionValidator. Dessa forma, sugiro implementar as propriedades MaxLength (TextBox), ErrorMessage (RequiredFieldValidator) e etc...

Adicione a referência do seu componente no projeto Web. Arraste o controle CustomTextBox para sua página ou então faça:

< %@ Page Language="VB" AutoEventWireup="false" CodeFile="Validation.aspx.vb" Inherits="Default5" %>
 
< %@ Register Assembly="CustomControls" Namespace="CustomControls" TagPrefix="cc1" %>
 
< !DOCTYPE html PUBLIC " -//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
 
< html xmlns="http://www.w3.org/1999/xhtml" >
< head runat="server" >
   < title>Untitled Page< /title>
< /head>
< body>
   < form id="form1" runat="server" >
   < div>
       < !-- campo obrigatorio -->
       < cc1:CustomTextBox ID="CustomTextBox1" runat="server" IsNotNull="true" />< br />
       < !-- campo obrigatorio e somente dígito -->
       < cc1:CustomTextBox ID="CustomTextBox2" runat="server" ValueType="Digito" Display="None" IsNotNull="true" />< br />
       < !-- campo não obrigatorio com validaç ão de e-mail e mensagem de erro externo -->
       < cc1:CustomTextBox ID="CustomTextBox3" runat="server" ValueType="Email" Display="Dynamic" ErrorMessage="E-mail inválido" />< br />
       < !-- campo não obrigatorio com validaç ão de telefone e mensagem de erro interna -->
       < cc1:CustomTextBox ID="CustomTextBox4" runat="server" ValueType="Telefone" Display="Static" />< br />
       < asp:Button ID="Button1" runat="server" Text="Button" />
   < /div>
   < /form>
< /body>
< /html>

O mérito da criatividade é seu. Você pode criar várias outras coisas utilizando este simples exemplo. Tente desenvolver este mesmo componente utilizando os tipos predefinidos do controle RangeValidator.

Referências:
MCTS Self-Paced Training Kit (Exam 70-536): Microsoft .NET Framework 2.0 Application Development Foundation / byTony Northrup, Shawn WildermuthandBill Ryan;
http://msdn2.microsoft.com/en-us/library/system.web.ui.control.aspx;
http://msdn2.microsoft.com/en-us/library/system.web.ui.control.createchildcontrols(VS.71).aspx;
http://msdn2.microsoft.com/en-us/library/system.web.ui.control.onprerender(VS.71).aspx;
http://msdn2.microsoft.com/en-us/library/system.web.ui.ivalidator_members.aspx;