
第3章 ASP.NET开发大杂烩
随着 PC 时代的过去,互联网趋势已经势不可挡。很多数据表明互联网的快速成长,已经达到了令人疯狂的境界。根据CNNIC发布的《第23次中国互联网网络发展状况统计报告》数据统计, 中国网站数量已经超过了288万,比上一年增长91.4%。换句话说,去年中国平均每半分钟就诞生一个网站!这还仅仅是网站,并不包括大量基于互联网其他形式的应用和企业应用系统。
同时,过去几年大量的企业软件C/S结构已逐渐转为B/S结构,软件的Web(互联网)化,似乎有不可阻挡之势。Web 2.0 的热潮也推动着互联网迈向一个新的时代,许许多多的人带着梦想加入互联网创业大军。而这一切预示着互联网时代的兴盛。互联网已经融入到人们的经济、生活、娱乐等各个方面,在某个社会群体中发掘出一个有规模的社会需求,就有可能形成一个互联网向上发展的拐点。所以,这样的趋势也导致网站(Web)的开发技术成为软件从业者所必需的主流技术。微软这样的巨无霸当然不会袖手旁观,它重力打造的.NET计划早以为这一切做好了准备。
ASP.NET是微软.NET框架提供的一个统一的Web开发模型,它包括你使用尽可能少的代码生成企业级Web应用程序所必需的各种服务。当你编写ASP.NET应用程序的代码时,可以访问 .NET Framework中的类。你可以使用与公共语言运行库(CLR)兼容的任何语言来编写应用程序的代码,这些语言包括VB、C#、JScript .NET和J#。使用这些语言,可以开发利用具有公共语言运行库、类型安全、继承等方面的优点的ASP.NET应用程序。ASP.NET页面(Web Forms)需经过编译,与使用脚本编写语言相比,具有更好的性能和安全性。Web Forms允许构建强大的基于窗体的Web页面。构建这些页面时,可以使用ASP.NET服务器控件创建常用的UI元素并对其进行编程以执行常见的任务。这些控件允许从可重用的内置或自定义组件快速构建 Web 窗体,从而简化页面代码。实现真正快捷、高效而简单的 Web开发。
3.1 页面生命周期
人都知道人是有生命的,一个人从呱呱落地到慢慢老去经历了一个生命的轮回,这是生命的一个周期。同样,ASP.NET开发的Web页面也有它自己的生命周期,从生成到销毁,也经历了不同的阶段和过程。
虽然 ASP.NET 技术已经为我们提供了很强大的功能,但有的时候我们还是需要根据自己的需求做一些改变,来满足千奇百怪的效果需求。要做到知己知彼,方能百战百胜,所以,我们需要更深一步了解 ASP.NET 页面从生到死的执行过程,才能更好地对症下药完成我们的需求。
3.1.1 独立页面生命周期事件顺序
既然页面是有生命的,那就让我们来看一下,页面的“一生”是如何度过的吧!它的“一生”都发生了哪些大事件,以及事件的顺序,便于我们根据页面的事件来决定我们自己该做些什么,从而改变页面的“命运”来为我们的业务服务。
在一个独立的页面中,我们加入以下代码来监测事件的执行顺序:
代码示例: (示例位置:光盘\code\ch03\01)
public partial class _Default : System.Web.UI.Page { protected void Page_PreInit(object sender, EventArgs e) { Response.Write("Page_PreInit<br/>"); } protected void Page_Init(object sender, EventArgs e) { Response.Write("Page_Init<br/>"); } protected void Page_InitComplete(object sender, EventArgs e) { Response.Write("Page_InitComplete<br/>"); } protected void Page_PreLoad(object sender, EventArgs e) { Response.Write("Page_PreLoad<br/>"); } protected void Page_Load(object sender, EventArgs e) { Response.Write("Page_Load<br/>"); } protected void Page_LoadComplete(object sender, EventArgs e) { Response.Write("Page_LoadComplete<br/>"); } protected void Page_PreRender(object sender, EventArgs e) { Response.Write("Page_PreRender<br/>"); } protected void Page_PreRenderComplete(object sender, EventArgs e) { Response.Write("Page_PreRenderComplete<br/>"); } protected void Page_SaveStateComplete(object sender, EventArgs e) { Response.Write("Page_SaveStateComplete<br/>"); } protected void Page_Unload(object sender, EventArgs e) { //Response.Write("Page_Unload<br/>"); int i = 0; i++;//这行代码是用来设置断点的,这里不能用Response.Write } protected void Button1_Click(object sender, EventArgs e) { Response.Write("Button事件触发!<br/>"); } }
输出结果:
Page_PreInit Page_Init Page_InitComplete Page_PreLoad Page_Load Page_LoadComplete Page_PreRender Page_PreRenderComplete Page_SaveStateComplete
这就是独立页面事件的执行顺序。
单击页面的Button1按钮,页面会进行Postback并重新加载页面,这个过程的事件输出顺序:
Page_PreInit Page_Init Page_InitComplete Page_PreLoad Page_Load Button事件触发! Page_LoadComplete Page_PreRender Page_PreRenderComplete Page_SaveStateComplete
根据这些,我们可以轻松地知道该在什么时候加入我们自己需要的代码,修改原有的逻辑,以完成在页面操作过程中需要完成的功能。
3.1.2 具有Master页的生命周期事件顺序
当页面有 Master 页的时候,将上面的代码分别复制一份到 Site.Master 页和内容子页ContentPage.aspx,再次执行页面ContentPage.aspx,可以看到具有Master页的页面里面的事件执行顺序。
代码示例: (示例位置:光盘\code\ch03\01)
public partial class Site: System.Web.UI.MasterPage { protected void Page_PreInit(object sender, EventArgs e) { Response.Write("MasterPage_PreInit<br/>"); } protected void Page_Init(object sender, EventArgs e) { Response.Write("MasterPage_Init<br/>"); } protected void Page_InitComplete(object sender, EventArgs e) { Response.Write("MasterPage_InitComplete<br/>"); } protected void Page_PreLoad(object sender, EventArgs e) { Response.Write("MasterPage_PreLoad<br/>"); } protected void Page_Load(object sender, EventArgs e) { Response.Write("MasterPage_Load<br/>"); } protected void Page_LoadComplete(object sender, EventArgs e) { Response.Write("MasterPage_LoadComplete<br/>"); } protected void Page_PreRender(object sender, EventArgs e) { Response.Write("MasterPage_PreRender<br/>"); } protected void Page_PreRenderComplete(object sender, EventArgs e) { Response.Write("MasterPage_PreRenderComplete<br/>"); } protected void Page_SaveStateComplete(object sender, EventArgs e) { Response.Write("MasterPage_SaveStateComplete<br/>"); } protected void Page_Unload(object sender, EventArgs e) { //Response.Write("MasterPage_Unload<br/>"); int i = 0; i++;//这行代码是用来设置断点的,这里不能用Response.Write } }
这次的输出结果顺序:
Page_PreInit MasterPage_Init Page_Init Page_InitComplete Page_PreLoad Page_Load MasterPage_Load Page_LoadComplete Page_PreRender MasterPage_PreRender Page_PreRenderComplete Page_SaveStateComplete
单击页面的Button1按钮后的输出顺序:
Page_PreInit MasterPage_Init Page_Init Page_InitComplete Page_PreLoad Page_Load MasterPage_Load Button事件触发! Page_LoadComplete Page_PreRender MasterPage_PreRender Page_PreRenderComplete Page_SaveStateComplete
由此可以看出,有些时候是先执行 Master 页面的事件的,有时候是先执行内容子页的事件的,从而我们可以知道如何安排某些逻辑代码的处理到具体的位置。
3.1.3 ASP.NET生命周期详解
ASP.NET 页运行的生命周期中将执行一系列处理步骤。这些步骤包括初始化、实例化控件、还原和维护状态、运行事件处理程序代码及呈现。了解页生命周期非常重要,因为这样做你就能在生命周期的合适阶段编写代码,以达到预期效果。此外,如果你要开发自定义控件,就必须熟悉页生命周期,以便正确进行控件初始化,使用视图状态数据填充控件属性,以及运行任何控件行为代码(控件的生命周期基于页的生命周期,但是页引发的控件事件比单独的ASP.NET页中可用的事件多)。
1.常规页生命周期阶段
一般来说,页要经历如表3-1所示的各个阶段。除了页生命周期阶段以外,在请求前后还存在应用程序阶段,但是这些阶段并不特定于页。
表3-1 页要经历的各个阶段及其说明

2.生命周期事件
在页生命周期的每个阶段中,页将引发可运行你自己的代码进行处理的事件。对于控件事件,通过以声明方式使用属性(如OnClick)或以使用代码的方式,均可将事件处理程序绑定到事件。
页还支持自动事件连接,即ASP.NET将查找具有特定名称的方法,并在引发了特定事件时自动运行这些方法。如果@ Page指令的AutoEventWireup属性设置为true(或者未定义该属性,因为该属性默认为true),则页事件将自动绑定至使用Page_事件的命名约定的方法(如Page_Load和Page_Init)。
表3-2列出了最常用的页生命周期事件。除了列出的事件外还有其他事件;不过,大多数页处理方案不使用这些事件。而是主要由 ASP.NET 网页上的服务器控件使用,以初始化和呈现它们本身。如果要编写自己的 ASP.NET 服务器控件,则需要详细了解这些阶段。
表3-2 最常用的页生命周期事件

3.其他的页生命周期注意事项
各个ASP.NET服务器控件都有自己的生命周期,该生命周期与页生命周期类似。例如,控件的Init和Load事件在相应的页事件期间发生。
虽然Init和Load都在每个控件上以递归方式发生,但它们的发生顺序相反。每个子控件的Init事件(还有Unload事件)在为其容器引发相应的事件之前发生(由下到上)。但是,容器的Load事件是在其子控件的Load事件之前发生(由上到下)的。
可以通过处理控件的事件(如 Button 控件的 Click 事件和 ListBox 控件的SelectedIndexChanged 事件)来自定义控件的外观或内容。在某些情况下,可能也需要处理控件的DataBinding或DataBound事件。
当从 Page 类继承类时,除了可以处理由页引发的事件以外,还可以重写页的基类中的方法。例如,可以重写页的InitializeCulture方法,以便动态设置区域性信息。注意,在使用Page_事件语法创建事件处理程序时,将隐式调用基实现,因此无须在方法中调用它。例如,无论是否创建Page_Load方法,始终都会调用页基类的OnLoad方法。但是,如果使用override关键字重写页的OnLoad方法,则必须显式调用基方法。例如,如果在页中重写OnLoad方法,则必须调用base.Load以运行基实现。
3.2 页面状态管理
我们在开发时会需要保存当前一些信息和数据,以便后面使用。由于B/S结构的特点,很多时候信息不能像在硬盘保存文件那样容易实现,但ASP.NET仍然提供了相应的保存方式来实现信息数据的保存。根据数据的保存位置,ASP.NET 的状态管理又分为客户端的状态管理和服务器端的状态管理。
● 客户端的状态管理:在客户端、服务器之间的多次请求-应答期间,服务器上不保存信息,信息将被存储在网页或用户的计算机上。包括 Cookie、HtmlInputHidden隐藏域、ViewState和查询字符串。
● 服务器端的状态管理:信息存储在服务器上,尽管其安全性较高,但会占用较多的Web服务器资源。一般是通过Aplication、Session、数据库来实现服务器端的状态保存。
3.2.1 Cookie
Cookie 是存储在客户端文件系统的文本文件中或客户端浏览器对话的内存中的少量数据,它主要用来跟踪数据设置。
下面我们举例说明:假设我们要访问一个网站网页,当用户请求该网页时,应用程序会首先检查用户在此前是否已经登录,我们可以通过读取 Cookie 获取用户信息来判断是否让它继续访问;我们使用的购物车很多时候也是使用Cookie来记录当前的购物情况的。
你可以在IE中选择“工具”→“Internet选项”菜单命令,在弹出的对话框中选择“常规”选项卡,单击“设置”→“查看文件”按钮,查看所有保存到你的电脑里的Cookie,如图3-1所示。

图3-1 查看Cookie
这些文件通常是以 user@domain 格式命名的,user 是你的本地用户名,domain 是所访问的网站的域名,如图3-2所示。

图3-2 Cookie文件
1.记录Cookie信息
创建一个名称是user的Cookie对象:
HttpCookie cookie = new HttpCookie("user");
给Cookie赋值,只能使用字符串赋值:
cookie.Value = "litianping";
如果有多个字符串需要保存,则可以通过如下方式实现:
cookie["sex"] = "男"; //或者这种方式也可以 cookie.Values.Add("age","30");
为了控制Cookie的有效期限,可以设置Cookie的过期时间:
cookie.Expires = DateTime.Now.AddHours(1); //从当前开始1小时后过期
将Cookie添加到内部Cookie集合。Cookie集合中的所有Cookie均通过HTTP输出流在Set-Cookie头中发送到客户端。
Response.AppendCookie(cookie); //Response.Cookies.Add(cookie);//这样直接保存也可以
2.读取Cookie信息
从Cookie中获取用户的信息:
HttpCookie cookie = Request.Cookies["user"]; if (null == cookie) //确定是否存在用户输入的cookie { Response.Write("没有发现指定的cookie"); } else { Response.Write("cookie 的全部值为:" + cookie.Value + "<br>"); Response.Write("sex 值为:" + cookie["sex"] + "<br>"); Response.Write("age 值为:" + cookie.Values["age"] + "<br>"); }
3.删除Cookie
由于 Cookie 位于用户的计算机中,所以你无法直接将其删除。但是,你可以让浏览器为你删除Cookie。将Cookie的有效期设置为过去的某个日期。当浏览器检查Cookie的有效期时,就会删除这个已经过期的Cookie。
cookie.Expires = DateTime.Now.AddHours(-1);
4.跨域读取 Cookie
在默认情况下,Cookie与特定的域相关联,不同域的Cookie是无法共享的。如果你的站点有子域(例如demo.com、sales.demo.com和support.demo.com),则可以把Cookie同特定的子域相关联。为此,需要设置Cookie的Domain属性。
例如:
cookie.Domain = "sales.demo.com"; cookie.Domain = "support.demo.com";
这样,该Cookie就可用于主域(demo.com)、sales.demo.com 和 support.demo.com等多个子域了。
3.2.2 HtmlInputHidden隐藏域
隐藏域不会显示在用户的浏览器中,但我们可以像设置标准控件的属性那样设置其属性。当一个网页被提交给服务器时,隐藏域的内容和其他控制的值一块儿被送到HTTP Form集合中。
ASP.NET中的HtmlInputHidden或HiddenField控件提供了隐藏域的功能:
System.Web.UI.WebControls.HiddenField HiddenField1; HiddenField1.Value = "test"; //给隐藏域赋值 string str = HiddenField1.Value; //获得一个隐藏域的值
注 意
要使用隐藏域,就必须使用Http-Post方法提交互联网网页。尽管其名字是隐藏域,但它的值并不是隐藏的,我们可以通过“查看源代码”菜单找到它的值。
3.2.3 ViewState
实际上ViewState并不神秘,它就是一个Hidden字段,但是它实现了保存控件状态的功能;服务器端控件能够在多次请求期间保存状态完全靠它。我们可以用IE查看HTML源码,找到一个名为“__VIEWSTATE”的Hidden字段,其中有一大堆乱七八糟的字符,这就是页面的ViewState。实际上ViewState保存到客户端的一串字符串就是内部的ViewState通过某种方式序列化之后再经过Base64编码得来的。
获取信息格式:string value=ViewState[key];。
例如:
protected void Page_Load(object sender, EventArgs e) { if (ViewState["arrList"] != null) { PageArrayList = (ArrayList)ViewState["arrList"];//从ViewState获取数据 } else { //如果ViewState没有保存,则加载数据 PageArrayList = CreateArray(); } this.GridView1.DataSource = arrayList; this.GridView1.DataBind(); }
保存信息格式:ViewState.Add(key,value);。
例如:
protected void Page_PreRender(object sender, EventArgs e) { //可以在页面呈现之前保存或更改ViewState数据 ViewState.Add("arrList", PageArrayList); }
注 意
关于ViewState的了解大体有以下几点:
● 与隐藏域不同的是,在使用查看源代码功能时,ViewState属性的值是不可见的,它是被压缩和加密的。
● ViewState存在客户端,因此会减轻服务器的负担,是一种比较好的保存数据的方式。
● 由于ViewState本身的限制,只能保存可以序列化的对象。
● 不要在 ViewState 中存储大量信息,以免减慢传输的速度,加重服务器解析的负担。
● 通过很简单的方式就可以把 ViewState 里面的值获取出来,利用LosFormatter可以得到ViewState反序列化后的对象;所以ViewState在安全性方面还是比较差,建议不要存放比较机密和敏感的信息,尽管ViewState可以加密,但是由于ViewState要保存在客户端,天生就有安全性的隐患。
● 也可以关闭ViewState以提高性能。例如:EnableViewState="false"
3.2.4 查询字符串Request
查询字符串提供了一种简单而受限制的维护状态信息的方法,我们可以方便地将信息从一个网页传递给另一个网页,但大多数浏览器和客户端装置都把URL的长度限制在255个字符之内。此外,查询值是通过URL传递给互联网的,因此,在有些情况下,安全就成了一个大问题。
带有查询字符串的URL如下:
http://www.examples.com/list.aspx?categoryid=1&productid=101
在客户端请求list.aspx后,可以通过下面的代码来获取目录和产品信息:
protected void Page_Load(object sender, EventArgs e) { string categoryid="", productid=""; if ((Request["categoryid"] != null) && (Request["categoryid"]. ToString() != "")) { ViewState["categoryid"] = Request["categoryid"]; } if ((Request["productid"] != null) && (Request ["productid"].ToString() != "")) { ViewState["categoryid"] = Request["productid"]; } }
注 意
对于查询字符串,我们只能使用Http-Get来提交该互联网网页,否则就不能从查询字符串获得需要的值。
3.2.5 Application对象
Application 是一个共享对象,这意味着所有访问此应用程序的客户均可以看到这个Application对象的值,Application对象主要用于存储那些需要让所有用户共用的信息。如果没有使用程序强制释放,则Application对象将存在于整个应用程序的生存周期内。
在ASP.NET中,有一个应用程序池,其中保存了数个(或数十个)应用程序实例,每一次请求都会从池中取出一个实例来处理请求,在请求完毕之前,这个实例不会接受其他请求;这就出现了一个问题,同一时间可能存在多个应用程序,也就是多个线程,这些线程都存在访问Application的可能,所以在对Application中的对象进行处理的时候需要考虑线程同步的问题;实际上Application对象内部实现了一个线程锁,调用它本身的Add、Remove等方法的时候会自动调用加锁和解锁的操作,但是出于性能考虑,对于直接通过索引器或其他方式得到其中的对象并进行操作的过程,Application并没有自动处理线程同步,需要利用下列类似的代码来处理:
Application.Lock(); Application["Count"] = (int)Application["Count"] + 1; Application.UnLock(); Application["table_bgcolor"] = "#F5F9FF"; //表格背景
注 意
在调用了Lock之后,如果没有显式地调用Unlock,那么在这个请求结束的时候,Application对象会自动解锁,这样防止了死锁问题的产生,但是为了代码的健壮性,调用完Lock并且修改完毕后应该立即调用Unlock方法。
Application对象本质上就是一个Hash表,按照键值存放了对象,由于对象是全局并且存放在服务器上,而且存在多线程同时访问,所以,Application里面存放的应该是访问较多、修改较少并且是全局至少大部分功能会使用的数据,例如计数器或者数据库连接串等。
3.2.6 Session对象
Session 是什么呢?简单来说就是用户与网站服务器建立的一个连接,服务器给客户端分配一个编号。当一台 WWW 服务器运行时,可能有若干个用户正在浏览运行在这台服务器上的网站。当用户首次与这台 WWW 服务器建立连接时,他就与这个服务器建立了一个Session,同时服务器会自动为其分配一个 SessionID,用以标识这个用户的唯一身份。也就是说不同的客户端生成不同的Session对象。存储在对话状态变量中的数据,存在的周期较短。这个SessionID是由WWW服务器随机产生的一个由24个字符组成的字符串,我们会在下面的实验中见到它的实际样子。
1.Web.config文件中的Session配置
<sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" cookieless="false" timeout="20" />
这一段就是配置应用程序是如何存储Session信息的。让我们先看看这一段配置中所包含的内容的意思。sessionState节点的语法是这样的:
<sessionState mode="Off|InProc|StateServer|SQLServer" cookieless="true|false" timeout="number of minutes" stateConnectionString="tcpip=server:port" sqlConnectionString="sql connection string" stateNetworkTimeout="number of seconds" />
必须有的属性含义,如表3-3所示。
表3-3 属性及其含义

可选的属性含义如表3-4所示。
表3-4 可选的属性含义

2.Session的具体操作
Session的操作非常简单,就像使用一个变量一样去使用它。
//存储信息 Session["myname"] = "Mike"; //获得信息 myname = Session["myname"]; //清除Session Session.Clear(); //从Session状态集合中移除所有的键和值 Session.Abandon(); //取消当前Session会话
3.Session超时和莫名丢失的处理
原因:
● 改动global.asax、Web.config、bin目录里的东西,导致Web Application 重启。
● 有些杀病毒软件会去扫描你的Web.config文件,也会导致Session丢失。
● 服务器内存不足导致Session丢失。
● 程序内部有让Session丢失的代码。
● 程序有框架页面和跨域情况。
解决:
● 修改Web.config中timeout的时效时间。
● 建议让网站使用独立的应用程序池。
● IIS设置Session超时时间:网站属性→“主目录”→“配置”→“应用程序配置”→“选项”,重新设置会话超时时间,默认为20分钟。
● 在应用程序池上打开网站对应的应用程序池属性,将Web数量改为1。重启IIS。
● 在你的主页面里面嵌入一个框架页iframe,设置宽度和高度为0。在里面的加载页面的<head>里面加入<meta http-equiv="refresh" content="1080"> 这一句,意思就是每隔18分钟向服务器发送一次请求。刚好赶在Session失效之前。这样Session就永不失效了。
4.Session变量和Cookie的区别
信息的存储位置不同,保存的时间也不同。
超文本传输协议(HTTP)本质上是无状态的,也就是说,从网页客户端到网页服务端的所有请求之间没有任何关联。最初Cookie的作用就是作为这种无状态的补偿。
Cookie 是网页服务器存储在网页客户端硬盘上的文本文件。网页服务器向网页客户端请求存储一段信息,信息可以保存在Cookie 中。之后每当客户端向服务器申请一个页面时,就会将该信息发回服务端。
Session(会话)变量可以做同样的事情,但Session变量将在服务端为每个连接建立一个字典对象,这时使用的是服务端存储。根据浏览器和Session的状态对该站点做出的定义, Session可以真正地创建一个Cookie ,并使用一个标识符来存储对该字典对象的引用。Cookie可能会有一个按照年月来判断的作废日期,而Session 级别的变量在连接超时后就作废了。
3.2.7 示例项目:在线用户列表统计
本节结合上面讲述的知识点,实现一个很多网站经常见到的效果:在线用户的列表功能。(完整代码示例位置:光盘\code\ch03\02OnlineUser)
实现步骤:
(1)打开VS.NET,选择菜单“文件”→“新建项目”命令,在左边的”项目类型“面板中选择“Visual C#”选项,在右边的面板中选择“ASP.NET Web应用程序”选项,如图1-18所示。
(2)当网站启动时会触发Application_Start事件。
在Global.asax里面Application_Start事件(网站启动时)添加如下代码,实现创建一个用户的DataTable来保存每个访问网站的用户的信息。
protected void Application_Start(object sender, EventArgs e) { #region OnlineUsers try { DataTable userTable = new DataTable(); userTable.Columns.Add("SessionID"); userTable.Columns.Add("UserIP"); userTable.Columns.Add("Browser"); userTable.Columns.Add("OSName"); userTable.AcceptChanges(); Application.Lock(); Application["OnlineUsers"] = userTable; Application.UnLock(); } catch { } #endregion
(3)一个用户访问网站时会触发Session_Start事件。
当用户访问网站时,严格地讲应该是一个客户端浏览器访问网站时,在Session_Start中记录下该用户的客户端信息,存入userTable中:
protected void Session_Start(object sender, EventArgs e) { try { string seesionid = Session.SessionID; string UserIP = Request.UserHostAddress; HttpBrowserCapabilities bc = Request.Browser; string OSName = "Win2000"; switch (bc.Platform) //判断操作系统 { case "WinNT 5.1": case "WinXP": OSName = "Windows XP"; break; case "WinNT 5.0": OSName = "Win2000"; break; case "WinNT": OSName = "Win2003"; break; default: OSName = bc.Platform; break; } string Browser = bc.Type; DataTable userTable = (DataTable)Application["OnlineUsers"]; if (userTable == null) return; DataRow[] curRow = userTable.Select("SessionID='" + seesionid + "'"); if (curRow.Length == 0) { DataRow newRow = userTable.NewRow(); newRow["SessionID"] = seesionid; newRow["UserIP"] = UserIP; newRow["Browser"] = Browser; newRow["OSName"] = OSName; userTable.Rows.Add(newRow); userTable.AcceptChanges(); Application.Lock(); Application["OnlineUsers"] = userTable; Application.UnLock(); } } catch { } }
由于每个用户在访问时都会触发该事件,最终 Application["OnlineUsers"]将保存所有访问该网站的用户的信息。
注 意
如果是IE浏览器,则即使同一个机器访问,打开不同的IE进程访问,服务器也会认为是不同的用户客户端的访问,会触发不同的Session_Start。
(4)当用户离开网站时或者用户的会话终结时,会触发Session_End事件。
protected void Session_End(object sender, EventArgs e) { Hashtable onlineUsersHash = (Hashtable)Application["OnlineUsers"]; onlineUsersHash.Remove(Request.UserHostAddress); try { string seesionid = Session.SessionID; //获取当前SessionID DataTable userTable = (DataTable)Application["OnlineUsers"]; if (userTable == null) return; foreach (DataRow row in userTable.Select("SessionID='" + seesionid + "'")) { userTable.Rows.Remove(row); //移除该Session } userTable.AcceptChanges(); Application.Lock(); Application["OnlineUsers"] = userTable; Application.UnLock(); } catch { } }
在这个事件中,将同步把 userTable 中该用户的信息移除,以保持和实际用户的信息相一致。
(5)显示用户列表信息。
在项目中添加一个页面,并添加GridView控件用于显示用户列表信息,在页面中添加如下关键代码实现将内存中保存的用户列表信息展示出来。
DataTable userTable = null; try { userTable = (DataTable)Application["OnlineUsers"]; } catch { Response.Write("获得内存在线数据失败"); } if (userTable != null) { gridView.DataSource = userTable; gridView.DataBind(); }
运行效果如图3-3所示。

图3-3 在线用户列表
3.3 服务器和客户端数据交互
本节将为你介绍服务器与客户端数据交互的相关知识。
3.3.1 页面数据绑定全攻略
在做Web开发时,都会遇到将后台的数据显示在前台页面的情况,而在ASP.NET中显示数据的方式有很多种,这里总结了几种比较典型的应用示例。(完整代码示例位置:光盘\code\ch03\03WebDataBind)
1.在页面显示CS后台代码中的字符串变量
1)方法1:<%# %>
后台CS代码:
public partial class WebForm1 : System.Web.UI.Page
{
public string name = "ltp";
public string sex = "man";
public string old = "25";
protected void Page_Load(object sender, EventArgs e)
{
Page.DataBind(); //千万不能忘! <%# %>只有DataBind()后才有效
}
}
前台页面HTML代码:
<div> <p>姓名:<%# name %></p> <p>性别:<%# sex %></p> <p>年龄:<%# old %></p> </div>
注 意
● CS代码中的字符串变量必须是public的才可以被页面使用。
● 必须调用Page.DataBind()、<%# %>绑定才会有效。
2)方法2:<%= %>
后台CS代码:
public partial class WebForm1 : System.Web.UI.Page { public string name = "ltp"; public string sex = "man"; public string old = "25"; protected void Page_Load(object sender, EventArgs e) { } }
前台页面HTML代码:
<div> <p>姓名:<%= name %></p> <p>性别:<%= sex %></p> <p>年龄:<%= old %></p> </div>
注 意
<%= %>与<%# %>的区别在哪里?
<%= %>相当于Response.Write(),是放变量但是取出变量的值!
例如:实现两个字符串动态连接后得到的地址:
public string url = ConfigurationSettings.AppSettings["Url"];
ASP.NET页面动态得到系统变量的值组合成地址字符串:
<a href="<%=url%>/test.aspx">进入</a>
实现在页面里动态得到例如http://www.test.com/test.aspx这样的地址。
而<%# %> 专门用于数据绑定,可以绑定一些变量或者数据源中的东西,中间绑定是数据源的条目。而且要想让它起作用,必须调用DataBind()方法。
2.动态绑定服务器控件属性值
后台CS代码:
public partial class WebForm2 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Page.DataBind(); } }
前台页面HTML代码:
<div> <asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True"> <asp:ListItem Value="images\001.jpg" Selected="True">图1</asp:ListItem> <asp:ListItem Value="images\002.jpg">图2</asp:ListItem> <asp:ListItem Value="images\003.jpg">图3</asp:ListItem> </asp:DropDownList> <asp:Image ID="Image1" runat="server" ImageUrl="<%# DropDownList1.SelectedItem.Value %>"></asp:Image> </div>
通过选择DropDownList,可以实现动态地绑定图片控件的图片路径。
3.绑定Hashtable到DataList
后台CS代码:
public partial class WebForm3 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { Hashtable list = new Hashtable(); list.Add("ltp", "李天平"); list.Add("zs", "张三"); list.Add("ls", "李四"); this.DataList1.DataSource = list; this.DataList1.DataBind(); } } }
前台页面HTML代码:
<div> <asp:DataList ID="DataList1" runat="server"> <HeaderTemplate> <table width="200"> <tr> <td width="100">ENGLISH </td> <td> 中文</td> </tr> </table> </HeaderTemplate> <ItemTemplate> <table width="200"> <tr> <td> <%# ((DictionaryEntry)Container.DataItem).Key %> </td> <td><%#((DictionaryEntry)Container.DataItem).Value %></td> </tr> </table> </ItemTemplate> </asp:DataList> </div>
实现将Hashtable数据动态绑定到DataList。
4.页面绑定使用服务器方法
后台CS代码:
public partial class WebForm4 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { Hashtable list = new Hashtable(); list.Add("1", "1"); list.Add("2", "2"); list.Add("3", "3"); this.DataList1.DataSource = list; this.DataList1.DataBind(); } } public int GetSumCount(int n,int m) { return n + m; } }
前台页面HTML代码:
<div> <asp:DataList ID="DataList1" runat="server"> <HeaderTemplate> <table width="200"> <tr> <td width="100">数值1</td> <td> 数值2</td> <td>求和 </td> </tr> </table> </HeaderTemplate> <ItemTemplate> <table width="200"> <tr> <td width="100"><%# ((DictionaryEntry)Container.DataItem). Key %></td> <td><%# ((DictionaryEntry)Container.DataItem).Value %> </td> <td> <%# GetSumCount(int.Parse(((DictionaryEntry) Container.DataItem).Key.ToString()), int.Parse(((DictionaryEntry)Container.DataItem).Value.ToString()))%> </td> </tr> </table> </ItemTemplate> </asp:DataList> </div>
<%# GetSumCount(1,int.Parse(((DictionaryEntry)Container.DataItem).Key.ToString()))%>在页面中调用了后台CS服务端的方法GetSumCount进行计算。
5.将DataView绑定到DataList,并调用服务器方法计算
后台CS代码:
public partial class WebForm5 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string conString = "data source=127.0.0.1; initial catalog=codematic;user id=sa;password="; SqlConnection myConnection = new SqlConnection(conString); string strSQL = "SELECT * FROM P_Product"; DataSet ds = new DataSet(); myConnection.Open(); SqlDataAdapter adapter = new SqlDataAdapter(strSQL, myConnection); adapter.Fill(ds, "ds"); myConnection.Close(); DataView dv = ds.Tables[0].DefaultView; this.DataList1.DataSource = dv; this.DataList1.DataBind(); } } //计算差额的方法 public string odds(object a, object b) { return String.Format("{0:n}", ((decimal)a - (decimal)b)); } }
前台页面HTML代码:
<div> <asp:DataList ID="DataList1" runat="server"> <HeaderTemplate> <table> <tr> <td>商品</td> <td>价格</td> <td>促销价</td> <td>差额</td> </tr> </table> </HeaderTemplate> <ItemTemplate> <table> <tr> <td> <%# String.Format("{0:c}", (System.Data.DataRowView)Container.DataItem) ["Name"])%> </td> <td> <%# String.Format("{0:c}", ((System.Data.DataRowView)Container.DataItem) ["Price"])%> </td> <td> <%# String.Format("{0:c}", ((System.Data.DataRowView)Container.DataItem) ["VipPrice"])%> </td> <td> <%# odds(((System.Data.DataRowView)Container.DataItem)["Price"], ((System.Data.DataRowView)Container.DataItem)["VipPrice"])%> </td> </tr> </table> </ItemTemplate> </asp:DataList> </div>
动态绑定各字段数据,最后一列调用服务器方法odds计算两个字段的差额。
6.使用DataBinder类进行绑定
DataBinder类最主要的好处是会自动执行类型转换。
绑定格式:DataBinder.Eval(数据项的命名容器,数据字段名称,格式字符串)。
在 DataList、DataGrid 或 Repeater 等能够显示多笔数据的服务器控件的模板中,数据项的命名容器永远是Container.DataItem。
● Container:代表绑定到数据源的父控件(在此就是DataList、DataGrid或Repeater)。
● DataItem:代表父控件目前正在处理的数据记录。
上面的页面绑定代码可以替换为:
<div> <asp:DataList ID="DataList1" runat="server"> <HeaderTemplate> <table> <tr> <td>商品</td> <td>价格</td> <td>促销价</td> <td>差额</td> </tr> </table> </HeaderTemplate> <ItemTemplate> <table> <tr> <td> <%# DataBinder.Eval(Container.DataItem, "Name")%> </td> <td> <%# DataBinder.Eval(Container.DataItem, "Price", "{0:n}")%> </td> <td> <%# DataBinder.Eval(Container.DataItem, "VipPrice", "{0:n}")%> </td> <td> <%# odds(DataBinder.Eval(Container.DataItem, "Price"), DataBinder.Eval(Container.DataItem, "VipPrice"))%> </td> </tr> </table> </ItemTemplate> </asp:DataList> </div>
还有另一种写法:
<%# DataBinder.Eval(Container, "DataItem.Name")%>
7.数据邦定格式化输出
在DataGrid或DataList显示数据时,可以通过模板列绑定个性化数据显示:
<ItemTemplate> <table> <tr> <td> <asp:HyperLink ID="HyperLink1" runat="server" Font-Bold="True" Text='<%# DataBinder.Eval(Container.DataItem, "ForumName","『{0}』") %>' NavigateUrl='<%# "Forum.aspx?ForumID=" + DataBinder.Eval( Container.DataItem, "ForumID") %>' /> 绑定日期格式: <%# DataBinder.Eval(Container.DataItem,"AddedDate","{0:yyyy/MM/dd}")%> - <%# DataBinder.Eval(Container.DataItem, "AddedDate", "{0:HH:mm:ss}") %> </td> </tr> </table> </ItemTemplate>
其他类似的数据绑定方式:
<A href="<%# "PostMessage.aspx?Action=NewReply&TopicID=" + Request.QueryString["TopicID"] + "&QuoteReplyID=" + DataBinder.Eval(Container.DataItem, "ReplyID") %>">引用</A> <a href="RoleAssignment.aspx?UserID=<%# DataBinder.Eval(Container, "DataItem.UserID") %> &PageIndex=<%# DataGrid1.CurrentPageIndex %>"> <%# DataBinder.Eval(Container, "DataItem.UserName") %></a>
8.直接绑定执行javascript方法
在页面中直接通过绑定脚本来执行javascript方法。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm8.aspx.cs" Inherits="WebDataBind.WebForm8" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>绑定执行javascript</title> <script language="javascript"> function DeleteForum(id) { if (confirm('你确认要删除这个论坛及它下面所有的主题吗?')) { document.forms['Forums'].elements['paramID'].value = id; __doPostBack('DeleteForum', ''); } } function DeleteCategory(id) { if (confirm('你确认要删除这个类别和它的子论坛吗?')) { document.forms['Forums'].elements['paramID'].value = id; __doPostBack('DeleteCategory', ''); } } </script> </head> <body> <form id="form1" runat="server"> <div> <asp:DataList ID="DataList1" runat="server"> <ItemTemplate> <table> <tr> <td> <a href='<%# string.Format("javascript:DeleteCategory({0});", DataBinder.Eval(Container.DataItem, "CategoryID")) %>'>删除</a> <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl='<%# "javascript:DeleteForum(" + DataBinder.Eval( Container.DataItem, "ForumID") + ")" %>' Visible='<%# CanAdministerCategories %>' Text="删除" /> </td> </tr> </table> </ItemTemplate> …其他代码略
9.绑定表达式计算
在页面绑定数据时,可以直接绑定计算表达式。如:
<asp:DataList ID="DataList1" runat="server"> <ItemTemplate> <table> <tr> <td> <asp:Image ID="Image1" Visible='<%# DataBinder.Eval(Container.DataItem, "ImageUrl").ToString().Length > 0 %>' BorderWidth="0" runat="server"></asp:Image><br> <%=i++%> <%# sumval += decimal.Parse(DataBinder.Eval(Container, "DataItem.Price").ToString())%> </td> </tr> </table> </ItemTemplate> </asp:DataList>
3.3.2 Bind和Eval的区别
Eval()方法在运行时使用反射执行后期绑定计算,因此与标准的ASP.NET数据绑定方法Bind相比,性能明显下降。它一般用在绑定时需要格式化字符串的情况下。多数情况尽量少用此方法。
Eval方法是静态(只读)方法,该方法采用数据字段的值作为参数并将其作为字符串返回。
Bind方法支持读/写功能,可以检索数据绑定控件的值并将任何更改提交回数据库。
1.使用Eval方法
Eval方法可计算数据绑定控件(如GridView、DetailsView和FormView控件)的模板中的后期绑定数据表达式。在运行时,Eval方法调用DataBinder对象的Eval方法,同时引用命名容器的当前数据项。命名容器通常是包含完整记录的数据绑定控件的最小组成部分,如GridView控件中的一行。因此,只能对数据绑定控件的模板内的绑定使用Eval方法。
Eval方法以数据字段的名称作为参数,从数据源的当前记录返回一个包含该字段值的字符串。可以提供第2个参数来指定返回字符串的格式,该参数为可选参数。字符串格式参数使用为String类的Format方法定义的语法。
2.使用Bind方法
Bind方法与Eval方法有一些相似之处,但也存在着很大的差异。虽然可以像使用Eval方法一样使用Bind方法来检索数据绑定字段的值,但当数据可以被修改时,还是要使用Bind方法。
在ASP.NET中,数据绑定控件(如GridView、DetailsView和FormView控件)可自动使用数据源控件的更新、删除和插入操作。例如,如果已为数据源控件定义了 SQLSelect、Insert、Delete和Update语句,则通过使用GridView、DetailsView或FormView控件模板中的 Bind 方法,就可以使控件从模板中的子控件中提取值,并将这些值传递给数据源控件。然后数据源控件将执行适当的数据库命令。出于这个原因,在数据绑定控件的EditItemTemplate或InsertItemTemplate中要使用Bind函数。
Bind 方法通常与输入控件一起使用,例如由编辑模式中的 GridView 行所呈现的 TextBox控件。当数据绑定控件将这些输入控件作为自身呈现的一部分创建时,该方法便可提取输入值。
Bind方法采用数据字段的名称作为参数,从而与绑定属性关联,如下面的示例所示:
<EditItemTemplate> <table> <tr> <td align=right> <b>Employee ID:</b> </td> <td> <%# Eval("EmployeeID") %> </td> </tr> <tr> <td align=right> <b>First Name:</b> </td> <td> <asp:TextBox ID="EditFirstNameTextBox" RunAt="Server" Text='<%# Bind("FirstName") %>' /> </td> </tr> <tr> <td align=right> <b>Last Name:</b> </td> <td> <asp:TextBox ID="EditLastNameTextBox" RunAt="Server" Text='<%# Bind("LastName") %>' /> </td> </tr> <tr> <td colspan="2"> <asp:LinkButton ID="UpdateButton" RunAt="server" Text="Update" CommandName="Update" /> <asp:LinkButton ID="CancelUpdateButton" RunAt="server" Text="Cancel" CommandName="Cancel" /> </td> </tr> </table> </EditItemTemplate>
单击行的Update按钮时,使用Bind语法绑定的每个控件属性值都会被提取出来,并传递给数据源控件以执行更新操作。
3.使用DataBinder.Eval方法
ASP.NET提供了一个名为DataBinder.Eval的静态方法,该方法计算后期绑定的数据绑定表达式,并将结果格式化为字符串(可选)。利用此方法,可以避免许多在将值强制转换为所需数据类型时必须执行的显式强制转换操作。
例如,在下面的代码片段中,一个整数显示为货币字符串。使用标准的ASP.NET数据绑定语法,必须首先强制转换数据行的类型以便检索数据字段 IntegerValue。然后,这将作为参数传递String.Format方法:
<%#String.Format("{0:c}",((DataRowView)Container.DataItem) ["IntegerValue"])%>
将此语法与DataBinder.Eval的语法进行比较,后者只有3个参数:数据项的命名容器、数据字段名称和格式字符串。在模板化列表中(如DataList类、DataGrid类或Repeater类),命名容器始终是Container.DataItem。
<%#DataBinder.Eval(Container.DataItem,"IntegerValue","{0:c}")%>
格式字符串参数是可选的。如果它被忽略,则DataBinder.Eval将返回类型对象的值,如下面的示例所示:
<%#(bool)DataBinder.Eval(Container.DataItem,"BoolValue")%>
当对模板化列表中的控件进行数据绑定时,DataBinder.Eval特别有用,因为数据行和数据字段通常都必须强制转换。
3.4 ASP.NET编程中的技巧
本节将为你介绍一些ASP.NET编程中的技巧。
3.4.1 页面之间传值的7种方法
做 Web 开发,页面之间传值是最常用的操作了,例如向导注册、信息显示等。你是否一直就只在用GET方式的URL字符串的方式传值呢?其实有很多的方法实现页面之间传值的功能。这里总结一下。(完整代码示例位置:光盘\code\ch03\04WebApp\1PageValue)
1.get方式
发送页:
1.<a href="WebFormA2.aspx?sum=1">进入WebFormA2.aspx</a><br /> 2.<asp:TextBox ID="TextBox1" Text="litianping" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="进入WebFormA2.aspx" onclick="Button1_Click" />
后台CS代码:
protected void Button1_Click(object sender, EventArgs e) { Response.Redirect("WebFormA2.aspx?name=" + TextBox1.Text); }
接收页:
protected void Page_Load(object sender, EventArgs e) { this.TextBox1.Text = Request["name"]; //this.TextBox1.Text=Request.Params["name"]; //this.TextBox1.Text=Request.QueryString["name"]; }
2.使用内存变量Session和Application
发送页:
protected void Button1_Click(object sender, EventArgs e) { Session["name"] = this.TextBox1.Text; //Application["name"]=this.TextBox1.Text; Server.Transfer("WebFormB2.aspx"); }
接收页:
protected void Page_Load(object sender, EventArgs e) { this.TextBox1.Text = (string)Session["name"]; //this.TextBox1.Text = (string)Application["name"]; }
Application实质上是整个虚拟目录中所有文件的集合,如果想在整个应用范围内使用某个变量值,则Application对象将是最佳的选择。
3.post方式
发送页:
<form id="form1" action="WebFormC2.aspx" method="post"> <INPUT name="txtname" type="text" value="litianping"/> <INPUT type="submit" value="提交到WebFormC2.aspx"/> </form>
接收页:
if (Request.Form["txtname"] != null) { TextBox1.Text = Request.Form["txtname"]; }
注 意
● <form>中不能带runat="server",否则会不起作用。
● <form>中的method="post"。
在runat="server"调用post方法可以这样调用:
<form id="Form1" method="post" runat="server"> <input id="btnTransfer" type="button" onclick="post();" runat="server" value="提交到WebFormC2.aspx"> <input type="text" runat="server" id="SourceData" value="litianping"> </form> <form id="forPost" method="post"> <input type="text" runat="server" id="txtname" value="litianping"> </form> <script language="javascript"> function post() { forPost.action="WebFormC2.aspx"; forPost.submit(); } </script>
4.静态变量
发送页:
//定义一个公共变量 public static string strname = ""; protected void Button1_Click(object sender, EventArgs e) { strname = this.TextBox1.Text; Server.Transfer("WebFormD2.aspx"); //这里用的是Server.Transfer }
接收页:
protected void Page_Load(object sender, EventArgs e) { this.TextBox1.Text = WebFormD1.strname; }
5.Context.Handler获取控件
发送页:
<asp:TextBox ID="TextBox1" Text="litianping" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="进入WebFormE2.aspx" onclick="Button1_Click" /> // Button1_Click 事件代码 Server.Transfer("WebFormE2.aspx");
接收页:
protected void Page_Load(object sender, EventArgs e) { //获取post过来的页面对象 if (Context.Handler is WebFormE1) { //取得页面对象 WebFormE1 poster = (WebFormE1)Context.Handler; //取得控件 TextBox1.Text=((TextBox)poster.FindControl("TextBox1")).Text; //this.TextBox1.Text = poster.TextBox1.Text;//如果TextBox1是public }
6.Context.Handler获取公共变量
发送页:
//定义一个公共变量 public string strname = "litianping"; protected void Button1_Click(object sender, EventArgs e) { Server.Transfer("WebFormF2.aspx"); }
接收页:
protected void Page_Load(object sender, EventArgs e) { //获取post过来的页面对象 if (Context.Handler is WebFormF1) { //取得页面对象 WebFormF1 poster = (WebFormF1)Context.Handler; this.TextBox1.Text = poster.strname; } }
Context 提供对整个当前上下文(包括请求对象)的访问。你可以使用此类共享页之间的信息。
7.Context.Items变量
发送页:
protected void Button1_Click(object sender, EventArgs e) { Context.Items["name"] = TextBox1.Text; Server.Transfer("WebFormG2.aspx"); }
接收页:
protected void Page_Load(object sender, EventArgs e) { if (Context.Handler is WebFormG1) { this.TextBox1.Text = Context.Items["name"].ToString(); } }
3.4.2 get与post方法的区别
HTTP 定义了与服务器交互的不同方法,最基本的方法是 get 和 post。表单提交中 get和post方式具体有什么区别呢?
● get从服务器上获取数据,post向服务器传送数据。
● get把参数数据队列加到提交表单的Action属性所指的URL中,值和表单内各个字段一一对应,在URL中可以显示出来。post通过HTTP post机制,将表单内各个字段与其内容放置在HTML Header内一起传送到Action属性所指的URL地址中。不会在URL中显示出来,用户看不到这个过程。
● 对于get方式,服务器端用Request.QueryString获取变量提交的值,对于post方式,服务器端用Request.Form获取提交的数据。
● 由于受到URL长度的限制,get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS5 中为 100KB,最大可以达到2MB。
● get安全性非常低,post安全性稍高。get和post的具体使用方式,可以参照3.4.1节的示例代码。
3.4.3 ASP.NET服务器控件和HTML控件的区别
ASP.NET之所以开发方便快捷,关键在于它有一组强大的控件库,包括ASP.NET服务器控件、ASP.NET用户控件、HTML服务器控件和HTML控件等。但是,这些控件之间到底有什么区别呢?
1.HTML控件
就是我们通常所说的 HTML 语言标记元素,用来标记文本,表示文本的内容,只能在客户端通过JavaScript和VBScript等程序语言来控制。
如:<input id="Button" type="button" value="button" />。
2.HTML服务器控件
其实就是在HTML控件的基础上加上runat="server"所构成的控件。它们的主要区别是运行方式不同,HTML控件运行在客户端,而HTML服务器控件是运行在服务器端的。
在默认情况下,ASP.NET中的HTML元素作为文本进行处理,并且不能在服务器端代码中引用这些元素。若要使这些元素能以编程方式进行访问,则可以通过添加 runat="server"属性表明应将HTML元素作为服务器控件进行处理。还可以设置元素的 id 属性,使你可以通过编程方式引用控件。然后可以通过设置属性(Attribute)来声明服务器控件实例上的属性(Property)参数和事件绑定。
当ASP.NET网页执行时,会检查标注有无runat 属性,如果没有,那么HTML标注就会被视为字符串,并被送到字符串流等待送到客户端,客户端的浏览器会对其进行解释。如果HTML标注有runat="server" 属性,则Page对象会将该控件放入控制器,服务器端的代码就能对其进行控制,等到控制执行完毕后再将HTML服务器控件的执行结果转换成HTML标注,然后当成字符串流发送到客户端进行解释。
如:<input id="Button" type="button" value="button" runat="server" />。
注 意
HTML服务器控件必须放在具有runat="server"属性的form中。
3.ASP.NET服务器控件
是Web Forms编程的基本元素,也是ASP.NET所特有的。它会根据客户端的情况产生一个或者多个HTML控件,而不是直接描述HTML元素,它们不必一对一地映射到 HTML服务器控件,而是相当于一种抽象控件。
如:<asp:Button ID="Button2" runat="server" Text="Button"/>。
那么ASP.NET服务器控件和HTML服务器控件有什么区别呢?
● ASP.NET服务器控件提供更加统一的编程接口,例如每个ASP.NET服务器控件都有Text属性。
● 自动浏览器检测。控件可以检测浏览器的功能并呈现适当的标记。这样程序员可以把更多的精力放在业务上,而不用去考虑客户端的浏览器是IE还是Firefox,或者是移动设备。
● ASP.NET服务器控件可以保存状态到ViewState里,这样页面在从客户端回传到服务器端或者从服务器端下载到客户端的过程中都可以保存。
● 事件处理模型不同,HTML标注和HTML服务器控件的事件处理都是在客户端的页面上,而 ASP.NET 服务器控件则是在服务器上,例如:<input id="Button4"type="button" value="button" runat="server"/>是HTML服务器控件,此时我们单击此按钮,页面不会回传到服务器端,原因是我们没有为其定义鼠标单击事件。<input id="Button4" type="button" value="button" runat="server" onserverclick="test"/>我们为HTML服务器控件添加了一个onserverclick事件,单击此按钮页面会发回服务器端,并执行test(object sender, EventArgs e)方法。<asp:Button ID="Button2" runat="server" Text="Button" />是ASP.NET服务器控件,并且我们没有为其定义click事件,但是当我们单击时,页面仍会发回到服务器端。
由此可见:HTML 标注和 HTML 服务器控件的事件是由页面来触发的,而ASP.NET服务器控件则总是由页面把Form发回到服务器端,由服务器来处理。
4.ASP.NET什么时候用服务器控件,什么时候用HTML控件
服务器控件服务器端运行,和服务器有数据交互的时候用比较好。HTML控件客户端运行,客户端判断客户行为的时候用比较好。服务器控件是提交到服务器端执行后返回给客户端的,所以,服务器控件会比较占用服务器资源。根据不同需求选择用不同的控件。
ASP.NET 控件是服务端控件,响应服务端事件。HTML 控件是客户端控件,响应客户端事件。简单来说,对一些不需要和服务器交互,仅仅改变客户端展示的情况,强调客户端的应用,可以考虑使用 HTML 控件,会给用户更好的用户体验,减少服务器负担,比如说单击一个按钮改变文字的颜色,只是针对用户机器本身的,不会发送数据包给远程的服务器。而需要把数据发送回服务器进行处理,和服务器有交互的功能的情况下可以使用服务器控件,开发会比较有效率,例如进行数据库操作等,页面提交后页面会有刷新。做的项目可以两者结合使用,优势互补,最好不要一味用服务器端控件,会加重服务器负担的。
3.4.4 Server.Transfer和Response.Redirect的区别
1.Server.Transfer
用于把处理的控制权从一个页面转移到另一个页面,在转移的过程中,没有离开服务器,内部控件(如request、session等)保存的信息不变,因此,你能从页面A跳到页面B而不会丢失页面A中收集的用户提交信息。比如:Server.Transfer("WebForm2.aspx")。
2.Response.Redirect
发送一个HTTP响应到客户端浏览器,告诉客户端跳转到另一个页面,客户端再发送跳转请求到服务器。使用此方法时,将无法保存所有的内部控件数据,页面A跳转到页面B,页面B将无法访问页面A中Form提交的数据。
比如:Response.Redirect("WebForm2.aspx")或者Response.Redirect("http://www.cnnas.com/")。
3.两种都是将用户重定向到另一页,或者说进行页面的跳转
二者的区别和优缺点如下。
● Server.Transfer使用服务器端方法将用户重定向到另一页;Response.Redirect将用户从浏览器重定向到另一页。
● Server.Transfer 的优点是可以将页面参数方便地传递到指定页面。服务器只是将上下文传输到另一页。你可以共享页之间的页上下文信息,不会占用较多的HTTP请求,可以减少客户端对服务器的请求,因此可以减轻服务器的压力,使你的服务器运行更快。Response.Redirect需执行额外的往返过程,这会影响性能。通常在做某个指向外部的Web链接的时候用。
● Server.Transfer跳到别的页面后,浏览器显示的URL地址不会改变,用户的浏览器不知道在进行传输,因此不更新浏览器的历史记录。如果用户刷新此页,则可能会产生意外的结果。有时会造成误会,当然也有些场合需要这样的效果。通常在程序内部各个form之间跳转的时候使用。
● 注意,Server.Transfer 只能在同一服务器端的同一站点运行,所以你不能用Server.Transfer 将用户重定向到另一服务器上的站点。要重定向到服务器以外的站点,只有Response.Redirect 能办到。
● 不能使用 Server.Transfer 重定向到.asp 或.asmx页,而使用Response.Redirect可以。
所以,在我们编程时,网站系统内部进行跳转时尽量采用 Server.Transfer,定向外部地址时可以用Response.Redirect。
3.4.5 刷新页面的方法汇总
1.实现页面自动刷新
把如下代码加入到<head>区域中:
<meta http-equiv="refresh" content="5">
其中“5”指每隔5秒刷新一次页面。
2.页面自动跳转
<meta http-equiv="refresh" content="5;url=http://www.maticsoft.com">
指隔5秒后跳转到http://www. Maticsoft.com页面,如果是当前页面即为自动刷新了。3.setTimeout实现
<body onload="setTimeout('history.go(0)',5000)"> <body onload="setTimeout('this.location.reload();',5000)">
也可以通过脚本实现:
<script language="javascript"> function chang() { document.location='http://www.maticsoft.com'; } setTimeout(chang,5000); //定时执行 //setInterval(chang,5000); //间隔执行 //setTimeout("document.location='http://www.maticsoft.com'",3000) </script>
4.按钮刷新的N种方法
<input type=button value=刷新onclick="history.go(0)"> <input type=button value=刷新onclick="location.reload()"> <input type=button value=刷新onclick="location=location"> <input type=button value=刷新onclick="location.assign(location)"> <input type=button value=刷新onclick="document.execCommand('Refresh')"> <input type=button value=刷新onclick="window.navigate(location)"> <input type=button value=刷新onclick="location.replace(location)"> <input type=button value=刷新onclick="window.open( 'http://www.maticsoft.com','_self')"> <input type=button value=刷新 onClick=document.all.WebBrowser.ExecWB(22,1)>
(完整代码示例位置:光盘\code\ch03\04WebApp\5refresh)
3.4.6 页面事件控制
“尽量让用户少点一次鼠标,尽量减少用户的操作时间”已经成为产品设计中提高用户体验的常理,所以我们在开发的时候,很多时候就是靠这些细节来打动用户使用我们的产品的。(完整代码示例位置:光盘\code\ch03\04WebApp\6KeyEvent)
1.设置默认焦点
当页面显示后,光标的焦点自动定位到用户要开始录入的第一个文本框,不需要用户自己再单击鼠标进行选择。
在ASP.NET 1.1中可以这样设置:
<body onload="document. getElementById('TextBox1').focus();"> <body onkeydown="document.all['TextBox1'].focus();">
在ASP.NET 2.0中可以这样设置:
<form id="Form1" method="post" runat="server"
defaultfocus="TextBox2" defaultbutton="btnOK">
2.回车文本框焦点自动跳转
当用户输入完第1个文本框时,通过回车可以让光标自动跳到第2个文本框。
<body onload="document.all['TextBox1'].focus();"> <form id="Form1" onkeydown='if(event.keyCode==13&&event. srcElement.type=="text")event.keyCode=9' method="post"runat="server"> <div> <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><br> <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox><br> <asp:TextBox ID="TextBox3" runat="server"></asp:TextBox><br> <asp:TextBox ID="TextBox4" runat="server"></asp:TextBox><br> <asp:TextBox ID="TextBox5" runat="server"></asp:TextBox><br> <asp:Button ID="btnOK" runat="server" Text="提交" onclick="btnOK_Click" /> </div> </form> </body>
3.按快捷键提交(如“Ctrl+Enter”)
用户录入完毕后,无须用鼠标再去单击“提交”按钮,通过快捷键即可实现提交功能。
<head runat="server"> <title>Ctrl+Enter提交</title> <script> function save() { if(window.event.keyCode==13&&window.event.ctrlKey) { document.form1.submit(); } } </script> </head> <body onkeydown="save();"> <form id="form1" runat="server" method="post" action="result.aspx"> <div> <textarea rows="4" name="cword" cols="49"></textarea> <input type="submit" value="提交"> </div> </form> </body> </html>
4.回车自动提交
用户录入完毕后,通过回车即可实现提交。
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>回车自动提交</title> <script language="javascript"> function SubmitClick(button) { if (event.keyCode == 13) { event.keyCode=9; event.returnValue = false; document.getElementById(button).click(); } } </script> </head> <body onload="document. getElementById('txtUserName').focus();"> <form id="form1" runat="server" onkeydown='if(event.keyCode== 13&&event.srcElement.type=="text")event.keyCode=9' > <div> <asp:TextBox ID="txtUserName" runat="server"></asp:TextBox> <asp:TextBox ID="txtPassword" runat="server"></asp:TextBox> <asp:Button ID="btnOK" runat="server" Text="Button" onclick="btnOK_Click" /> </div> </form> </body> </html> protected void Page_Load(object sender, EventArgs e) { txtPassword.Attributes.Add("onkeydown", "SubmitClick('btnOK');"); } protected void btnOK_Click(object sender, EventArgs e) { Response.Write("OK"); }
3.4.7 在URL中传递中文的解决方案
在做 ASP.NET 开发时,URL 中如果将中文字符作为参数值进行传递,则 QueryString得到的值可能会出错。简单地说,比如下面这个URL:WebForm1.aspx?parm1=中国&parm2=中国人。在 Request.QueryString 时,parm1 和 parm2 得到都是“中国”,有的时候还是之类的乱码。这说明在URL中传输中文字符是有问题的。那如何在URL中传递中文字符呢?(完整代码示例位置:光盘\code\ch03\04WebApp\7UrlEncode)
1.通过设置Web.config 指定ASP.NET 应用程序默认的请求和响应编码
<system.web> <globalization requestEncoding="gb2312" responseEncoding="gb2312" culture="zh-CN" fileEncoding="gb2312" /> </system.web>
2.通过设置Aspx页面head指定ASP.NET 页面的请求和响应编码
<head runat="server"> <title></title> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> </head>
3.在传递中文之前,对将要传递的中文参数进行编码,在接收时再进行解码
编码方式1:HttpUtility.UrlEncode
<asp:HyperLink ID="HyperLink1" runat="server" NavigateURL='<%# "B.aspx?name=" + HttpUtility.UrlEncode("中国人", System.Text.Encoding.GetEncoding("GB2312")) %>' >传给B</asp:HyperLink>
编码方式2:Server.UrlEncode
protected void Button1_Click(object sender, EventArgs e) { string Name = "中国人";// 进行传递 Response.Redirect("B.aspx?name=" + Server.UrlEncode(Name)); }
编码方式3:JavaScript编码传输
<head runat="server"> <title>方式3</title> <script language="JavaScript"> function GoUrl() { var name = "中国人"; location.href = "B.aspx?name="+escape(name); } </script> </head> <body onload="GoUrl()">
接收上面3种编码方式传过来的值并解码:
protected void Page_Load(object sender, EventArgs e) { if (Request["name"] != null) { string Name = Request["parm1"]; Response.Write(Server.UrlDecode(Name)); } }
一般来说,设置Web.config文件就可以了。但是如果你用JavaScript调用Web Service方法的话(往Web Service里面传递中文参数),那么设置Web.config文件有时候无效。
4.让机器环境支持中文
如果以上方法还不行,中文传递还是有问题,则可以试试下面的方法。
(1)将页面用记事本打开,然后选中“另存为”并选择“UTF-8”作为编码方式,而不是默认的ANSI。
(2)打开注册表,确保 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Inetinfo\Parameters\FavorDBCS值为0。
(3)修改该数值后,必须运行IISRESET以重新启动IIS服务。
3.4.8 增强用户体验的一些技巧
1.轻松实现页面提交时,显示“提交中”等待效果
由于 Web 开发的特点,有的时候提交数据量较大或计算量较大的页面,需要等待很长的时间,整个页面都刷新成白的了,给用户的感受不是很好。有的时候提交后,页面反映慢,用户以为没有提交成功,所以又提交一次,从而造成重复提交的情况。所以,我们来实现一种简单的“正在提交,请稍候”的效果,在显示提交时,界面上其他的东西都不允许单击,鼠标变为“沙漏”形状,这样可以避免重复提交。
当然现在Ajax已经可以通过<asp:UpdateProgress>来实现这样的效果了,但我们这种方式在不能用Ajax的情况下同样可以实现,并且很简单。
先看效果,如图3-4所示。

图3-4 等待效果
实现步骤:(完整代码示例位置:光盘\code\ch03\04WebApp\8Runing)
(1)在页面上添加一个要显示的效果层,用于显示提示内容。
<div id="runing" runat="server" style="z-index: 12000; left: 0px; width: 100%; cursor: wait;position: absolute; top: 0px; height: 100%"> <table width="100%" height="100%"> <tr align="center" valign="middle"> <td> <table width="200" height="120" bgcolor="Gray" style="filter: Alpha(Opacity=70); color:White"> <tr align="center" valign="middle" > <td> <div id="Clocktimes"></div><br> 正在提交<br> 请稍候.... </td> </tr> </table> </td> </tr> </table> </div> … … <asp:Button ID="btnOk" runat="server" Text=" 提交" OnClick="btnOk_Click" />
(2)在页面添加计时脚本,用于显示执行的时长:
<script language="javascript"> var times=0; function tick() //用于显示执行的时长 { times++; var min=Math.floor(times/60); var scend=times-min*60; document.getElementById('Clocktimes').innerHTML = min+'分'+scend+' 秒'; } </script>
(3)给提交按钮添加客户端脚本:
protected void Page_Load(object sender, EventArgs e) { this.btnOk.Attributes.Add("onclick", "javascript:document.getElementById('runing').style.visibility= 'visible';window.setInterval('tick()',1000);"); }
让按钮在单击后,用客户端脚本把图层显示出来,并执行计时脚本开始计时。
(4)重载Page的OnPreRenderComplete方法,在代码中添加如下代码:
override protected void OnPreRenderComplete(EventArgs e) { runing.Style.Add("visibility", "hidden"); }
在进行 PreRenderComplete(在页生命周期的预呈现阶段完成,但在呈现页之前)时,把图层隐藏掉(注:ASP.NET1.1下需要用OnPreRender)。
这样,当单击btnOk按钮时,将首先显示如图3-4所示的等待效果,并显示等待的时长,等按钮的处理完成了,提示层效果自动消失。
2.给个提示再删除
有时候为了防止误操作,需要提醒用户做出最终的确认,也给系统的使用带来很好的用户体验,所以,当用户单击按钮时给用户先弹出一个提示窗口,然后让用户来确认再做处理。(完整代码示例位置:光盘\code\ch03\04WebApp\8Delete)
方式1:在CS代码里为按钮绑定事件响应脚本。
protected void Page_Load(object sender, EventArgs e) { Button1.Attributes.Add("onclick", "if(confirm('是否真的要删除?')) {document.getElementById('Hidden1').value='你要返回的值'} else{return false;}"); }
方式2:在CS代码里为按钮绑定事件方法,在JS里实现响应处理。
<script language="javascript"> function getMessage() { var Flag=confirm("是否真的要删除?"); if (Flag) { alert("删除操作"); // window.event.returnValue =false; } else { alert("取消操作"); } } </script>
后台代码:
protected void Page_Load(object sender, EventArgs e) { this.Button2.Attributes.Add("onclick", "return getMessage();"); } protected void Button2_Click(object sender, EventArgs e) { Response.Write("<script defer>alert('Button2_Click!');</script>"); }
注 意
● 本例中,Button2会先执行onclick,再执行服务端方法Button2_Click。
● 方法2其实是方法1的一个变形,只是把脚本事件代码放到页面处理而已。
方式3:监测页面中所有链接和按钮进行提示。
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>删除提示</title> <script language="JavaScript"> function delete_confirm(e) { if(event.srcElement.outerText=="删除" || event.srcElement.value=="删除") event.returnValue=confirm("删除后将不能恢复,你确认执行删除操作么?"); } document.onclick=delete_confirm; </script> </head> <body> <form id="form1" runat="server"> <a href="WebForm1.aspx" >删除</a> <asp:Button ID="Button1" runat="server" Text="删除" /> </form> </body> </html>
3.4.9 XHTML与HTML的区别
如果你开发程序是从VS 2003开发一路跟到VS 2008的话,那么你会发现有很多不同,这里不是指开发工具使用的不同,而是指技术上的不同。本章主要讲ASP.NET页面,所以,这里指的不同就是ASP.NET页面的不同。闲话少说,让我们先有个直观的感性认识吧。
1.用VS 2003和VS 2008创建的页面有什么不同
用VS 2003创建的页面:
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="VS2003.WebForm1" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <HTML> <HEAD> <title>WebForm1</title> <meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1"> <meta name="CODE_LANGUAGE" Content="C#"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form2" method="post" runat="server"> </form> </body> </HTML>
用VS 2008创建的页面:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="vs2008.WebForm1" %> <!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> </div> </form> </body> </html>
通过对比你发现有什么不一样吗?
<!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" >
我们看到,在VS 2008创建的页面中这两句好像有很大的变化,那这两句话到底是什么意思呢?
DOCTYPE是Document Type(文档类型)的简写,用来说明你用的XHTML或者HTML是什么版本。DOCTYPE 声明的作用是指出阅读程序应该用什么规则集来解释文档中的标记。不正确的DOCTYPE声明经常导致网页不正确显示,或者导致它们根本不能显示。我们对比发现VS 2003用的是HTML 4.0 Transitional,VS 2008用的是XHTML 1.0 Transitional。
再看<html>,在VS 2008中多了xmlns=http://www.w3.org/1999/xhtml,这是什么意思呢?xmlns是XML NameSpace的缩写,代表的是文档的命名空间。它的作用是标明在所处的这个页面内所用到的标记属于哪个命名空间,在不同的命名空间可以有相同的标记表示不同的含义,所以有必要写明标记是属于哪个命名空间的。
例如,如果需要使用符合XML规范的XHTML文档,则应该在文档中的<html>标签中至少使用一个xmlns属性,以指定整个文档所使用的主要命名空间:
<html xmlns=”http://www.w3.org/1999/xhtml”>
虽然在大多数情况下,绝大多数 XHTML 作者都不需要定义多个命名空间,但是你仍然有必要理解存在着多个命名空间,以便在需要选择将基于某个 DTD 的内容嵌入其他DTD 定义的内容中时,可以管理多个命名空间。
从以上分析可以看出,在VS 2008中,我们创建页面文档类型是XHTML,命名空间用的也是http://www.w3.org/1999/xhtml。那么XHTML和HTML到底有什么不同呢?
HTML的英文全称是 Hyper Text MarkUp Language,中文叫做“超文本标记语言”,是一种基本的Web网页设计语言。而XHTML是一个基于XML的标记语言,看起来与HTML有些相像,其实,XHTML是一种为适应XML而重新改造的HTML。当XML越来越成为一种趋势时,就出现了这样一个问题:如果我们有了XML,我们是否依然需要HTML呢?为了回答这个问题,1998年5月W3C的HTML工作组主席Steven Pemberton在旧金山的工作会议讨论决定:需要。我们依然需要使用HTML。因为大多数的人们已经习惯了使用HTML来作为他们的设计语言,而且,已经有数以百万计的页面是采用HTML编写的。
所以,XHTML 1.0是一种在HTML 4.0基础上优化和改进的新语言,是基于XML应用的。XHTML是一种增强了的HTML,它的可扩展性和灵活性将适应未来网络应用更多的需求。再简单点理解,XHTML可以认为是XML版本的HTML,为符合XML要求,XHTML在语法上要求更严谨些。
2.XHTML与HTML相比有哪些特点
● XHTML 解决 HTML 语言所存在的严重制约其发展的问题。HTML 发展到今天存在 3 个主要缺点:不能满足现在越来越多的网络设备和应用的需要,比如手机、PDA、信息家电都不能直接显示HTML;由于HTML代码不规范、臃肿,浏览器需要足够智能和庞大才能够正确显示HTML;数据与表现混杂,这样你的页面要改变显示,就必须重新制作HTML。因此HTML需要发展才能解决这个问题,于是W3C又制定了XHTML,XHTML是HTML向XML过渡的一个桥梁。
● XML是Web发展的趋势,所以人们急切地希望加入XML的潮流中。XHTML是当前替代HTML4标记语言的标准,使用XHTML 1.0,只要你小心遵守一些简单规则,就可以设计出既适合XML系统,又适合当前大部分HTML浏览器的页面。也就是说,你可以立刻设计使用 XML,而不需要等到人们都使用支持 XML 的浏览器。这个指导方针可以使Web平滑地过渡到XML。
● 使用XHTML的另一个优势是它非常严密。当前网络上的HTML的糟糕情况让人震惊,早期的浏览器接受私有的 HTML 标签,所以人们在页面设计完毕后必须使用各种浏览器来检测页面,看是否兼容,往往会有许多莫名其妙的差异,人们不得不修改设计以便适应不同的浏览器。
● XHTML能与其他基于XML的标记语言、应用程序及协议进行良好的交互工作。
● XHTML是Web标准家族的一部分,能很好地用在无线设备等其他用户代理上。
● 在网站设计方面,XHTML可助你去掉表现层代码的恶习,帮助你养成标记校验来测试页面工作的习惯。
3.XHTML与HTML的主要区别
1)XHTML要求正确嵌套
以下是正确的嵌套:
<p>ASP.NET深入体验与<strong>实战精要</strong>。</p>
以下是错误的嵌套:
<p>ASP.NET深入体验与<strong>实战精要</p></strong>
2)XHTML所有元素必须关闭
在HTML中,比如<p>标记,你可以不写</p>。但是在XHTML里,必须要求写关闭标记。
比如:
<p>实战精要。
应该写成:
<p >实战精要。</p>
有些空元素,在XHTML里的写法是在“>”之前加空格和斜杠。比如<br>,应该写成<br />。
以下是空元素的例子:
<meta http-equiv="content -type" content="text/html; charset=UTF-8" / > <br /> <hr /> <img src = "/images/1.jpg" alt = "test" />
3)XHTML区分大小写
HTML不区分大小写,但是XHTML是区分大小写的。XHTML的所有标记和属性都要小写。
比如:
<IMG SRC = "/images/1.jpg" Alt = "asp.net" />
应该写成:
<img src = "/images/1.jpg" alt = "asp.net" />
4)XHTML属性值要用双引号
HTML并不强制要求属性值加双引号。比如你可以写:
<table cellspacing = 0>
但在XHTML里,应该写成:
<table cellspacing ="0">
5)XHTML用id属性代替name属性
HTML 很多元素,比如 a、frame、iframe、img 和 map,都有 name 属性。在 XHTML里是要废除的,而用id属性取而代之。
比如:
<img src="1.gif" name="asp.net" />
应该写成:
<img src="1.gif" id="asp.net" />
6)XHTML特殊字符的处理
如&在XHTML里应该写成&。
比如:
You & Me
应该写成:
You & Me
还有如果内嵌JavaScript代码,则在XHTML里应该写成:
<script type="text/javascript">//<![CDATA[ ... //]]></script>
4.浏览器的智慧
既然我们用VS 2003和VS 2008开发的页面,在文档类型上是不同的,那么浏览器在解析的时候如何来处理呢?对于不严格的XHTML文档,浏览器会怎么做呢?是不是会弹出一个对话框,提示你:“这个网站的开发人员技术不过关,写的不是正宗的 XHTML 文件,咱不显示”?显然,如果浏览器做成这个样子,估计倒贴钱也不会有人用的。
其实,针对上面讲到几个不同的写法,浏览器有自己的处理方法。
● XHTML要求正确嵌套:如果你没有嵌套,则浏览器会试图帮你嵌套。
● XHTML要求所有元素必须关闭:如果你没有关闭,则浏览器会试图帮你关闭。
● XHTML区分大小写:你非要写成大写,浏览器帮你转换成为小写。
● 属性值要用双引号:你非要不加,浏览器帮你加。
● 用id属性代替name属性:你非要用 name 也可以。
● 特殊字符的处理:You & Me也好,You & Me也好,浏览器都能读取。
虽然我们以前没有完全按照标准的XHTML编写页面,但页面照样显示得很正常,那是因为浏览器替我们擦的屁股。但谁也说不准哪天浏览器发脾气了,不陪你玩了,你就傻眼了。况且现在的浏览器众多,也不是每个都脾气那么好。所以,还是严谨一点,遵照XHTML标准自己把代码写好吧。
3.5 打造自己的页面基类PageBase
在 ASP.NET 项目开发时,我们总会遇到一些各个页面共性的操作,如页面风格、权限验证、错误处理、记录日志等,这些操作在每个页面访问时也许都需要做处理,为了保证页面风格的一致性,以及减少重复代码的编写,我们需要引入基类页的概念,即定义一个基类页,让所有的页面都继承这个基类。
代码示例: (示例位置:光盘\code\ch03\WebPageBase)
//<summary> //页面基类 //</summary> public class PageBase : System.Web.UI.Page { protected override void OnInit(EventArgs e) { base.OnInit(e); this.Load += new System.EventHandler(this.Page_Load); this.Error += new System.EventHandler(this.Page_Error); } }
定义页面共有的一些属性,来获取页面参数信息,供派生类使用。例如:
public virtual string Name { get { if ((Request["name"] != null) && (Request["name"].ToString() != "")) { return Request.QueryString["name"].Trim(); } return ""; } }
定义页面共有错误处理方法Page_Error,例如:
protected void Page_Error(object sender, System.EventArgs e) { string errMsg = ""; Exception currentError = Server.GetLastError(); errMsg += "系统发生错误:<br/>" + "错误地址:" + Request.Url.ToString() + "<br/>" + "错误信息:" + currentError.Message.ToString() + "<br/>"; Response.Write(errMsg); Server.ClearError();//要注意这句代码的使用,清除异常。 }
加入页面的权限检查处理,例如:
//<summary> //页面访问权限ID //</summary> public virtual int PermissionID { get { return -1; } } public AccountsPrincipal CurrentPrincipal { get { if (Context.User.Identity.IsAuthenticated) { AccountsPrincipal user = new AccountsPrincipal( Context.User.Identity.Name); return user; } return null; } } //<summary> //得到当前登录用户对象实例 //</summary> public LTP.Accounts.Bus.User CurrentUser { get { if (CurrentPrincipal == null) { return null; } if (Session["UserInfo"] == null) { LTP.Accounts.Bus.User currentUser = new LTP.Accounts.Bus.User(CurrentPrincipal); Session["UserInfo"] = currentUser; } return Session["UserInfo"] as LTP.Accounts.Bus.User; } } //在页面加载时进行权限检查 { private void Page_Load(object sender, System.EventArgs e) //可以定义自己的权限检查代码 if (Context.User.Identity.IsAuthenticated) { AccountsPrincipal user = new AccountsPrincipal( Context.User.Identity.Name); if ((PermissionID != -1) && (!user.HasPermissionID(PermissionID))) { Response.Clear(); Response.Write("<script defer>window.alert('你没有权限进入本页!') </script>"); Response.End(); } } else { FormsAuthentication.SignOut(); Session.Clear(); Session.Abandon(); Response.Clear(); Response.Write("<script defer>window.alert('你没有权限进入本页或 当前登录用户已过期!\\n请重新登录或与管理员联系!'); parent.location='" + virtualPath + "/" + loginPage + "'; </script>"); Response.End(); } }
我们在开发具体页面的时候,可以继承这个类,实现具体的功能,对这些共性的功能就可以交给基类去统一处理。会节省很多开发时间,提高开发效率,同时,降低维护成本,增强可扩展性。
例如:
public partial class WebForm1 : PageBase { protected void Page_Load(object sender, EventArgs e) { } }
另外,在实际开发时,我们也可以再按照功能页面划分,继承基类创建不同的派生基类,实现不同功能定义的页基类,例如:
public class BizFormBase : PageBase { public override string Name { get { return "ltp";// base.Name; } } public class AgentBase : PageBase { } public class ProductBase : PageBase { }
从面向对象的角度看,基类页与普通的基类,继承类设计其实区别并不大,都要在基类中编写公用的属性方法,并通过虚函数、事件等方式让派生类重写或响应。所不同的是基类页的设计过程受到所在环境的约束。在 WinForm 环境下,我们可以预先定义好窗体的公用元素,如工具条、默认的表格及DataSource控件等。而到了ASP.NET下的Web Forms,则无法实现界面一级的继承,同时加入了状态管理等要求。
为了更好地维护和扩展,降低模块耦合,可以单独建立一个类库项目,将该类放在类库项目中,Web项目可以通过引用该类库来实现对该类的继承。
本章常见技术面试题
✧ 页面事件的执行顺序?
✧ 页面之间传值有哪几种方法?
✧ ASP.NET服务器控件和HTML控件的区别?
✧ Server.Transfer和Response.Redirect的区别?
✧ XHTML与HTML的区别?
常见面试技巧之如何做好自我介绍
作为进入新公司的第一步,“请你自我介绍一下”这道题 90%以上的用人单位都会问,面试者事先最好以文字的形式写好背熟。其实面试者的基本情况用人单位已掌握,考这道题的目的是考核面试者的语言表达能力、逻辑能力,以及诚信度。
回答要点:这个问题主要是在测试你对自己的了解与人格成熟度,因此在回答中所谈的每一个生活经验,都必须能帮助主考官更了解现在的你,尤其是有关这份工作所需要的重要特质。
高明的回答:“我大学重考一次,那段经验让我培养出更好的挫折忍受力及毅力。”这就对了!
白痴的回答:拉拉杂杂地叙述家庭背景,要不只说学历(这点对方看履历表就行了),或者回答:“我没什么特别的”、“我没什么优点”;若未能适时表现你对自己的深刻了解及重要的能力特质,那可会错失良机。
所以,面试者在自我介绍时要注意以下几点:
✧ 自我介绍的内容要与个人简历相一致。
✧ 语气平稳,说话有条理、清晰、层次分明,避免东一句,西一句。
✧ 表述方式上尽量采用口语化,避免像背课文,让考官听着打瞌睡。
✧ 注意内容简洁,切中要害,不谈和应聘工作无关的内容。
✧ 着重突出能力和素质与所应聘职位的相关性。
✧ 忌抱怨、嘲笑前任公司或领导,那只能会给你的人品打折扣。
✧ 作为技术开发人员,应重点介绍自己过去做过什么项目,创造过什么成就,积累了哪些经验。在介绍自己的项目经验的时候,应该先介绍项目的总体框架,然后说明你在项目中担当的角色及主要负责的工作。
✧ 经验欠缺者,应着重体现自己的学习能力,最好不要靠说,而是通过一个例子来体现。公司不可能全招牛人和高手,也需要具有潜力的应届生,也就更看重新手的学习能力。
✧ 自我介绍最好不要超过2分钟,最好把握在1分钟左右。
✧ 最后一点,虽不重要但也很关键:尽量面带微笑,面带真诚。面试官也是人,良好的印象能让你更容易成功。
本章小结
本章主要介绍了关于ASP.NET开发中常常遇到的一些相关问题,并没有对ASP.NET进行全面的讲解,因为在 ASP.NET 开发中很大一部分工作是基于控件的使用。而控件使用往往是一种技术含量较低的重复劳动,且控件往往会随着开发工具的版本更新而过时。并不能作为一个人长期的核心竞争力保存下来。所以,这里更多地还是从项目和经验的角度讲了一些实际项目技术要点,也期望读者可以在日常的开发中更注重对提高自己核心竞争力技术的积累和总结。