4.8 验证控件编程实践
同其他服务器控件一样,不仅可以采用直接拖曳的方式将验证控件拖入要验证的页面,还可以采用以后台代码编程的方式来使用这些验证控件。当然,如果这些验证控件都不能够满足你的需求,也可以在BaseValidator类的基础上开发属于自己的验证控件。
4.8.1 以编程方式验证ASP.NET服务器控件
默认情况下,在页面回发到服务器时、页面初始化之后(即视图状态和回发数据已处理之后)和调用事件处理代码之前,ASP.NET验证控件将自动执行验证。如果浏览器支持客户端脚本,控件也可以在浏览器中执行验证。
但在实际开发应用中,上面的情况有可能不能够满足你的开发需求,有时候你可能更加需要以编程方式来动态执行验证。如在以下几种情况中,就需要通过编程的方式来进行验证:
1)验证值在运行时尚未设置,它可能需要在程序运行时根据相关情况动态地进行设置。例如,如果正在使用RangeValidator验证控件,则可能需要根据用户输入的值或者用户的身份在程序运行时来动态设置其MinimumValue和MaximumValue属性。此时默认的验证将不起任何作用,因为当页调用验证控件执行验证时,RangeValidator控件中没有足够的信息。
2)需要确定Page_Load事件处理程序中的控件(或整个页)的有效性。在页的处理阶段,验证控件尚未调用,因此页面或单独控件的IsValid属性也未设置。如果试图获取该属性的值,将引发异常。但如果要确定其有效性,则能以编程方式调用验证。
3)在运行时编辑控件。其实,通过编程方式来进行验证是一件既简单又灵活的验证技术。同其他服务器控件的后台编程一样,只需要在后台代码里面设置好验证控件的相关属性就可以。下面的代码示例演示了如何以编程方式来设置属性并进行验证,如代码清单4-8所示。
代码清单4-8 TestRangeValidator1.aspx
<form id="form1"runat="server">
<div>
<asp:TextBox
ID="TextBox1"
runat="server">
</asp:TextBox>
<br/>
<asp:RangeValidator
ID="RangeValidator1"
ControlToValidate="TextBox1"
EnableClientScript="false"
runat="server">
</asp:RangeValidator>
<br/>
<asp:Button ID="Button1"
runat="server"
Text="数据提交"
onclick="Button1_Click"/>
</div>
</form>
在代码清单4-8中,设置了一个TextBox控件TextBox1和一个空的没有任何意义的RangeValidator验证控件RangeValidator1,并将该控件的EnableClientScript属性设置为false,即禁用客户端验证。对控件TextBox1进行验证的功能将通过编程的方式在Button1按钮的Button1_Click事件里面具体实现。代码如下所示:
protected void Button1_Click(object sender, EventArgs e)
{
RangeValidator1.MaximumValue="20";
RangeValidator1.MinimumValue="1";
RangeValidator1.Type=ValidationDataType.Integer;
RangeValidator1.Validate();
if(!RangeValidator1.IsValid)
{
RangeValidator1.ErrorMessage="你输入的数字必须在1~20内";
}
}
在Button1_Click事件里,首先给Range Validator1控件的MaximumValue、Minimum Value和Type属性进行了赋值(在实践开发中,MaximumValue和MinimumValue属性的值可以根据具体情况从配置文件或者数据库等里面得到),再调用验证控件的Validate方法进行验证。该控件将执行检查并设置控件和页面的IsValid属性。如果检测到错误,那么当页面返回到用户时,将照常显示错误信息。运行结果如图4-11所示。
图 4-11 TestRangeValidator1.aspx运行结果
注意 用编程方式进行验证时,应该禁用客户端脚本,即将验证控件的EnableClientScript属性设置为false。这样,控件不会在你的服务器端验证代码执行之前显示不正确的错误信息。
4.8.2 开发自己的文本验证控件
本章的开始已经介绍过BaseValidator类,它是所有验证控件的抽象基类,它定义了验证控件的基本功能,为所有验证控件提供核心实现。因此,可以通过从BaseValidator类中派生一个新的控件类来创建新的验证控件。在使用BaseValidator类时,必须注意下面两个方法:
1)EvaluateIsValid。当被检验的表单字段通过验证时,该方法返回true,否则返回false。一般情况下,在创建自定义验证控件时,必须要重写EvaluateIsValid()方法,并在EvaluateIsValid()方法中调用GetControlValidationValue()方法来获取被验证的控件的值。
2)GetControlValidationValue。该方法用于获取被验证的控件的值。
简单地了解了BaseValidator类之后,下面就来通过BaseValidator类创建防SQL注入攻击的文本验证控件。关于SQL注入攻击请见1.5.2节,这里就不再继续阐述。控件代码如代码清单4-9所示。
代码清单4-9 StringValidator.cs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;
using System.Text.RegularExpressions;
namespace MyValidator
{
public class StringValidator:BaseValidator
{
///<summary>
///验证是否存在注入代码
///</summary>
///<param name="inputData">输入字符</param>
private static bool ValidData(string inputData)
{
//验证inputData是否包含恶意集合
if(Regex.IsMatch(inputData, GetRegexString()))
{
return false;
}
else
{
return true;
}
}
///<summary>
///获取正则表达式
///</summary>
private static string GetRegexString()
{
//构造SQL的注入关键字符
string[]strBadChar={"and"
,"exec","insert","select","delete","update"
,"count","from","drop","asc","char","or"
,"%",";",":","\'","\"","-","chr"
,"mid","master","truncate","char","declare"
,"SiteName","net user","xp_cmdshell","/add"
,"exec master.dbo.xp_cmdshell","net localgroup administrators"};
//构造正则表达式
string str_Regex=".*(";
for(int i=0;i<strBadChar.Length-1;i++)
{
str_Regex+=strBadChar[i]+"|";
}
str_Regex+=strBadChar[strBadChar.Length-1]+").*";
return str_Regex;
}
//重写EvaluateIsValid()方法
protected override bool EvaluateIsValid()
{
string value=
this.GetControlValidationValue(this.ControlToValidate);
return ValidData(value);
}
}
}
在代码清单4-9中,在MyValidator命名空间中创建了一个文本验证控件类StringValidator,它从BaseValidator抽象类继承而来。这个StringValidator控件类重写了基类的EvaluateIsValid()方法,被验证的控件的值利用GetControlValidationValue()方法获取,并通过ValidData(value)方法验证被验证的控件的值是否含有非法字符串,如果有,则返回false,否则返回true。
定义好文本验证控件类StringValidator之后,来继续做如下测试,如代码清单4-10所示。
代码清单4-10 TestStringValidator.aspx
<%@Page Language="C#"AutoEventWireup="true"
CodeBehind="TestStringValidator.aspx.cs"
Inherits="_4_2.TestStringValidator"%>
<%@Register TagPrefix="custom"
Namespace="MyValidator"Assembly="4-2"%>
<!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>测试自定义控件</title>
</head>
<body>
<form id="form1"runat="server">
<div>
<asp:TextBox ID="TextBox1"
runat="server"
TextMode="MultiLine"
Height="80px"/>
<br/>
<custom:StringValidator
ID="StringValidator1"
ControlToValidate="TextBox1"
Text="你输入的文本里面有非法字符"
runat="server"/>
<br/>
<asp:Button ID="Button1"
runat="server"Text="数据提交"/>
</div>
</form>
</body>
</html>
在代码清单4-10中,利用语句<%@RegisterTagPrefix="custom"Namespace="MyValidator"Assembly="4-2"%>对文本验证控件StringValidator进行了页面注册。其中TagPrefix="custom"表示页面引用StringValidator验证控件的标记,Namespace="MyValidator"表示StringValidator验证控件的命名空间,Assembly="4-2"表示StringValidator验证控件的程序集名称。
注册完StringValidator验证控件之后,就可以在页面直接引用该控件了,引用方式与一般的验证控件一样。如:
<custom:StringValidator
ID="StringValidator1"
ControlToValidate="TextBox1"
Text="你输入的文本里面有非法字符"
runat="server"/>
运行TestStringValidator.aspx页面,结果如图4-12所示。当在文本框里面输入一些非法的SQL注入字符串,如“sel ect”,将会触发Str ing Validator验证控件的EvaluateIsValid()方法返回false,从而给用户返回验证失败的错误的信息“你输入的文本里面有非法字符”。
图 4-12 TestStringValidator.aspx运行结果
4.8.3 引用自定义服务器控件的方法
上面的自定义文本验证控件其实就是一个简单的用户自定义服务器控件,可以根据上面的方法,利用BaseValidator抽象类来创建自己所需要的特殊验证控件。当然,如果要想创建一个复杂度较高的用户自定义服务器控件,则远比这要复杂得多,所以我们会在本书的后面安排一整章来详细讲解。创建好控件之后在页面通常可以通过三种方法来进行引用,下面就来详细解释这三种自定义服务器控件引用方法。
1.使用@Register指令
使用@Register指令应该是最简单的一种方法,它可以创建标记前缀和自定义控件之间的关联,从而用来在ASP.NET应用程序文件(包括网页、用户控件和母版页)中引用自定义控件。在实际开发中,@Register指令一般应用于如下两种情况:
1)以声明方式将自定义服务器控件添加到网页、用户控件、母版页或外观文件。如:
<%@Register TagPrefix="custom"
Namespace="MyValidator"Assembly="4-2"%>
2)以声明方式将用户控件添加到网页、用户控件、母版页或外观文件。如:
<%@Register TagPrefix="custom"TagName="MyValidator"
Src="~\Controls\MyValidator.ascx"%>
它有非常重要的几个属性:
❑tagprefix
一个任意别名,它提供对包含指令的文件中所使用的标记的命名空间的短引用。如果把TagPrefix属性定义为custom,则在页面引用控件时就必须以custom标记开头。如:
<custom:StringValidator
ID="StringValidator1"
ControlToValidate="TextBox1"
Text="你输入的文本里面有非法字符"
runat="server"/>
❑Namespace
正在注册的自定义控件的命名空间。
❑assembly
与tagprefix属性关联的命名空间所驻留的程序集,程序集名称不能包括文件扩展名。
值得注意的是,在以Web网站的方式创建的项目中,如果assembly属性丢失,ASP.NET分析器会假定应用程序的App_Code文件夹中存在源代码。如果希望在页面上注册控件的源代码而不对其进行编译,请将源代码放在App_Code文件夹中。ASP.NET在运行时动态编译App_Code文件夹中的源文件。
❑tagname
与类关联的任意别名,此属性只用于用户控件。
❑src
与tagprefix:tagname对关联的声明性ASP.NET用户控件文件的位置,src属性值既可以是相对路径,也可以是从应用程序的根目录到用户控件源文件的绝对路径。为方便使用,建议使用相对路径。例如,假设将应用程序的所有用户控件文件存储在应用程序根目录的子目录\Usercontrol中。若要包括Usercontrol1.ascx文件中的用户控件,请在@Register指令中包含以下内容:
Src="~\usercontrol\usercontrol1.ascx"
其中,“~”表示应用程序的根目录。
在使用@Register指令引用自定义服务器控件时,可以将控件的代码放在以下位置:
1)作为应用程序的App_Code文件夹的源代码,将在运行时在该文件夹中动态编译代码。在开发过程中可以使用这一便捷选项。如果选择此选项,则不必在@Register指令中使用assembly属性(但它仅仅适合于以Web网站的方式创建的项目)。
2)作为应用程序的Bin文件夹中的编译的程序集,这是一个针对部署的Web应用程序的通用选项。
3)作为全局程序集缓存((GC)中编译和签署的程序集。这是一个针对希望在多个应用程序之间共享编译的控件的通用选项。通过向assembly属性分配正在识别的字符串,可以引用GAC中的控件。此字符串指定有关控件所需的详细信息,包括控件的完全限定类型名、版本、公钥标记和区域性。如下面的虚拟字符串阐明了对GAC中的自定义控件的引用:
<%@Register TagPrefix="custom"
Namespace="MyControls.namespace"
Assembly="MyControls.namespace.control, Version=1.2.3.4,
PublicKeyToken=12345678abcdefgh, Culture=neutral"%>
2.配置Web.config文件
如果想要在同一个We b应用程序中的多个页面上使用同一控件,还可以通过使用配置Web.config文件中的pages节点下的controls元素在应用程序的所有页上注册自定义控件,如代码清单4-11所示。
代码清单4-11 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true"targetFramework="4.0"/>
<pages>
<controls>
<add tagPrefix="custom"namespace="MyValidator"assembly="4-2"/>
</controls>
</pages>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
其中,<add tagPrefix="custom"namespace="MyV alidator"assembly="4-2"/>元素中的tagPrefix、namespace和assembly的意义与@Register指令相同。这样定义之后,就不需要在每个页面都定义@Register指令了。
3.使用Visual Studio工具箱
令人激动的是,除了上面两种方法之外,Visual Studio的工具箱内置了对自定义控件的支持。可以将自己的任何自定义服务器控件添加到Visual Studio的工具箱,这样,就可以像使用其他服务器控件一样,使用拖曳的方式就可以完成页面设计工作。同时,这样也大大提高了代码的可复用性和开发的方便性。
首先,将鼠标放在Visual Studio工具箱的任何位置,右击鼠标选择“Add Tab”命令,输入Tab的名称“MyControls”(当然也可以输入其他名称),如图4-13所示。
图 4-13 添加Tab“MyControls”
其次,在工具箱的“MyControls”Tab里,右击鼠标选择“Choose Items”命令,在弹出的Choose Toolbox Items对话框里单击“Browse”按钮,选择自定义服务器控件的程序集,如4-2.dll。这样,就可以在Choose Toolbox Items对话框中看见相关的用户自定义服务器控件了,如图4-14所示。
在图4-14所示的Choose Toolbox Items对话框里,选择要在工具箱里显示的自定义服务器控件,如StringValidator。单击“OK”按钮,如图4-15所示。
图 4-14 选择自定义服务器控件
图 4-15 自定义服务器控件添加完成
这样,就在Visual Studio工具箱里面添加了一个自定义控件,见图4-15。现在,可以像使用其他服务器控件一样来使用它了,它的默认前缀是cc1,如下面的代码所示:
<cc1:StringValidator ID="StringValidator1"
runat="server"></cc1:StringValidator>