1. 罗列ASP.NET服务器控件的运行的生命周期。
一般服务器控件的生命周期包含11个阶段:
/// <summary> /// 1. 初始化 /// </summary> /// <remarks> /// 1. 主要完成控件的初始化,页面通过ProcessRequest方法来递归它的子控件并依次调用对应的OnInit方法; /// 2. 通过调用TrackViewState方法打开控件的视图状态跟踪功能,以便存储在ViewState中的值在页面回发时能正确的恢复到控件上。 /// </remarks> protected override void OnInit(EventArgs e) { Write("1. OnInit"); base.OnInit(e); this.Page.RegisterRequiresPostBack(this); } /// <summary> /// 2. 加载视图 /// </summary> /// <remarks> /// 本阶段仅在页面回发时执行,事实上页面在第一次执行时还没有获得存在到视图状态中的数据,主要完成加载视图状态到控件的过程, /// 前提是该控件启用视图状态,在客户端请求过程中,视图状态将存储到一个隐藏域中并回传到服务器,以便页面再次回发时觉得是否执行回发事件。 /// </remarks> protected override void LoadViewState(object savedState) { Write("2. LoadViewState"); base.LoadViewState(savedState); } /// <summary> /// 3. 数据回传处理 /// </summary> /// <param name="postDataKey">装载了客户端提交的页面控件ID</param> /// <param name="postCollection">装载了客户端提交的数据</param> /// <remarks> /// 本阶段仅在页面回发时执行,主要完成控件数据回传功能,根据客户端提交数据的新值和旧值比较是否执行RaisePostDataChangedEvent方法, /// 客户端修改窗体数据提交后,将以“&”形式隔开并将数据与ID一一对应起来,根据控件ID来检测是否实现IPostBackDataHandler接口,如果实现 /// 才会调用LoadPostData方法,给其刷新其值的机会。 /// </remarks> public bool LoadPostData(string postDataKey, NameValueCollection postCollection) { Write("3. LoadPostData"); return true; } /// <summary> /// 4. 加载事件 /// </summary> /// <remarks> /// 先执行页面的Page_Load事件再递归执行控件的OnLoad事件,主要完成创建控件及初始化,根据视图状态还原控件客户端数据, /// 经常通过IsPostBack来判断控件是第一次请求页面还是在回发事件中进行相应的代码逻辑处理。 /// </remarks> protected override void OnLoad(EventArgs e) { Write("4. OnLoad"); base.OnLoad(e); } /// <summary> /// 5. 回传事件通知 /// </summary> /// <remarks> /// 本阶段仅在页面回发时执行,此方法也是接口IPostBackDataHandler的方法,只有当LoadPostData为true时,RaisePostDataChangedEvent才会被调用。 /// </remarks> public void RaisePostDataChangedEvent() { Write("5. RaisePostDataChangedEvent"); } /// <summary> /// 6. 处理回发事件 /// </summary> /// <remarks> /// 本阶段仅在页面回发时执行,主要引发回发的客户端事件,成功捕获回发的客户端事件并在服务器端进行处理。前提是必须实现IPostBackEventHandler接口, /// 根据参数eventArgument来判断是哪个控件触发的回发事件,进而完成相应的事件处理。 /// </remarks> public void RaisePostBackEvent(string eventArgument) { Write("6. RaisePostBackEvent"); } /// <summary> /// 7. 预呈现 /// </summary> /// <remarks> /// 主要完成控件呈现之前的一些操作,注册控件相应的资源,如:Javascript脚本和隐藏域控件等。 /// </remarks> protected override void OnPreRender(EventArgs e) { Write("7. OnPreRender"); base.OnPreRender(e); } /// <summary> /// 8. 保存视图状态 /// </summary> /// <remarks> /// 与LoadViewState正好相反,主要完成存储页面视图状态信息,同时将在页面第一次请求时就会执行,而LoadViewState仅在页面回发时才执行。 /// </remarks> protected override object SaveViewState() { Write("8. SaveViewState"); base.SaveViewState(); return new Pair(); } /// <summary> /// 9. 呈现 /// </summary> /// <remarks> /// 将控件和字符文本输出到服务器控件输出流中,以HTML的形式呈现在客户端。对于控件而言,可调用RenderControl方法输出流。 /// </remarks> protected override void Render(HtmlTextWriter writer) { writer.Write("<input type=‘button‘ name=‘{0}‘ value=‘Click Me!‘ style=‘position:absolute;left:20px;top:280px‘ onclick=\"{1}\"/>", this.UniqueID, Page.ClientScript.GetPostBackEventReference(this, "")); Write("9. Render"); base.Render(writer); } /// <summary> /// 10. 卸载 /// </summary> /// <remarks> /// 主要完成页面及控件资源的清理,不能等同于当离开一个页面或关闭浏览器时触发,因为依靠页面视图状态使得两次请求看起来是连续的,而两次请求间是无状态的。 /// </remarks> protected override void OnUnload(EventArgs e) { Write("10. OnUnload"); base.OnUnload(e); } /// <summary> /// 11. 释放 /// </summary> /// <remarks> /// 释放一些资源,如数据库连接或IO文件流。 /// </remarks> public override void Dispose() { Write("11. Dispose"); base.Dispose(); } private void Write(string text) { HttpContext.Current.Response.Write(text + "<br/>"); }
注:像Page这样的容器型服务器控件具有更细化的生命周期阶段。如对于每一个控件而言,都只有一个Init事件,而Page控件又细化为PreInit、Init、InitComplete三个阶段,通常PreInit阶段用于设置模板页和主题的属性,一旦到了Init阶段将不能再改变,Init阶段将依次调用各子控件的Init事件来初始化并设置命名容器,而InitComplete阶段将启用控件具有视图状态跟踪能力。
2. 简述一下ASP.NET的服务器控件设计模式与运行模式的区别。
设计模式是为开发者而设计,使开发者能及时看到控件的展现效果以便快捷设置控件的属性和行为,但在设计模式下将不执行某些控件生命周期的事件(如OnPreRender,Load,CreateChildControls等),也不存在仅在运行模式下的上下文环境变量,但Init, Construct, Render, RenderContents, Dispose等事件都会在设计模式下执行。如OnPreRender事件要引入一些资源文件(Javascript/CSS/Pictures), 在IDE设计器状态下时文件路径是不可取的,它必须根据当前运行模式下的虚拟服务器路径来获取。再比如在Page控件的PageLoad事件中引用了服务器的上下文环境,也将报"获取不到信息"的异常错误。
3. 简述服务器控件的ID,UniqueID和ClientID的区别。
每一个服务器控件都由ID,UniqueID和ClientID来唯一标识,其中ID是我们命名的ID,UniqueID是服务器端的ID,ClientID是客户端的ID,通常情况下控件没有作为子控件或在MasterPage下时,这三者是完全一致的,如果控件继承了INamingContainer接口,UniqueID和ClientID将以父控件的this.UniqueID加上ID并分别以不同的分隔符($和_)来标识。