restart ASP.NET(1)
半年的时间一直没有去接触.NET的东西,自身的.NET的基础都一直很薄弱,最近想开始重新拾起来。虽然微软这两年的技术出得很多,但我也不回去跟风都样样都沾,导致样样都不精通,我还是一步一个脚印,从头开始搞起,如果自己有一个比较坚实的基础,那么学起微软其他的知识来也能很快的融会贯通。
目前我个人看得是一本很基础的书,ASP.NET 3.5揭秘卷(1),里面的知识讲的还是很详细的,也提供了不少代码示例。我尽量每篇文章都去详细解释一些知识点,当然也有不少来自网络的引用,可能有些地方我理解有误,希望能够有朋友能够及时指出。希望能够通过这一阶段学习,重新拾起它,同时也希望此系列文章能够对和我基础一样薄弱的同学有用。
第一章 Framework概述
1.1 ASP.NET和.NET Framework
ASP.NET是.NET Framework的子集,.NET Framework相当于.NET的虚拟机,构建ASP.NET页面依赖于.NET Framework。.NET Framework由两部分组成:框架类库(Frame work Class Library)和公共语言运行库。
1.11 框架类库
1.命名空间(namespace)
一个命名空间下定义了一组具有相似功能或者类和接口,一般情况下从命名空间的名称就可以判断出这个命名空间下的类和接口所具有的功能。
每个aspx.cs页面都引用了一些默认的命名空间:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
默认的命名空间在.NET framework的web.config文件中列出,它的路径为:C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config。
<pages>
<namespaces>
<add namespace="System" />
<add namespace="System.Collections" />
<add namespace="System.Collections.Specialized" />
<add namespace="System.Configuration" />
<add namespace="System.Text" />
<add namespace="System.Text.RegularExpressions" />
<add namespace="System.Web" />
<add namespace="System.Web.Caching" />
<add namespace="System.Web.SessionState" />
<add namespace="System.Web.Security" />
<add namespace="System.Web.Profile" />
<add namespace="System.Web.UI" />
<add namespace="System.Web.UI.WebControls" />
<add namespace="System.Web.UI.WebControls.WebParts" />
<add namespace="System.Web.UI.HtmlControls" />
</namespaces>
<controls>
<add tagPrefix="asp" namespace="System.Web.UI.WebControls.WebParts" assembly="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</controls>
</pages>
使用命名空间有四种方式(以System.IO为例):
(1)直接在方法中使用完整的命名空间
(2)在aspx页面中导入命名空间<%@ Import Namespace="System.IO" %>
(3)在aspx.cs页面中引用命名空间using System.IO;
(4)在项目的web.config文件中进行配置
<pages>
<namespaces>
<add namespace="System.IO"/>
</namespaces>
</pages>
2.程序集(assembly)
(1)程序集是.NET Framework中最基本的部署、安全和版本控件单位。通俗的说,一个程序集物理表现为一个dll文件。
(2)程序集分两类:私有的和共享的。私有的程序集只能用于一个应用程序,而共享的程序集能用于同一个服务器端上的所有应用程序。共享程序集位于全局程序集缓存(GAC)中,全局程序集的物理地址在C:\Windows\Assembly目录中。
(3)默认情况下,ASP.NET应用程序引用全局程序缓存中最常用的程序集:
□ mscorlib.dll □ System.Web.Services.dll
□ System.dll □ System.Xml.dll
□ System.Configuration.dll □ System.Drawing.dll
□ System.Web.dll □ System.EnterpriseServices.dll
□ System.Data.dll □ System.Web.Mobile.dll
//在.NET Framework 3.5中还引用了如下程序集:
□ System.Web.Extensions
□ System.Xml.Linq
□ System.Data.DataSetEntensions
(4)使用.NET Framework中的类时,应用程序必须引用包含这个类的程序集,并且必须导入这个类所关联的命名空间。
大部分情况下,最常用的程序集Visual Studio在创建项目时已经自动的帮我们引用了。如果要使用其他的程序集,必须显示地添加对程序集的引用,就是引用程序集的dll。
(5)当为一个项目添加引用后,会在web.config文件中显示该引用,比如一个web项目添加了对System.Message.dll(System.Message命名空间)的引用,在web.config文件中将会显示(当然也可以手工写代码创建对程序集的引用,在web.config中也是以如下这种形式):
<system.web>
<compilation debug="false">
<assemblies>
<add assembly="System.Message, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7llD50A3A"/>
</assemblies>
</compilation>
</system.web>
1.12 公共语言运行库(Common Language Runtime)
.NET Framework的CLR用于执行程序的代码,当编写ASP.NET程序时,编译器不会把源代码直接编译成机器码(机器码简单的说就是0和1,无论什么语言,当程序写好后,都必须转化成机器认识的语言,才能够执行,机器只认识机器码),C#编译器会把代码转换成MSIL语言(.NET Framework只理解一种语言——MSIL),应用程序的IL代码实际上在每次应用程序运行的时候都会被重新编译为本机代码。
作为一种代码指令平台,Microsoft .NET比微软公司先前推出的其他技术平台要来得更为复杂。由于.NET提供了对多种编程语言以及(在理论上说)多重平台的支持,这就需要在传统的两个代 码层添加一个中间代码层。在这里,传统的两层分别是源代码层和编译后的本机代码层。新加的代码层给.NET平台带来了额外的灵活性,不过,反过来却又增加 了系统的复杂性。此外,由于这一新代码层的出现,一连串的新型应用程序部署选项也首次展现在了程序员的面前。
在Microsoft .NET框架内,应用程序可以用好多种高级程序语言编写、创建,例如VB.NET、C#乃至COBOL .NET等等都可以编写.NET应用程序。而通过每一种遵守.NET规范的编程语言所编写的程序代码首先都得通过一种初始编译步骤从源代码变成.NET的 公共标准语言:MSIL(微软中介语言:Microsoft Intermediate Language)。MSIL自身是一种完整的、和对象相关的语言,只有它才可能创建出应用程序。.NET应用程序是以MSIL的形式出现的,只有在程序执行的时候才通过即时编译器(JIT)被编译为本机代码。
1.2 ASP.NET控件
1.21 控件概览
◇标准控件——表单元素
◇验证控件——验证表单元素
◇Rich控件——日历、文件上传、交替显示的广告横幅和多步骤用户向导
◇数据控件——关联数据库,显示数据
◇导航控件——菜单、树视图、面包屑导航
◇登录控件——注册、登录、修改密码表单
◇HTML控件——把任何HTML标签转换为服务器端控件
所有的.NET服务器控件都是.NET的一个类,都在System.Web.UI.WebControls命名空间下,它们有3个特点:①.包含在asp标签中②.有runat=”server”属性③.有ID属性。asp标签声明了控件的命名空间(System.Web.UI.WebControls),runat=”server”属性表明它在服务器端执行,ID作为唯一的标识符。html、css、javascript作为web的通用元素,本质上浏览器只识别这3种元素,服务器控件之所以称作服务器控件,它是在通过服务器执行,服务器最终把它们的标签、属性、事件转换成浏览器可识别的元素。服务器端控件是对这3种元素的封装,但最终又转成这些元素,这颇有点脱裤子放屁的味道。有些复杂的控件,比如数据控件、日历控件、导航控件,对于css、javascript功底不是很深的同学来说是一种偷懒的方式。这些复杂的控件会在在客户端生成不少html,css,javascript代码,会严重损失页面的性能,控件越复杂,性能越差劲。
1.22 HTML控件
ASP.NET Framework接受所有的HTML标签加入runat=”server”属性,这个属性会HTML标签转换成服务器控件。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="HelloWorld.Web.Test" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
this.show.InnerText = "Starcraft II:show me the money.";
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div runat="server" id="show">
</div>
</form>
</body>
</html>
1.23 理解和处理控件事件
大多数的ASP.NET控件支持一个或多个事件,服务器端的事件都是在服务器端触发的。当点击一个服务端按钮时,事件并没有马上发生,服务器端会等待包含这个页面的回发,直到这个页面会发给服务端时,服务端才会去处理和调用按钮事件,如下例:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="HelloWorld.Web.Test" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected void Button1_Click(Object sender, EventArgs args)
{
this.Label1.Text = "After Clicking";
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:Button ID="Button1" runat="server" Text="Click" OnClick="Button1_Click"/>
<br />
<asp:Label ID="Label1" runat="server" Text="Before Clicking"></asp:Label>
</form>
</body>
</html>
上面代码中的Button1_Click方法包含两个参数,第一个参数表示引发事件的控件(事件源),在这里就是Button1;第二个参数表示附加的事件关联的事件信息。ASP.NET控件的事件的签名是相同的。
下面的代码演示了第二个参数的含义,只有当第二个参数不是默认的EventArgs时,就可以知道有附加的事件信息传递给了处理程序。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ShowEventArgs.aspx.cs" Inherits="HelloWorld.Web.chapter1.ShowEventArgs" %>
<script runat="server">
protected void Image_Click(Object sender, ImageClickEventArgs args)
{
string txtCoor = "X=" + args.X.ToString() + ", Y=" + args.Y.ToString();
coordinate.Text += txtCoor;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ImageButton ID="img" runat="server" ImageUrl="~/images/photo.png" OnClick="Image_Click"/>
<br />
<asp:Label ID="coordinate" runat="server" Text="坐标:"></asp:Label>
</form>
</body>
</html>
1.24 视图状态
http是无状态的协议,每次发起请求时,http不会记住以前的请求状态,它会把它当成全新的一次请求就行处理。但是在下面的代码中,每点击一次Button,页面会向服务器请求一次,并且刷新,Label的数值却每次都会递增。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ShowViewState.aspx.cs" Inherits="HelloWorld.Web.chapter1.ShowViewState" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected void btnAdd_Click(object sender, EventArgs args)
{
Label1.Text = ((Int32.Parse(Label1.Text)) + 1).ToString();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Button ID="Add" runat="server" Text="Add Count" OnClick="btnAdd_Click"/>
<asp:Label ID="Label1" runat="server" Text="0"></asp:Label>
</div>
</form>
</body>
</html>
这里主要的原因是ASP.NET对所有的服务器控件都提供了视图状态,在浏览器中,我们查看Source Code时,可以发现其中有一个名叫__VIEWSTATE的隐藏域,这个隐藏域包含了Label1的Text属性,不仅仅是Label1,只要在这个页面中的控件的属性都保存在这个隐藏域的value中。
<input type="hidden" value="/wEPDwUJNjUxNDcwNzM5D2QWAgIED2QWAgIDDw8WAh4EVGV4dAUBMmRkZCvsx12FOkolPHrlfy3zi04UBlB+" id="__VIEWSTATE" name="__VIEWSTATE">
当页面回传到服务器时,ASP.NET Framework对这个value字符串进行分解,重建所有保存在视图状态中的所有属性值。默认情况下ASP.NET Framework的每个控件都启用了ViewState,在<%@ Page %>指令中加入Trace=”true”属性,可以启用跟中功能,我们可以查看每个控件“消费”了多少个ViewState。关于ViewState,也是一个不小的话题,它也非常容易被误解,关于ViewState的深入理解,请参考真正理解ViewState(TRULY Understanding ViewState)。
在ASP.NET 2.0中还有两个玩意有点和ViewState类似:
(1)Event Validation,仔细查看源代码,一个隐藏域id,name都为__VIEWSTATE,另一个隐藏域id,name都为__EVENTVALIDATION。
ASP.NET 2.0 增加了一个新特性: Event Validation. 这个特性会对 PostBack 的值进行验证,确保是合法的值。其实现原理是在页面 Render 的时候,ASP.NET 引擎会对控件的可能的值以及控件的 UniqueID 进行 hash 计算,得到一个值。页面里所有需要回发的控件的这些计算值就组成了一个列表,组合后放在隐藏字段 __EVENTVALIDATION 中。在页面回发后,会对这个字段的内容进行解包,然后重新计算对比 hash 值是否一致。这个做法的好处是能够防止一些模拟的 post 攻击,但也有一个不方便的地方,就是有时候如果需要用 javascript 修改页面里的一些内容,则回发后不会被当作合法的数据,而抛出一个异常。
另外,如果页面非常大或者网速缓慢,用户在还没有下载到 __EVENTVALIDATION 这个字段的时候就点下 submit,导致回发数据不完整,也会导致异常的发生。
解决这个问题的办法是在 Page 的 Directive 里面禁用 EventValidation,目前还没有针对单独控件进行禁用的办法。
(2)ControlState。
它用于保存(自定义)控件的关键信息。就算页面或者控件的ViewState被关闭它还能起作用,弥补了ViewState能被禁止的不足。不过使用ControlState稍显复杂,我们需要自己序列化复杂对象进行存储。下面的代码演示了如何在ControlState中保存和读取简单字符串:
PageStatePersister.ControlState = "哼哼哈伊";
Response.Write(PageStatePersister.ControlState.ToString());
1.3 ASP.NET页面
1.31 动态编译
当创建一个ASP.NET页面时,其实是在创建一个.NET类的源码,创建一个System.Web.UI.Page类的实例,ASP.NET页面的所有内容(包含所有的脚本和HTML)都会在首次运行时编译到一个.NET的类中,这个类保存在类似\windows\Mircrosoft .NET\Framework\v2.0.50727\Temporary ASP.NET Files文件夹下。当再次对同一个页面发起请求时,ASP.NET页面不需要重新编译,服务器会自发的去调用这个.NET类。除非对ASP.NET页面进行了修改,才会重新对ASP.NET页面重新编译,编译器会先删除掉原来的.NET类,然后产生一个新的.NET类。
1.32 控件树
如果ASP.NET页面中包含很多控件,而很多控件之间又存在包含嵌套关系,那么我们可以把一个ASP.NET页面看成一个控件树,其根节点就是页面本身,页面有一个ID为_Page。在每个控件树中都有一个HtmlForm的实例——form1,它包含所有的其他窗体控件作为自己的子控件。
1.33 代码隐藏(code behind)
在ASP或是PHP页面中都杂糅了页面显示和业务逻辑,既包含前端代码又包含后台处理,看起来非常混乱。ASP.NET的code behind技术把这两者进行了分离,在aspx页面中展示页面,在aspx.cs页面中处理后台逻辑。
1.34 ASP.NET页面的生命周期
了解ASP.NET页面的生命周期非常重要,我们可以在合适的生命周期阶段变下代码达到某些预期效果。如果开发自定义控件,则必须熟悉页面生命周期,从而正确地初始化控件,使用视图状态数据填充控件属性以及运行所有控件行为逻辑。在面试中,生命周期的理解和描述也是经常考的地方。关于ASP.NET页面的生命周期详情可以参考:ASP.NET 页生命周期概述。
(1) PreInit
使用该事件来执行下列操作:
-
- 检查IsPostBack属性来确定是不是第一次处理该页。
- 创建或重新创建动态控件。
- 动态设置主控件。
- 动态设置Theme属性。
- 读取或设置配置文件属性值。
(2) Init
在所有控件都已初始化且已应用所有外观设置后引发。使用该事件来读取或初始化控件属性。
(3) InitComplete
由 Page 对象引发。使用该事件来处理要求先完成所有初始化工作的任务。
(4) PreLoad
如果需要在 Load 事件之前对页或控件执行处理,请使用该事件。在 Page 引发该事件后,它会为自身和所有控件加载视图状态,然后会处理 Request 实例包括的任何回发数据。
(5) Load
Page 在 Page 上调用 OnLoad 事件方法,然后以递归方式对每个子控件执行相同操作,如此循环往复,直到加载完本页和所有控件为止。使用 OnLoad 事件方法来设置控件中的属性并建立数据库连接。
(6) 控件事件
使用这些事件来处理特定控件事件,如 Button 控件的 Click 事件或 TextBox 控件的 TextChanged 事件。
(7) LoadComplete
对需要加载页上的所有其他控件的任务使用该事件。
(8) PreRender
在该事件发生前:
-
-
Page 对象会针对每个控件和页调用 EnsureChildControls。
-
设置了 DataSourceID 属性的每个数据绑定控件会调用 DataBind 方法。有关更多信息,请参见下面的数据绑定控件的数据绑定事件。
-
页上的每个控件都会发生 PreRender 事件。使用该事件对页或其控件的内容进行最后更改。
(9) SaveStateComplete
在该事件发生前,已针对页和所有控件保存了 ViewState。将忽略此时对页或控件进行的任何更改。使用该事件执行满足以下条件的任务:要求已经保存了视图状态,但未对控件进行任何更改。
(10)Render
这不是事件;在处理的这个阶段,Page 对象会在每个控件上调用此方法。所有 ASP.NET Web 服务器控件都有一个用于写出发送给浏览器的控件标记的 Render 方法。
如果创建自定义控件,通常要重写此方法以输出控件的标记。不过,如果自定义控件只合并标准的 ASP.NET Web 服务器控件,不合并自定义标记,则不需要重写Render 方法。有关更多信息,请参见开发自定义 ASP.NET 服务器控件。
用户控件(.ascx 文件)自动合并呈现,因此不需要在代码中显式呈现该控件。
(10)Unload
该事件首先针对每个控件发生,继而针对该页发生。在控件中,使用该事件对特定控件执行最后清理,如关闭控件特定数据库连接。对于页自身,使用该事件来执行最后清理工作,如:关闭打开的文件和数据库连接,或完成日志记录或其他请求特定任务。
1.35 Page.IsPostBack属性
Page.IsPostBack属性用于检测页面是否已经回传给服务器,或者可以称为页面是否首次加载。这样能够避免某些控件在每次回传时都重新初始化或绑定数据。
1.36 调试
我们编写应用程序时,常常要花大量的时间进行调试。调试程序也是编程人员应该掌握的基本技巧。关于Visual Studio调试的技巧网上有很多文章,在google搜索中搜索关键字Visual Studio调试技巧,你能找到你想要的文章。
在<%@ Page %>指令中加入Debug="true"属性,可以启动当前页面的调试,如果在运行当前页面时,出现异常会有错误信息显示在页面上。在我们创建一个web网站时,在web.config文件中,默认启动了所有页面的调试,<compilation debug="true">。在调试位于远程服务器上的程序时,需要禁用自定义错误(custom error),这也是出于安全性考虑。
<system.web>
<compilation debug="true" />
<customeErrors mode="off" />
</system.web>
由于安全性和性能问题,将网站发布时,应该禁止调试,禁止自定义错误和跟踪。在你的产品服务器上,将以下元素添加到machine.config文件的system.web部分中:<deployment retail="true">。
对于一个项目来说,你不可能通过设定起始页按F5键进行调试,原因是:各个网页间的关联性太强,要验证的的东西也很多。在调试时很难进行(实际上在我做的项目中根本不能进行)。
那么,我们来用另外一种调试方法:
菜单栏→调试→进程→选中:w3wp.exe(它是系统进程,记得在【显示系统进程】复选框上打钩,否则找不到该进程)→附加→【选择要调试的程序类型】里一般选择第一个CRL和第三个Script→确定
下面就是给你要调试的asp.net网页加断点,然后在IE中打开你要调试的项目的网址,按照流程走到你要调试的网页处,就会发现在断点处停住了......下面就让我们开始尽情的调试吧!
追加:
......也许你会郁闷的发现弹出一个窗口:无法显示进程,计算机调试管理器服务被禁用。
这种几率小的到了蚂蚁踩死象的程度,竟然被我遇到了。(一般装完.NET后就会默认启动,所以才能对.NET项目进行调试) 。
解决方法:
打开控制面板→服务→Machine Debug Manager,把这个服务启动设成自动,启动一下就可以了(不会启动的按下机箱上的重启键)。