在今天我主要要介绍的有如下知识点:
HTML表单的提交方式
HTM控件
获取HTML表单内容
乱码问题
SQL注入
服务器端表单
HTML服务器控件
 
HTML表单的提交方式
        对于一个普通HTML表单来说,它有两个重要的属性:action和method。
action属性指明当前表单提交之后由哪个程序来处理,这个处理程序可以是任何动态网页或者servlet或者CGI(Common Gateway Interface),在asp.net里面一般都是都aspx页面来处理。
        method属性指明form表单的提交方式。它有两个可能值get和post。
下面我们以一个例子来说明get和post的区别。用Dreamweaver8创建两个aspx页面,分别为Register.aspx和 GetUserInfo.aspx。暂时我们不需要在GetUserInfo.aspx页面写任何代码,Register.aspx页面的代码如下:
     1. <%@ Page Language="C#" ContentType="text/html" ResponseEncoding="gb2312" %>
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <html xmlns="http://www.w3.org/1999/xhtml">
     4. <head>
     5. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
     6. <title>用户注册</title>
     7. </head>
     8. <body>
     9. <form action="GetUserInfo.aspx" method="get">
    10. <table border="1" width="400px">
    11. <tr><td colspan="2">用户注册</td></tr>
    12. <tr><td>用户名</td><td><input type="text" name="username" /></td></tr>
    13. <tr><td>密码</td><td><input type="password" name="pwd" /></td></tr>
    14. <tr><td><input type="submit" value="提交" /></td><td><input type="reset" value="重置" /></td></tr>
    15. </table>
    16. </form>
    17. </body>
    18. </html>

 
        将Register.aspx和GetUserInfo.aspx都保存到C:\Inetpub\wwwroot下,然后打开浏览器在地址栏中输入http://localhost/register.aspx,用户名和密码分别输入“zhoufoxcn”和“123456”,点击“提交”按钮,我们看到如下结果:
 
        注意上面的代码中“<form action="GetUserInfo.aspx" method="get">”这句,现在我们仅仅将method由“get”变成“post”,我们再次输入“zhoufoxcn”变成 “123456”,提交表单,我们看到如下结果:
 
        细心的朋友可能会注意到,当我们以get方式提交的时候,在浏览器地址栏里除了接收参数的网页名之外,还有我们的表单名和参数值,在这里是 “username=zhoufoxcn&pwd=123456”,而以post方式提交的时候地址栏除了接收参数的网页之外并没有这样参数。具体说来get和post提交方式有如下两个区别:
        (1)get方式提交的表单在地址栏会显示参数名和参数值,而post方式不会。用post提交参数相对来说更隐蔽一些,也相对安全一些。假如我们这个页面用于用户登录并且存放用户信息的表为“users”,我们的sql语句可能会这么写:string sql=” select * from users where username='”+username+”’ and password='”+password+”’”;然后我们的username和password变量从表单提交的数据获取,正常情况下用户填写是没有问题,就上面的例子来说用户填写的用户名和密码分别是“zhoufoxcn”和“123456”,那么我们最后得到的SQL语句是:sql=”select * from users where username=’zhoufoxcn’ and password=’123456’”,这样是没有问题的。如果用get方式提交,用户可以对上面的参数进行一些改动,比如在地址栏直接输入:http://localhost/GetUserInfo.aspx?username=zhoufoxcn&pwd=123';delete * from users',那么我们得到的SQL就是:string sql=” select * from users where username='zhoufoxcn' and password='123';delete * from users'”,执行上面的SQL语句就能把Users这个表里的所有数据删除掉!这个一个触目惊心的危险!这个就叫SQL注入。
        (2)由于浏览器地址栏能输入的最大字符数有限制,所以用get方式提交不能处理参数值更大的表单,而post方式则没有这个限制。
上面我说到用post方式提交表单相对安全,并没有说绝对安全,就拿上面的表单来说,假如上面的网站代码发布在www.netskycn.com上发布,我们可以通过http://www.netskycn.com/register.aspxhttp://www.netskycn.com/GetUserInfo.aspx来访问,那么我们完全可以在本地制作一个网页,HTML的页面都行,它的代码如下:
     1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2. <html xmlns="http://www.w3.org/1999/xhtml">
     3. <head>
     4. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
     5. <title>用户注册</title>
     6. </head>
     7. <body>
     8. <form action="http://www.netskycn.com/GetUserInfo.aspx" method="post">
     9. <table border="1" width="400px">
    10. <tr><td colspan="2">用户注册</td></tr>
    11. <tr><td>用户名</td><td><input type="text" name="username"    value="zhoufoxcn" /></td></tr>
    12. <tr><td>密码</td><td><input type="password" name="pwd"    value="123';delete * from users" /></td></tr>
    13. <tr><td><input type="submit" value="提交" /></td><td><input type="reset" value="重置" /></td></tr>
    14. </table>
    15. </form>
    16. </body>
    17. </html>
        注意表单的action属性我用了全路径的url地址,只要我们能连上互联网,那么这个表单一样可以正确提交的,同样也能产生触目惊心的危害!对于这个问题如何避免我会专门抽出一篇文章来讲如何防范,这里暂不赘述。
HTML控件
        HTML控件在上面的例子里已经用到,它就是指用HTML表单里的一些列元素来提供用户交互,它们都是类似“<input type=”text” name=”name””这样的标记。要呈现什么的形式由type属性来决定,可以有“text”、“password”、“radio”、 “checkbox”、“submit”及“reset”等,分别呈现为文本框、密码框、单选框、复选框及提交按钮和重置按钮等。
获取表单值
        通过用get方式提交表单我们可以看到提交到服务器的时候,在网页后面有 “?username=zhoufoxcn&pwd=12345”这么一个字符串,也就是以表单里HTML控件的名字=控件值的方式,并且如果存在多个控件,彼此之间以“&”分割,那么我们就可以以控件名来获取控件值。获取HTML控件值常见有以下集中方式:
获取方式  表单提交方式
 Request.QueryString["控件名"]   适合于get方式提交的表单
Request.Form["控件名"]  适合于post方式提交的表单
Request["控件名"]  同时适合于get和post方式提交的表单

        从上面我们可以看到用Request["控件名"]这种方式对于get和post两种方式都可行,那么我们就可以用这种方式来应付所有提交的表单。现在我们在“GetUserInfo.aspx”页面编写如下代码:
     1. <%@ Page Language="C#" ContentType="text/html" ResponseEncoding="gb2312" %>
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <html xmlns="http://www.w3.org/1999/xhtml">
     4. <head>
     5. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
     6. <title>注册表单提交的信息</title>
     7. </head>
     8. <body>
     9. 用户名:<%=Request["username"]%><br />
    10. 密码:<%=Request["pwd"]%>
    11. </body>
    12. </html>
        我们再次输入“zhoufoxcn”和“123456”并提交表单,得到如下画面:
 
        得到了我们期望的效果。
        乱码问题
        对于上面的表单,我们在用户名和密码处分别输入“周公”和“123456”,会得到如下的结果,如下图:
 
        这里用户就变成了“???”了,之所以出现这样的情况就是因为我们的编码原因。这就像我们在酒吧里对一个懂普通话的服务员说来一杯酒,他马上会送你一杯酒;可是当我们对一个不懂普通话的服务员用普通话说来一杯酒,他就会一头雾水了。
        之所以出现乱码就是因为在这里客服端请求的编码和服务器响应的编码不一致,这个问题在《asp.net夜话之二:内置对象》中讲Request和Response对象的时候讲到过,没有看过的朋友请返回去看一下,这里只贴出结果:
 
        从上面的图上我们看到默认情况下Request的ContentEncoding为UTF8Encoding,而Response的 ContentEncoding为System.Text.DBCSCodePageEncoding,二者的不一致导致了输入中文成了乱码。
我们常见的编码有gb2312、gbk和unicode几种,gb2312能显示日常生活最常用的6000多个汉字,这对于一般的公文足够了,可是如果要用来显示一个古文献就不行了(据说康熙词典收录了4万多汉字),当它不能显示的时候也会出现乱码。Gbk是在gb2312的基础上扩展的,大概能显示1万8 千多个汉字,这对于一般古文献也差不多够了。Unicode则更大一些,它能显示20901个汉字(范围是从\u4e00到\u9fa5),并且还能显示日文、韩文、台湾文字、香港文字和新加坡等文字,所以目前很多网站都采用unicode编码。Utf8编码就是unicode编码中的一种,关于它们的编码原理有兴趣的朋友可以查询有关资料。
        因为目前用Dreamweaver创建动态网页的时候默认都是采用gb2312编码(asp和jsp的程序员的网站编码很多都采用了这个默认值)。如果我们要想正确获取表单的值,我们可能就需要更改默认编码,对于上面的乱码问题,我们只需要更改 Register.aspx页面就行,将Register.aspx页面的这部分更改一下:
   1. <%@ Page Language="C#" ContentType="text/html" ResponseEncoding="gb2312" %>
更改为:
   1. <%@ Page Language="C#" ContentType="text/html" ResponseEncoding="utf-8" %>

        这样就能正常显示了,如下:
 
        注意,使用Microsoft Visual Studio 2005创建网站的时候默认编码就是utf-8,无需更改。
服务器端表单
        在此之前我们见到的表单都是如下格式:

 
   1. <form action="接收数据页面" method="post">
并且我们都是利用的HTML控件。现在我们要介绍服务器端表单,服务器端表单与前面的表单相比,多了一个runat=”server”标记,如下:
   1. <form id="form1" runat="server">
在服务器端表单里可以不用指定action属性,表示由当前页面处理,也可以不指定method属性,默认为post方式提交表单。在服务器端表单里,我们不光可以使用HTML控件,还可以使用HTML服务器控件,还可以使用asp.net控件(asp.net控件稍后会专门花篇幅介绍)。
另外需要注意的是,在一个asp.net页面中可以有多个不带runat=”server”标记的表单,但是只能有一个服务器端表单。
 
HTML控件
        HTML服务器控件与普通服务器控件不同的是:在普通HTML控件中加上了一个id属性和一个runat=”server”标记。如下就是一个HTML服务器控件:
   1. <input type="text" runat="server" id="txtUserName" />
HTML服务器控件有几个限制:
在整个asp.net页面中这个控件id的必须唯一,并且HTML服务器控件只能放在HTML服务器表单中。因为一个asp.net页面只能有一个服务器表单,所以说在服务器表单中控件的id值必须唯一,因为我们在编程的时候通过这个id来访问HTML服务器控件。如果不唯一就会报错,如下:
 
        下面就是一个使用了HTML服务器控件并且能正确运行的表单:
     1. <%@ Page Language="C#" ContentType="text/html" ResponseEncoding="gb2312" %>
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <html xmlns="http://www.w3.org/1999/xhtml">
     4. <head>
     5. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
     6. <title>服务器表单和HTML服务器控件</title>
     7. </head>
     8. <body>
     9. <form runat="server">
    10. <input type="text" runat="server" id="txtUserName" />
    11. <input type="password" runat="server" id="txtPassword" />
    12. <input type="submit" runat="server" id="btnOK" value="提交" />
    13. </form>
    14. </body>
    15. </html>
        这个页面在浏览器端的HTML源代码如下:
     1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2. <html xmlns="http://www.w3.org/1999/xhtml">
     3. <head>
     4. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
     5. <title>服务器表单和HTML服务器控件</title>
     6. </head>
     7. <body>
     8. <form name="ctl00" method="post" action="serverform.aspx" id="ctl00">
     9. <div>
    10. <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNTQ0MjI5MTUzZGR1mFZ52ODFgAKe7Qx9/67qMGFJCA==" />
    11. </div>
    12. <input name="txtUserName" type="text" id="txtUserName" />
    13. <input name="txtPassword" type="password" id="txtPassword" />
    14. <input name="btnOK" type="submit" id="btnOK" value="提交" />
    15. <div>
    16.    <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWBAKIpeT6DgKl1bKzCQK1qbSRCwLdkpmPAcJ7Zy/C66ypRIq49nr3hQNayqwk" />
    17. </div></form>
    18. </body>
    19. </html>
        我们可以看到在客服端得到的HTML代码中都是标准的HTML代码,我们的文本框和密码框及服务器端提交按钮(因为它在服务器代码里也有runat=”server”标记)变成了如下代码:
<input name="txtUserName" type="text" id="txtUserName" />
<input name="txtPassword" type="password" id="txtPassword" />
<input name="btnOK" type="submit" id="btnOK" value="提交" />
        也就是,所有的服务器控件经过服务器运行之后都会变成标准的HTML控件。这样我们可以得出一个结论:如果我们的控件功能本来就很简单,我们就可以直接使用HTML控件,这样就可以减轻服务器的负担,提高运行效率。另外,在上面的代码中多了一些以前我们没有见过的部分:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNTQ0MjI5MTUzZGR1mFZ52ODFgAKe7Qx9/67qMGFJCA==" />
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWBAKIpeT6DgKl1bKzCQK1qbSRCwLdkpmPAcJ7Zy/C66ypRIq49nr3hQNayqwk" />
        因为服务器会保存服务器控件的状态和属性,所以它会利用一些隐藏域来保存这方面的信息,这部分的内容是经过Base64编码的。
服务器控件的好处是我们可以动态在代码中动态控制服务器控件的属性,对于上面的代码我们改造如下:
     1. <%@ Page Language="C#" ContentType="text/html" ResponseEncoding="gb2312" %>
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <html xmlns="http://www.w3.org/1999/xhtml">
     4. <head>
     5. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
     6. <title>服务器表单和HTML服务器控件</title>
     7. </head>
     8. <script runat="server">
     9. protected void btnOK_Click(Object Src, EventArgs E)
    10. {
    11. Response.Write("用户名:"+this.txtUserName.Value);
    12.     Response.Write("密码:"+this.txtPassword.Value);
    13.     this.btnOK.Disabled=true;
    14. }
    15. </script>
    16. <body>
    17. <form runat="server">
    18. <input type="text" runat="server" id="txtUserName" />
    19. <input type="password" runat="server" id="txtPassword" />
    20. <input type="submit" runat="server" id="btnOK" value="提交" onserverclick="btnOK_Click" />
    21. </form>
    22. </body>
    23. </html>

        上面代码中多了“<script runat="server"></script>”,并且提交按钮多了一个onserverclick="btnOK_Click" 属性。在“<script runat="server"></script>”标记中我们写了一个protected void btnOK_Click(Object Src, EventArgs E)方法,这个方法符合System.EventHandler委托的标准。这个方法有两个参数,第一个表示由什么控件激发了这个事件,第二个参数表示事件发生时的一些相关信息。
        在protected void btnOK_Click(Object Src, EventArgs E)方法中我们利用了类似WinForm中操作控件的方式来操作我们的服务器控件,这也就是为什么asp.net页面成为WebForm的原因。在这个方法里我们获取了控件的值,并最后将提交按钮禁用。
        提交按钮的onserverclick属性值表示当这个按钮点击后由服务器上的哪个方法进行处理,这个方法要满足System.EventHandler委托的定义,这里我们写了btnOK_Click这个方法名。
这个页面初次运行的效果如下:
 
        然后我们分别输入”zhoufoxcn”和”123456”,提交表单之后的效果如下:
 
        我们看到在当前页面输出了表单控件的值,并且最后提交按钮呈现灰色状态,也就是被禁用了。
        上面例子中我们确实就像在WinForm一样控制asp.net控件,非常方便。其实在asp.net开发中,用的最多的是asp.net服务器控件,而不是HTML服务器控件,asp.net控件提供了比HTML服务器控件更多的灵活性,以后的文章中会继续探讨asp.net控件。
        实际上不管是HTML控件还是HTML服务器控件在asp.net里面用的都不是太多,介绍这部分主要是提及一些被Microsoft Visual Studio 2005隐藏的细节,还有其它动态页面与asp.net页面进行交互等问题。
        后记:写完这篇文章已经是2008年9月18日临晨两点了,最近工作比较忙,整个系列下来可能会有30篇左右,我会在工作之余继续酝酿。同时也在考虑如何安排更合理,并且尽可能地将实际项目中应该注意到的一些问题贯穿进来。比如这次文章中讲到了SQL注入问题,以后在ADO.NET部分我会讲解如何防止SQL注入。希望大家多多提宝贵意见。
                                                                                周金桥于2008年9月18日星期四 2:03