
5.2 当UpdatePanel遇上MasterPage
MasterPage技术的出现,让设计师可以轻松地设计网页样板,ASP.NET AJAX自然也支持这种架构,设计师只需在MasterPage中放置一个ScriptManager控件,之后套用此MasterPage的页面便可直接放入UpdatePanel等ASP.NET AJAX的控件,无需再放入ScriptManager控件。这样的应用相当常见,并无任何可议之处,唯一可能会引发问题的情况是套用MasterPage之页面内的UpdatePanel控件需要依赖MasterPage中的控件来刷新时,此时并无法依赖Triggers来实现,因为你无法在Triggers编辑窗中选取位于MasterPage中的控件,这有两种解法,一是将MasterPage中欲触发UpdatePanel刷新的控件,通过ScriptManager的RegisterAsyncPostback函数注册成Async-Postback的控件,一旦完成此动作后,该控件的任何Postback动作将转变成Async-Postback,再对应于事件中,以Update函数来更新UpdatePanel即可。请照着下列步骤做。
1. 于AdvAjaxDemo的Web Site项目中新增一个MasterPage,命名为Default.master。
2. 在其内键入程序5-3的程序代码。
3. 在Default.master.cs中键入程序5-4的代码。
程序5-3
Samples\5\AdvAjaxDemo\Default.master <%@ Master Language="C#" AutoEventWireup="true" CodeFile="Default.master.cs" Inherits="_Default" %> <!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>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:ScriptManager ID="ScriptManager1" runat="server"> </asp:ScriptManager> <table> <tr> <td valign=top> <asp:TreeView ID="TreeView1" runat="server" ImageSet="Msdn" NodeIndent="10" OnSelectedNodeChanged="TreeView1_SelectedNodeChanged"> <ParentNodeStyle Font-Bold="False" /> <HoverNodeStyle BackColor="#CCCCCC" BorderColor="#888888" BorderStyle="Solid" Font-Underline="True" /> <SelectedNodeStyle BackColor="White" BorderColor="#888888" BorderStyle="Solid" BorderWidth="1px" Font-Underline="False" HorizontalPadding="3px" VerticalPadding="1px" /> <NodeStyle Font-Names="Verdana" Font-Size="8pt" ForeColor="Black" HorizontalPadding="5px" NodeSpacing="1px" VerticalPadding="2px" /> </asp:TreeView> </td> <td valign=top> <asp:contentplaceholder id="ContentPlaceHolder1" runat="server"> </asp:contentplaceholder> </td> </tr> </table> </div> </form> </body> </html>
程序5-4
Samples\5\AdvAjaxDemo\Default.master.cs using System; using System.Data; using System.Data.SqlClient; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class _Default : System.Web.UI.MasterPage { private void BindTreeView() { TreeView1.Nodes.Clear(); TreeNode currentMasterNode = null; using (SqlConnection conn = new SqlConnection( ConfigurationManager.ConnectionStrings[ "NorthwindConnectionString"].ConnectionString)) { using(SqlCommand cmd = new SqlCommand( "SELECT City FROM Customers GROUP BY City ORDER BY City",conn)) { conn.Open(); using(SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { while(reader.Read()) { if (!reader.IsDBNull(0)) { string city = reader.GetString(0); if (currentMasterNode == null) { currentMasterNode = new TreeNode( city.Substring(0, 1), "NonSelect"); TreeView1.Nodes.Add(currentMasterNode); } else { if (currentMasterNode.Text != city.Substring(0, 1)) { currentMasterNode = new TreeNode(city. Substring(0, 1), "NonSelect"); TreeView1.Nodes.Add(currentMasterNode); } } currentMasterNode.ChildNodes.Add(new TreeNode(city)); } } } } } } protected void Page_Load(object sender, EventArgs e) { if (!IsPostback && !ScriptManager1.IsInAsyncPostback) BindTreeView(); ScriptManager1.RegisterAsyncPostbackControl(TreeView1); } protected void TreeView1_SelectedNodeChanged(object sender, EventArgs e) { UpdatePanel up = ContentPlaceHolder1.FindControl("UpdatePanel1") as UpdatePanel; up.Update(); } }
此MasterPage一开始时,会由Northwind数据库中的Customers数据表以GROUP BY City的方式取出城市数据列表,然后一一指定给页面上的TreeView1控件,接着在Page_Load函数中将TreeView1注册为Async-Postback控件,这时TreeView1的任何Postback动作都会被转换成Async-Postback,不会引发整个页面的刷新,接着在TreeView1的SelectedNodeChanged事件中,通过ContentPlaceHolder1取得要刷新的UpdatePanel,调用Update函数要求刷新。完成后请再照着下列步骤建立套用此MasterPage的页面。
1. 新增一个网页,命名为TreeViewWithGridView.aspx,套用Default.master作为Master Page。
2. 在页面中放入一个UpdatePanel控件,命名为UpdatePanel1。
3. 在UpdatePanel1控件中放入一个SqlDataSource控件,命名为SqlDataSource1,连结至Northwind数据库的Customers数据表,选取CustomerID、CompanyName、ContactTitle、ContactName字段,指定一个WHERE参数,如图5-5所示。
4. 在UpdatePanel1控件中放入一个GridView控件,命名为GridView1,DataSourceID设为SqlDataSource1,勾选Enable Paging。
5. 在Page_Load函数中键入程序5-5的代码。

图5-5
程序5-5
Samples\5\AdvAjaxDemo\ TreeViewWithGridView.aspx.cs protected void Page_Load(object sender, EventArgs e) { if (ScriptManager.GetCurrent(this).IsInAsyncPostback && ScriptManager.GetCurrent(this).AsyncPostbackSourceElementID. EndsWith("TreeView1")) { TreeView tv = (TreeView)Master.FindControl(ScriptManager.GetCurrent(this). AsyncPostbackSourceElementID); if (tv.SelectedValue != "NonSelect") { SqlDataSource1.SelectParameters["City"].DefaultValue = tv.SelectedValue; GridView1.DataBind(); } } }
完成后将此页面设为默认并运行,便可看到如图5-6所示的画面。

图5-6
眼尖的读者应发现这个设计有些问题,MasterPage与套用MasterPage的页面有着一小层链接,那就是套用MasterPage的页面之UpdatePanel控件必须命名为UpdatePanel1,这在大量使用MasterPage技术的系统中,会显得非常不恰当,若能将此链接切断,改由套用MasterPage的页面来决定哪些UpdatePanel控件要随着MasterPage上的控件动作而更新,岂不更好?办得到吗?当然!只要将MasterPage中的TreeView1设为UpdatePanel的Trigger就可以了,见程序5-6。
程序5-6
Samples\5\AdvAjaxDemo\ TreeViewWithGridView.aspx.cs using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class TreeViewWithGridView : System.Web.UI.Page { private void AttachUpdateTrigger() { TreeView tv = (TreeView)Master.FindControl("TreeView1"); AsyncPostbackTrigger trigger = new AsyncPostbackTrigger(); trigger.ControlID = tv.ID; trigger.EventName = "SelectedNodeChanged"; UpdatePanel1.Triggers.Add(trigger); } protected void Page_Init(object sender, EventArgs e) { AttachUpdateTrigger(); } protected void Page_Load(object sender, EventArgs e) { if (ScriptManager.GetCurrent(this).IsInAsyncPostback && ScriptManager.GetCurrent(this).AsyncPostbackSourceElementID. EndsWith("TreeView1")) { TreeView tv = (TreeView)Master.FindControl( ScriptManager.GetCurrent(this).AsyncPostbackSourceElementID); if (tv.SelectedValue != "NonSelect") { SqlDataSource1.SelectParameters["City"].DefaultValue = tv.SelectedValue; GridView1.DataBind(); } } } }
程序5-6在Page_Init时调用了AttachUpdateTrigger函数,此函数会利用Master属性来找到位于MasterPage中的TreeView1控件,并将其添加成UpdatePanel1的Trigger,此举意味着当TreeView1发生Postback时,将会被转变为Async-Postback,并引发UpdatePanel1的刷新动作。一旦此页面添加Triggers后,MasterPage页面就不需要再使用RegisterAsyncPostback来将TreeView1注册为Async-Postback控件,也不需于TreeView1的SelectedNodeChanged事件中以Update函数来刷新UpdatePanel了,一切都移往套用MasterPage技术的页面,这不是更符合群体开发时的情况吗?程序5-7是修改后的Default.master.cs。
程序5-7
Samples\5\AdvAjaxDemo\Default.master.cs using System; using System.Data; using System.Data.SqlClient; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class _Default : System.Web.UI.MasterPage { private void BindTreeView() { TreeView1.Nodes.Clear(); TreeNode currentMasterNode = null; using (SqlConnection conn = new SqlConnection( ConfigurationManager.ConnectionStrings[ "NorthwindConnectionString"].ConnectionString)) { using(SqlCommand cmd = new SqlCommand( "SELECT City FROM Customers GROUP BY City ORDER BY City",conn)) { conn.Open(); using(SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { while(reader.Read()) { if (!reader.IsDBNull(0)) { string city = reader.GetString(0); if (currentMasterNode == null) { currentMasterNode = new TreeNode( city.Substring(0, 1), "NonSelect"); TreeView1.Nodes.Add(currentMasterNode); } else { if (currentMasterNode.Text != city.Substring(0, 1)) { currentMasterNode = new TreeNode(city.Substring(0,1),"NonSelect"); TreeView1.Nodes.Add(currentMasterNode); } } currentMasterNode.ChildNodes.Add(new TreeNode(city)); } } } } } } protected void Page_Load(object sender, EventArgs e) { if (!IsPostback && !ScriptManager1.IsInAsyncPostback) BindTreeView(); // ScriptManager1.RegisterAsyncPostbackControl(TreeView1); } protected void TreeView1_SelectedNodeChanged(object sender, EventArgs e) { //UpdatePanel up = ContentPlaceHolder1.FindControl("UpdatePanel1") as UpdatePanel; //up.Update(); } }
ScriptManagerProxy与MasterPage
将ScriptManager控件放在MasterPage上的缺点是,套用此MasterPage的网页由于没有ScriptManager控件,所以无法通过ScriptManager控件的Scripts、Services来引入外部的JavaScript程序文件及Web Services,这时另一个ScriptManagerProxy控件便派上用场了,只要将ScriptManagerProxy控件放在套用MasterPage的页面内的ContentPlaceHolder中,设计师便能通过它所提供的Scripts、Services来设定要引入的JavaScript程序文件及Web Services,这些设定会于此页面被浏览时迭加到MasterPage中的ScriptManager控件内。举个例子来说,Default2.master中的ScriptManager之Scripts引用了JScript1.js,Default2.aspx是套用了Default2.master作为MasterPage的页面,只要在Default2.aspx的ContentPlaceHolder中放入一个ScriptManagerProxy控件,于其Scripts中添加引用JScript2.js,那么当用户浏览Default2.aspx时,该页面就会引用JScript1.js、JScript2.js两个JavaScript程序文件。那如果MasterPage页面中没有ScriptManager控件,而套用MasterPage的页面有呢?结果并不意外,ScriptManagerProxy控件会丢出需要ScriptManager控件的出错信息。