[ASP.NET 自訂控制項]TextBox動態加入RequiredFieldValidator與CustomValidator

  • 26320
  • 0
  • 2009-01-08

[ASP.NET 自訂控制項]TextBox動態加入RequiredFieldValidator與CustomValidator

首先相當相當抱歉,上次發佈的那一篇「[ASP.NET 自訂控制項]如何在TextBox裡動態新增Validator」太草率了,

在今天練習將設計元件的語法從VB.NET改成用C#的時候,突然發現我怎麼犯了這麼大的錯。

我的天啊,不知道誤導了多少人。(雖然沒人回應 ),但是有幾位大大的推薦跟收藏,讓我發現這大trouble的時候,膽顫心驚。

 

原本那篇文章最大的問題,就在繼承的是TextBox,繼承TextBox是無法在PreRender的時候,動態加入Validator的。

因為TextBox不是「容器」,應該要繼承CompositeControl或WebControl才對。


 Download source codeCustomLabel.rar


以下為擴充完多功能的說明:

前言:

ASP.NET裡面,有許多內建的Validator很好用,例如RequiredFieldValidator、RangeValidator、RegularExpressionValidator、CompareValidator、CustomValidator等…

Validator就如同AJAX toolkit controls的Extender一般,可以直接將「驗證」的javascript,掛在指定的control上,只需透過屬性來設定,期望達到一行js都不需要寫。

這樣的確在設計上相當有彈性,但面對大量開發類似的驗證規則,或者會動態修改規則的。

例如在某情況下,需要驗證必KEY,其他情況不用。

例如當畫面上DropDownList值為「個人」時,要驗證ID符合「身份證字號規則」。值為「企業」時,要驗證ID符合「統一編號規則」。

這種動態的驗證情況下,往往最笨的方法也是最常見到的方法,就是拉兩組control與validator來用,再來控制顯示隱藏。

但這樣的方法,真的很笨,也沒效率。

所以這邊介紹一個另外的作法,就是透過屬性來控制Validator的產生。

 

Step1:

新增一個類別庫專案,名稱假設叫做Joey,新增一個類別,叫做JoeyTextBox,還有要加入相關參考,請參照using的部分。

Step2:

自訂一組列舉型別叫做TextDataType,用來給外界設定要驗證的規則為何。這邊我們舉的例子是驗證身份證字號(包括外國人)與統一編號。

using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Drawing;
namespace Joey
{

    public class JoeyTextBox : System.Web.UI.WebControls.CompositeControl  
    {
        #region "global variable"
        
            private bool _blnRequired;
            private string _strMessageParaMeter;
            private ValidatorCollection _colValidatorCollection;
            private ValidatorDisplay _validatorDisplay;
            private TextBox txtEdit = new TextBox();
            private TextDataType _DataType;
 
            public enum TextDataType
            {
                NoSet,
                CustID,
                CompID
            }

        #endregion
    }
}

 

 

 

 

 

Step3:

覆寫CreateChildControls(),把TextBox加進來,並且註冊TextChange事件。

            /// <summary>
            /// 由 ASP.NET 網頁架構呼叫,通知採用複合架構實作的伺服器控制項,去建立其包含的任何子控制項以備傳回或呈現。
            /// </summary>
            protected override void CreateChildControls()
            {
                this.Controls.Clear();
                this.txtEdit.ID = "EditText";
                this.Controls.Add(this.txtEdit);
                this.txtEdit.TextChanged += EditBoxTextChanged;
            }

 

 

 

 

 

        #region Event

            public event EventHandler TextChanged;
            /// <summary>
            /// When TextBox.text onchanged, raise TextChange event
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
            private void EditBoxTextChanged(object sender, System.EventArgs e)
            {
                if (TextChanged != null)
                {
                    TextChanged(sender, e);
                }
            }        

        #endregion
 
Step4:
Public TextBox與驗證相關屬性出來供外界設定。
#region Property

            /// <summary>
            /// Gets or sets the type of the data.
            /// </summary>
            /// <value>The type of the data.</value>
            public TextDataType DataType
            {
                get { return _DataType; }
                set { _DataType = value; }
            }

            /// <summary>
            /// Gets or sets a value indicating whether this <see cref="JoeyTextBox"/> is required.
            /// </summary>
            /// <value><c>true</c> if required; otherwise, <c>false</c>.</value>
            public bool Required
            {
                get { return _blnRequired; }
                set { _blnRequired = value; }
            }

            /// <summary>
            /// Gets or sets the message parameter.
            /// </summary>
            /// <value>The message parameter.</value>
            public string MessageParameter
            {
                get { return _strMessageParaMeter; }
                set { _strMessageParaMeter = value; }
            }

            /// <summary>
            /// Gets the validators.
            /// </summary>
            /// <value>The validators.</value>
            protected ValidatorCollection Validators
            {
                get
                {
                    if (_colValidatorCollection == null)
                    {
                        _colValidatorCollection = new ValidatorCollection();
                    }
                    return _colValidatorCollection;
                }

            }


            /// <summary>
            /// Gets or sets the display.
            /// </summary>
            /// <value>The display.</value>
            public ValidatorDisplay Display
            {
                get
                {
                    return _validatorDisplay;
                }
                set
                { _validatorDisplay = value; }

            }

            /// <summary>
            /// Gets or sets the validation group.
            /// </summary>
            /// <value>The validation group.</value>
            public string ValidationGroup
            {
                get
                {
                    return this.txtEdit.ValidationGroup;
                }
                set
                { this.txtEdit.ValidationGroup = value; }

            }



            public bool AutoPostBack
            {
                get
                {
                    return this.txtEdit.AutoPostBack;
                }
                set
                { this.txtEdit.AutoPostBack = value; }

            }

            /// <summary>
            /// Gets or sets the actual textbox.
            /// </summary>
            /// <value>The actual textbox.</value>
            public TextBox mTextBox
            {
                get { return this.txtEdit; }
                set { this.txtEdit = value; }
            }

            #endregion

 

 

 

 

 
Step5:
練習用jeff大大省ViewState的方法,所以要覆寫SaveViewState()與LoadViewState()
            /// <summary>
            /// 儲存自頁面回傳至伺服器以來文字方塊檢視狀態的變更。
            /// </summary>
            /// <returns>
            ///     <see cref="T:System.Object"/>,包含 <see cref="T:System.Web.UI.WebControls.TextBox"/> 檢視狀態的變更。如果沒有與物件相關聯的檢視狀態,則這個方法會傳回 null。
            /// </returns>
            protected override object SaveViewState()
            {
                object baseState = base.SaveViewState();
                object[] myState = new object[2];
                myState[0] = baseState;
                myState[1] = _blnRequired;
                return myState;
            }

            /// <summary>
            /// 從先前由 <see cref="M:System.Web.UI.WebControls.WebControl.SaveViewState"/> 方法所儲存的網頁要求還原檢視狀態資訊。
            /// </summary>
            /// <param name="savedState">物件,表示要還原的控制項狀態。</param>
            protected override void LoadViewState(object savedState)
            {
                if ((savedState != null))
                {
                    // Load State from the array of objects that was saved at vedViewState.
                    object[] myState = (object[])savedState;

                    if ((myState[0] != null))
                    {
                        base.LoadViewState(myState[0]);
                    }

                    if ((myState[1] != null))
                    {
                        _blnRequired = (bool)myState[1];
                    }
                }
            }

 

 

 

 

 
Step6:
撰寫要嵌入的Validator Method。
當Required設定為True時,加上RequiredFieldValidator。
根據DataType判斷,要加上驗證身份證字號或是統一編號的CustomValidator。
        #region Private Method
            /// <summary>
            /// Adds the validator.
            /// </summary>
            private void AddValidator()
            {

                if (this.Required)
                {
                    AddRequiredValidator();
                }

                switch (this.DataType)
                {
                    case TextDataType.NoSet:
                        break;
                    case TextDataType.CustID:
                        this._AddCustIDCheckValidator();
                        break;
                    case TextDataType.CompID:
                        this._AddCompIDCheckValidator();
                        break;
                    default:
                        break;
                }
            }


            /// <summary>
            /// Adds the required validator.
            /// </summary>
            private void AddRequiredValidator()
            {
                RequiredFieldValidator objRequiredValidator = new RequiredFieldValidator();
                objRequiredValidator.ErrorMessage = string.Format(Resources.Message._000001, _strMessageParaMeter);
                objRequiredValidator.ControlToValidate = this.txtEdit.ID;
                objRequiredValidator.Display = this.Display;
                objRequiredValidator.ValidationGroup = this.ValidationGroup;
                objRequiredValidator.SetFocusOnError = true;
                this.Validators.Add(objRequiredValidator);
            }

            /// <summary>
            /// add custID check validator.
            /// </summary>
            private void _AddCustIDCheckValidator()
            {            
                Page.ClientScript.RegisterClientScriptInclude("CheckCustID", "CheckCustID.js");

                CustomValidator objCustomValidator = new CustomValidator();
                objCustomValidator.EnableViewState = false;
                objCustomValidator.ControlToValidate = this.txtEdit.ID;
                //要呼叫的自訂驗證JS function
                objCustomValidator.ClientValidationFunction = "__ATValidateIDNO";
                objCustomValidator.ErrorMessage = string.Format(Resources.Message._000002, _strMessageParaMeter);
                objCustomValidator.Display = this.Display;
                objCustomValidator.ValidationGroup = this.ValidationGroup;
                //驗證非法時,需要set focus則設定為True
                objCustomValidator.SetFocusOnError = true;
                this.Validators.Add(objCustomValidator);
            }

            /// <summary>
            /// add compID check validator.
            /// </summary>
            private void _AddCompIDCheckValidator()
            {
                Page.ClientScript.RegisterClientScriptInclude("CheckCompID", "CheckCompID.js");

                CustomValidator objCustomValidator = new CustomValidator();
                objCustomValidator.EnableViewState = false;
                objCustomValidator.ControlToValidate = this.txtEdit.ID;
                //要呼叫的自訂驗證JS function
                objCustomValidator.ClientValidationFunction = "_CheckCOMPID";
                objCustomValidator.ErrorMessage = string.Format(Resources.Message._00003, _strMessageParaMeter);
                objCustomValidator.Display = this.Display;
                objCustomValidator.ValidationGroup = this.ValidationGroup;
                //驗證非法時,需要set focus則設定為True
                objCustomValidator.SetFocusOnError = true;
                this.Validators.Add(objCustomValidator);
            }
        #endregion

 

 

 

 

Step7:
最後覆寫OnPreRender()。
值得一提的是,這邊當AutoPostBack設定為True時,當自訂驗證失敗時,我們要額外去擋PostBack的事件,這也是上一篇沒提到的。
            /// <summary>
            /// 如果 <see cref="P:System.Web.UI.WebControls.TextBox.AutoPostBack"/> 為 true,則在用戶端上呈現前,會先註冊產生回傳事件的用戶端指令碼。
            /// </summary>
            /// <param name="e"><see cref="T:System.EventArgs"/>,包含事件資料。</param>
            protected override void OnPreRender(EventArgs e)
            {
                base.OnPreRender(e);

                AddValidator();

                for (int i = 0; i < this.Validators.Count; i++)
                {
                    this.Controls.Add((System.Web.UI.Control)this.Validators[i]);
                }

                if (this.AutoPostBack)
                {
                    
                    Page.ClientScript.RegisterClientScriptInclude("JoeyTextBoxValidator", "JoeyTextBoxValidator.js");
                    this.txtEdit.Attributes.Add("onchange", "return IsValidCanPostback(event);");
                }
            }

 

 

 

 

Step8:
在類別庫裡,新增一個資料夾叫做Resources,新增一個資源檔叫做Message.resx。
輸入自訂的驗證錯誤訊息該如何顯示。
MessageResource 
Message 
Step9:

 

 

 

 

 

沒錯,就是撰寫javascript,首先新增一個javascript叫做JoeyTextBoxValidator.js,裡面放著公用的function,目前是IsValidCanPostback()。

function IsValidCanPostback(event){    
    var objElement;
    //判斷IE或FireFox
    if (event.target) {
        if (event.currentTarget && (event.currentTarget != event.target)) {
            objElement= event.currentTarget;} 
        else {
            objElement = event.target;} 
    } 
    else {
    objElement = event.srcElement;}
    
    var strEventTarget=event.srcElement.name.replace(/:/g,"$");
    
    if (typeof(Page_ValidationVer) == "undefined"){
        __doPostBack(strEventTarget,"");
    }
    if(typeof(Page_Validators)!= "undefined"){
        for(var i=0;i<Page_Validators.length;i++){
            if(Page_Validators[i].controltovalidate==objElement.id){
                if(!Page_Validators[i].isvalid) return false;
            }
        }
    }

    __doPostBack(strEventTarget,"");
}

 

 

 

 

 

Step10:

新增一個javascript檔,叫做CheckCompID.js,裡面放用來驗證統一編號的function,這邊是入口點是_CheckCOMPID()。

function _CheckCOMPID(source, arguments){var objCtl = document.getElementById(source.controltovalidate);
var strInputValue=objCtl.value;

    if (strInputValue.length==8){
        var arrCOMPID = new Array();
        var arrCOMPID_Sum = new Array();
        arrCOMPID_Sum.length=8;
        arrCOMPID.length=8;
        for(i = 0 ; i < arrCOMPID.length ; i++){
            arrCOMPID[i]=strInputValue.substr(i,1);
            var subresult = 0;
            switch(i){        
            case 0:arrCOMPID_Sum[i]=_AddNumStr(arrCOMPID[i]*1);break;
            case 1:arrCOMPID_Sum[i]=_AddNumStr(arrCOMPID[i]*2);break;
            case 2:arrCOMPID_Sum[i]=_AddNumStr(arrCOMPID[i]*1);break;
            case 3:arrCOMPID_Sum[i]=_AddNumStr(arrCOMPID[i]*2);break;
            case 4:arrCOMPID_Sum[i]=_AddNumStr(arrCOMPID[i]*1);break;
            case 5:arrCOMPID_Sum[i]=_AddNumStr(arrCOMPID[i]*2);break;
            case 6:if (arrCOMPID[6]==7){
                    arrCOMPID_Sum[i]=0;break;
                    }
                   else{
                    arrCOMPID_Sum[i]=_AddNumStr(arrCOMPID[i]*4);break;
                    }
            case 7:arrCOMPID_Sum[i]=_AddNumStr(arrCOMPID[i]*1);break;
            }        
        }
        
        var ResultValue = 0;
        for(i = 0 ; i < arrCOMPID_Sum.length ; i++){
            ResultValue += arrCOMPID_Sum[i];        
        }       
            
        if (ResultValue%10==0){
        arguments.IsValid=true;    
        }
        else{
        
            if (arrCOMPID[6]==7){            
                if ((ResultValue+1)%10==0){    
                arguments.IsValid=true;    
                }
                else{
                arguments.IsValid=false;
                }                    
            }
            else{
                        arguments.IsValid=false;
            }        
        }
    }
    else{    
    arguments.IsValid=false;
    }        
}        
    
function _AddNumStr(intArrayElem){

    var strResult=0;
    if (intArrayElem >9){
        intArrayElem=intArrayElem.toString();
        strResult=intArrayElem.substr(0,1)*1+ intArrayElem.substr(1,1)*1;        
    }
    else{
        strResult=intArrayElem;
    }
    return strResult;
}

 

 

 

 

 

Step11:

新增一個javascript檔,叫做CheckCustID.js,用來驗證身份證字號(包括外國人)的function,這邊入口點為__ATValidateIDNO()

function  __ATValidateIDNO(source, arguments){
    var objCtl = document.getElementById(source.controltovalidate);
    
    var strValue=objCtl.value;
    
    var strFst,strInt,strTwo;
    var strFstWd,strIntAll,strInt;
    var intANS=0;    
    var blnRtn=true;
    strFst = 'ABCDEFGHJKLMNPQRSTUVXYWZIO';
    strInt = '19876543211';
    strTwo = '12';
     
    strFstWd = strValue.substr(0,1).toUpperCase();

    var intFst = parseInt(strFst.indexOf(strFstWd.substr(0,1)),10) + 10;
    
    if ((blnRtn) && (strValue.length!=10)){blnRtn=false;}
    if ((blnRtn) && (intFst==9)){blnRtn=false;}
    if ((blnRtn) && (strTwo.indexOf(strValue.substr(1,1))==-1)){blnRtn=false;}
    if(blnRtn){
        strIntAll = intFst + strValue.substring(1,10); 
        for (var i=0;i<11;i++){
            intANS += (parseInt(strInt.substr(i,1),10) * parseInt(strIntAll.substr(i,1),10));    
        }    
        if (intANS % 10 != 0){blnRtn=false;}
    }    
    
    if (!blnRtn){
    blnRtn=gChkForeignID(strValue);    
    }
         
    if((!blnRtn)&&(strValue!='')){
        arguments.IsValid = false;
    }
    else{
        arguments.IsValid = true;}    
}

//==================================================
//功能名稱:Function gChkForeignID
//功能用途:檢查是否外國人ID
//參      數:    strVal 
//==================================================
function gChkForeignID(strCUST_ID){

    if (gIsDigit(strCUST_ID.substring(0,7))== true && strCUST_ID.length >= 10 )//ID前8碼必須為數字,ID長度必須大於10
    {
        var intMonth = new Number(strCUST_ID.substring(4,6))
        var intDay = new Number(strCUST_ID.substring(6,8))
        if (intMonth > 12)    //檢查月份是否合法
            {
            return false;}
        else    
        {
            vntMonthDay = new Array(0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)    //檢查日期是否合法
            if (intDay > vntMonthDay[intMonth])
                {
                return false;}
            else        //檢查後2碼是否為A~Z的文字
            {
                var char_bln=true;
                for (x=0;x<2;x++) {
                    if (x == 0)
                        {input01 = new String(strCUST_ID.substring(8,9).toUpperCase());}    
                    else if (x == 1)
                        {input01 = new String(strCUST_ID.substring(9,10).toUpperCase());}    
                         
                    if (input01.charCodeAt(0) <= 64 || input01.charCodeAt(0) >= 91)
                    {
                      char_bln = false;
                    } 
                }    
                
                if (!char_bln)
                    return false;
                else
                    return true;                

            }            
        }        

    }     
    else
    {    
        if(gChkForeignID_New(strCUST_ID)) {            
            return true;
        }else{            
            return false;
        }        
    }        
}    

//檢查新的外國人統一證號 
// Ex: AA01234563、FA12345689
function gChkForeignID_New( strValue ) {
    var strFst,strInt;
    var strFstWd,strIntAll;
    var intANS=0,i=0;
    var intFst1,intFst2,intNum1,intNum2,intNum3;
    var blnRtn = true;    
    
       
    //檢查身份證格式是否符合 
    var re;                     
    re = /^[A-Za-z]{1}[A-Da-d]{1}\d{8}[A-Za-z0-9]{0,1}$/gi;   
                   
    if( !re.test(strValue) ) {          
        blnRtn = false;
    }else{        
        strFst = 'ABCDEFGHJKLMNPQRSTUVWXYZIO';
        strInt = '19876543211';                            //特定數                
        strFstWd = strValue.toUpperCase();    //字母轉成大寫
        
        intFst1 = parseInt(strFst.indexOf(strFstWd.substr(0,1)),10) + 10;    // 取得第一個字母的對應代碼
        intFst2 = parseInt(strFst.indexOf(strFstWd.substr(1,1)),10) + 10;    // 取得第二個字母的對應代碼
                            
        if( intFst1 > 29 ) {
            intNum1 = 3;
        }else if( intFst1 > 19 ) {
            intNum1 = 2;
        }else{
            intNum1 = 1;
        }        

        intNum2 = intFst1 % 10;
        intNum3 = intFst2 - 10;

        strIntAll = intNum1.toString() + intNum2.toString() + intNum3.toString() + strValue.substring(2,11);   // 字串組合 
        //alert( strIntAll );
        for(i=0;i<=10;i++){
            intANS += (parseInt(strInt.substr(i,1),10) * parseInt(strIntAll.substr(i,1),10));    
        }    
        
        if( intANS % 10 != 0 ){
            blnRtn = false;
        }
    }            
            
    if( !blnRtn ){                        //身份證號有誤
        return false;
    }else{        
        return true;        
    }

}

//==================================================
//功能名稱:Function gIsDigit
//功能用途:檢查是否為數字
//參    數: strVal 
//  
//傳 回 值:
//   True    
//   False  
//==================================================
function gIsDigit(strVal){
   if (strVal=="")
      return false;
   for(var i=0; i<strVal.length; i++) {        
      if (strVal.charCodeAt(i)<48 || strVal.charCodeAt(i)>57 || strVal.charCodeAt(i)==45)
         if (i!=0)
            return false;
         else if (i==0 && strVal.charCodeAt(0)!=45)
            return false;    
    }     
   return true;      
} 

 

 

 

 

 

Step12:

再來就要驗證看看我們的TextBox會不會Work了,建置Joey類別庫,Joey.dll加入網站參考中。

接著在網頁上加入一個JoeyTextBox、一個Button、一個ValidationSummary。

將JoeyTextBox的Required屬性設定為true,DateType設定為CustID,AutoPostBack為True,並加上TextChanged要執行的method。

我們在Button的Click與JoeyTextBox的TextChanged事件上加上程式碼,來辨別是否有PostBack 。

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="Joey" Namespace="Joey" TagPrefix="cc1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        <div>
            <cc1:JoeyTextBox ID="JoeyTextBox1" runat="server" MessageParameter="ID" 
                Required="true" ValidationGroup="" AutoPostBack="True" DataType="CustID" OnTextChanged="JoeyTextBox1_TextChanged1" />
            <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" ValidationGroup="" />
            <asp:ValidationSummary ID="ValidationSummary1" runat="server" ShowMessageBox="True" ShowSummary="False" ValidationGroup="" />
            
        </div>
    </form>
</body>
</html>

 

 

 

 

public partial class _Default : System.Web.UI.Page {
    protected void Page_Load(object sender, EventArgs e)
    {

    }

    protected void Button1_Click(object sender, EventArgs e)
    {
        this.JoeyTextBox1.mTextBox.Text = "Button Click了";
    }

    protected void JoeyTextBox1_TextChanged1(object sender, EventArgs e)
    {
        this.Button1.Text = "textchange了";
    }
}

 

 

 

 

Step13:

最後的執行畫面。

1

點了Button:

2

隨便在JoeyTextBox上輸入”abcdefg”,再點button。

3

 

 

在JoeyTextBox輸入”a123456789”,會發現TextChange觸發了。

4

接著點button,會發現驗證pass了。

5

 

結語:

相當抱歉之前文章的草率可能造成很多人的困擾,我下次會更謹慎小心的。


blog 與課程更新內容,請前往新站位置:http://tdd.best/