重拾web开发-ASP.NET复习
关于ASP.NET的知识太多了,这里只列出一些问题留作自己下次思考并随时会补充一些知识点。
页生命周期
关于asp.net的页面生命周期先看两幅我收藏的图片,第一幅图片有点瑕疵,第二幅图片信息量少了点,但结合两张图片能说明很多东西。
再提几个自己比较关心的问题:
页面声生命周期在整个http请求中的位置?
控件的事件执行顺序是什么?
控件什么时候进行数据绑定的?
ViewState的TrackViewState是什么时候开始的?
什么时候加载ViewState保存的值的?
控件是如何追赶生命周期的?只针对动态创建的控件?
服务器控件
关于服务器控件同样也提几个问题留给自己思考(答案)
什么是控件树?
html控件,html服务器控件以及asp.net的web服务器控件的区别?
ASP.NET是如何处理runat="server"?
服务器控件的OnClick是怎么处理的?
什么是嵌套控件的转发事件?
ViewState和回传值(PostData)
照例我们来看一个例子:一个页面我们在页面加载的时候绑定DropDownList,另外还有一个Button没有绑定任何处理事件只是希望点击它的时候能够回发页面:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="test3.aspx.cs" Inherits="test.test3" %> <!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:DropDownList ID="DropDownList1" runat="server" EnableViewState="True"> </asp:DropDownList> <asp:Button ID="Button1" runat="server" Text="PostBack" /> </div> </form> </body> </html>
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { this.DropDownList1.DataSource = QueryDatabase(); this.DropDownList1.DataBind(); } } private List<string> QueryDatabase() { List<string> result = new List<string>(); result.Add("北京"); result.Add("上海"); result.Add("深圳"); result.Add("安徽"); result.Add("广东"); return result; }
运行这个页面我们发现一切运行很正常,每次回发后DropDownList的数据仍然存在并且仍然显示的是上次选择的值。看我们的代码if(!IsPostBack)这行,这表示我们只是在第一次页面加载的时候绑定了DropDownList,之所以能够一切正常,通过前面对ViewState的介绍不难理解是因为页面Load的时候TrackViewState()已经启动了,这时候DropDownList里的数据已经通过序列化和反序列化以及LoadViewState()和SaveViwState()方法在回传之间进行了保存。但是如果这个下拉列表绑定的数据很大,那么每次页面回发来回都要携带大量的_ViewState信息,这在低带宽下是很影响速度的。于是我么考虑通过禁用DropDownList的EnableViewState属性而是每次都重新绑定数据(衡量这两者方式之间的性能)来避免开销,当然这时候像上面那样只在第一次加载页面时候绑定数据是不可行的了,去掉if(!IsPostBack)!貌似可以,但是会发现一个问题就是每次回发后下拉列表显示不再是上次选择的值了。为什么呢?很多人包括之前的我都会认为是因为我们禁用了DropDownList的ViewState的原因,那让我们在OnLoad中每次重新绑定数据并将EnableViewState属性设为True来试试(真实环境要这样做的话那就是造成性能的双重损失了)。结果发现上面的问题依然存在,每次回发后下发列表显示的将是”北京“,不管你之前选择的是什么。这又是为什么?原来在页面回传的时候还有一些东西并不是通过ViewState来保存的,而是通过另外一个键值对的玩意,它叫做回传值(PostData),默认是以Post请求回传的,我们可以修改为Get方式,在form表单中添加method="get":
<form id="form1" runat="server" method="get">
我们可以发现浏览器的url发生了变化,之所以向=像下面这样一大堆是因为进行了加密。
http://localhost:5738/test3.aspx?__VIEWSTATE=%2FwEPDwUJMzkwNTA5NDc2D2QWAgIDD2QWAgIBDxAPFgIeC18hRGF0YUJvdW5kZ2QQFQUG5YyX5LqsBuS4iua1twbmt7HlnLMG5a6J5b69BuW5v%2BS4nBUFBuWMl%2BS6rAbkuIrmtbcG5rex5ZyzBuWuieW%2BvQblub%2FkuJwUKwMFZ2dnZ2dkZGSNKet80L43lwcKiYK8hHkQraKPiqkyOu7uuQGF0c73yQ%3D%3D&__EVENTVALIDATION=%2FwEWBwLM2oqDDQK0tbT8CwK7n8DiBQLS0NCwBQK6hPj0CgLMv%2FT7CwKM54rGBrqd0YO1IQK9DNW9Us7zGuPCFrnql5ep1WBFw2V1eKTq&DropDownList1=%E6%B7%B1%E5%9C%B3&Button1=PostBack
那么为什么我们在OnLoad中绑定DropDownList回发的时候无法获取回发的值呢?我们回头看下那张页面生命周期的图,处理回传数据(PostData)的方法是ProcessPostData()调用了两次,一次是在页面Load事件之前,一次是在Load之后。对于静态的控件来说(我们代码中那样)将回传数据加载到控件只会发生在第一次ProcessPostData中,所以之所以下拉列表会忘记上次的选择是因为Load发生在ProcessPostData()之后,加载回发数据的时 候DropDownList还没有ListItem。而对于在OnLoad中动态创建的控件来说还有一次ProcssPostData的机会,所以解决上面问题的方法就是将数据绑定提前到ProcessPostData()之前比如OnInit中:
protected void Page_Load(object sender, EventArgs e) { //if (!IsPostBack) //{ // this.DropDownList1.DataSource = QueryDatabase(); // this.DropDownList1.DataBind(); //} } private List<string> QueryDatabase() { List<string> result = new List<string>(); result.Add("北京"); result.Add("上海"); result.Add("深圳"); result.Add("安徽"); result.Add("广东"); return result; } protected override void OnInit(EventArgs e) { this.DropDownList1.DataSource = QueryDatabase(); this.DropDownList1.DataBind(); base.OnInit(e); }
至于到底是每次重新绑定控件还是利用ViewState的性能更高或者更满足场景要看具体情况了。
控件的”追赶“
上面的例子虽然解决了我们遇到的问题:滥用ViewState带来的性能损失以及DropDownList忘记了上次的选择。但是通过提前绑定数据和禁用控件的ViewState带来了另一个问题,因为禁用了DropDownList的EnableViewState属性将导致我们的SelectedIndexChanged无法触发。注意将DropDownList的AutoPostBack属性设为True,否则不会立即回发要等到下次提交以及要意识到有PostData的存在,不要干扰你的判断,之所以强调指点是我发现一个很”奇怪“的现象,看下面的例子:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="test3.aspx.cs" Inherits="test.test3" %> <!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" method="get"> <div> <asp:DropDownList ID="DropDownList1" runat="server" EnableViewState="False" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"> </asp:DropDownList> <asp:Button ID="Button1" runat="server" Text="PostBack" /> </div> </form> </body> </html>
protected void Page_Load(object sender, EventArgs e) { //if (!IsPostBack) //{ // this.DropDownList1.DataSource = QueryDatabase(); // this.DropDownList1.DataBind(); //} } private List<string> QueryDatabase() { List<string> result = new List<string>(); result.Add("安徽"); result.Add("北京"); result.Add("上海"); result.Add("深圳"); result.Add("广东"); return result; } protected override void OnInit(EventArgs e) { this.DropDownList1.DataSource = QueryDatabase(); this.DropDownList1.DataBind(); base.OnInit(e); } protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e) { //说明SelectedItem不是通过ViewState回传的 //if (DropDownList1.SelectedItem.Text == "上海") //{ // Response.Write("你选择了上海"); //} Response.Write(DropDownList1.Text); }
对前面例子中的DropDwonList增加了一个SelectedIndexChanged事件,在响应时间的函数里将当前下拉框的文本输出到页面,和上面一样DropDownList的ViewState是禁用,按照我的预想现在应该是选择下拉框时候页面没有反应,但是实践的结果却是除了选择列表第一个元素选择其他的页面仍然能输出,当将DropDownList的EnableViewState属性设为True结果和预想的一样。难道下拉列表除了第一个元素是通过ViewState回传其余的都是通过PostData回传??总感觉怪怪的肯定另有原因。这个暂且不管既然禁用ViewState的时候选择第一个元素和我们想要的结果一直,我们姑且作为参照。言归正传:既然通过上一个例子的方法没法解决禁用ViewState带来的影响那么有没有一个两全其美的方法呢,所谓两全其美就是既能在初始化下拉列表数据的时候避免使得ViewState不必要的增大,又能在需要它的时候使其保持状态呢?有!这个方法就是动态创建控件,在说明为什么之前先看下如何做,先删除代码中静态添加的DropDownList,然后再OnInit中动态创建它:
DropDownList refDl = null; protected void Page_Load(object sender, EventArgs e) { } private List<string> QueryDatabase() { List<string> result = new List<string>(); result.Add("安徽"); result.Add("北京"); result.Add("上海"); result.Add("深圳"); result.Add("广东"); return result; } protected override void OnInit(EventArgs e) { DropDownList DropDownList1 = new DropDownList(); DropDownList1.DataSource = QueryDatabase(); DropDownList1.DataBind(); //保存引用 refDl = DropDownList1; //自动回发设置为true DropDownList1.AutoPostBack = true; //动态创建的控件必须显示的绑定事件 DropDownList1.SelectedIndexChanged += new EventHandler(DropDownList1_SelectedIndexChanged); this.Form.Controls.Add(DropDownList1); base.OnInit(e); } protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e) { //说明SelectedItem不是通过ViewState回传的 //if (DropDownList1.SelectedItem.Text == "上海") //{ // Response.Write("你选择了上海"); //} Response.Write(refDl.Text); } }
现在说明为什么,这个涉及到两个问题?
- 动态创建的控件何时开始跟踪它的ViewState?
- 当动态创建的控件的状态落后与页面生命周期的时候是如何处理的?
我们可以再页面生命周期的任何阶段去动态的创建控件,同时我们从上面第一张图中知道TrackViewState()是在OnInit阶段完成的,而控件的OnInit是在页面的OnInit前递归完成的,那么在页面的OnInit中甚至极端情况在页面的OnPreRender阶段中动态创建一个控件,那么这个控件是不是会错过自身的TrackViewState()呢?关键在于上面的this.Form.Controls.Add(DropDownList1);中的Add()方法,关于这个方法以及内部的细节我也在研究,但总的来说这个方法会检查控件所处的阶段,如果落后页面的生命周期的话会有一个”追赶“的过程,会执行这个控件经理的一些方法当然也包括TrackViewState()但好像也会忽略一些私有方法,因为这个追赶过程是发生在Add()之后所以这之前绑定的数据自然不会被当成”脏数据“,也不会添加到ViewState中,同时因为追赶所以SelectedIndexChange等事件依然会正常触发。
出处:http://www.cnblogs.com/zhanjindong
个人博客:http://zhanjindong.com
关于:一个程序员而已
说明:目前的技术水平有限,博客定位于学习心得和总结。