[ASP.NET 自訂控制項]TextBox動態加入RequiredFieldValidator與CustomValidator
首先相當相當抱歉,上次發佈的那一篇「[ASP.NET 自訂控制項]如何在TextBox裡動態新增Validator」太草率了,
在今天練習將設計元件的語法從VB.NET改成用C#的時候,突然發現我怎麼犯了這麼大的錯。
我的天啊,不知道誤導了多少人。(雖然沒人回應 ),但是有幾位大大的推薦跟收藏,讓我發現這大trouble的時候,膽顫心驚。
原本那篇文章最大的問題,就在繼承的是TextBox,繼承TextBox是無法在PreRender的時候,動態加入Validator的。
因為TextBox不是「容器」,應該要繼承CompositeControl或WebControl才對。
Download source code:CustomLabel.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。
輸入自訂的驗證錯誤訊息該如何顯示。
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:
最後的執行畫面。
點了Button:
隨便在JoeyTextBox上輸入”abcdefg”,再點button。
在JoeyTextBox輸入”a123456789”,會發現TextChange觸發了。
接著點button,會發現驗證pass了。
結語:
相當抱歉之前文章的草率可能造成很多人的困擾,我下次會更謹慎小心的。
blog 與課程更新內容,請前往新站位置:http://tdd.best/