ASP.NET AJAX(14)__UpdatePanel與伺服器端指令碼控制元件指令碼控制元件的作用指令碼控制元件的指責Extender模型指令碼控制元件和Extender模型在PostBack中保持狀態在UpdatePa
指令碼控制元件的作用
ASP.NET AJAX的指令碼控制元件,連線了伺服器端和客戶端,因為我們(可以)只在伺服器端程式設計,而效果產生在客戶端,這就需要我們首先在伺服器端編寫一個控制元件類,然後包含一個或幾個指令碼檔案,其中定義了客戶端元件,可以讓開發人員只在服務端操作控制元件,而在頁面上新增客戶端行為
一個典型的指令碼控制元件就是UpdateProgress,我們來看一下它的實現方式
一個UpdateProgress的簡單示例
建立一個aspx頁面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UpdateProgress.aspx.cs" Inherits="Demo13_UpdateProgress" %> <!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"> <asp:ScriptManager ID="ScriptManager1" runat="server"> </asp:ScriptManager> <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <%= DateTime.Now %><br /> <asp:Button ID="Button1" runat="server" Text="Update" onclick="Button1_Click" /> </ContentTemplate> </asp:UpdatePanel> <asp:UpdateProgress ID="UpdateProgress1" runat="server" DisplayAfter="1000"> <ProgressTemplate> Loading...... </ProgressTemplate> </asp:UpdateProgress> </form> </body> </html>
在Button1的點選事件裡,讓執行緒等待三秒鐘
protected void Button1_Click(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(3000);
}
開發頁面,我們點選按鈕Button1,可以看到,等待一秒鐘後,出現“Loading…”字樣,因為我們設定了UpdateProfress的DisplayAfter為1000,這裡程式碼1000毫秒,而我們讓控制元件的點選事件觸發,引發非同步回送後,在伺服器端停留了三秒鐘,所以三秒後,時間更新,同時“Loading…”字樣消失
我們開啟在網頁中右鍵選擇開啟原始檔,可在頁面的form結束之前找到如下程式碼
Sys.Application.add_init(function() {
$create(Sys.UI._UpdateProgress, {"associatedUpdatePanelId":null,"displayAfter":1000,"dynamicLayout":true}, null, null, $get("UpdateProgress1"));
});
這段程式碼,是不是很熟悉呢?沒錯,如果看過我上一節的文章的,就會很熟悉這種程式碼格式,它響應了Application的init事件,然後建立一個Sys.UI._UpdateProgress型別的元件,然後設定它繫結的ID,這裡是Null,和displayAfter,停留多少毫秒後顯示,和UpdateProgress的佔位方式,最後,設定的是它要修飾的element
指令碼控制元件的指責
- 在頁面上引入客戶端元件所需要的指令碼檔案
- 在頁面上生成使用客戶端元件的指令碼程式碼
於是出現了IScriptControl介面
- IEnumerable<ScriptReference> GetScriptReferences()方法:描述頁面中需要載入在頁面中的指令碼檔案
- IEnumerable<ScriptDescriptor> GetScriptDescriptors()方法:告訴頁面需要輸出的指令碼內容
如果我們要開發一個指令碼控制元件,除了實現以上的兩個方法以外,還需要重寫Control類的兩個方法
- OnPreRender
- OnRender
由於大部分的指令碼控制元件對於以上兩個方法實現相同,因此在開發時候,也可以直接繼承ScriptControl類,它已經實現了IScriptControl介面
一個指令碼控制元件的示例:StyledTextBox
建立一個名為StyledTextBox.js的檔案
/// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace('Demo');//註冊命名控制元件
Demo.StyledTextBox = function(element) {
Demo.StyledTextBox.initializeBase(this, [element]);//呼叫父類建構函式
this._highlightCssClass = null;
this._nohighlightCssClass = null;
this._onfocusHandler = null;
this._onblurHandler = null;
}
Demo.StyledTextBox.prototype =
{
//highlightCssClass屬性
get_highlightCssClass: function() {
return this._highlightCssClass;
},
set_highlightCssClass: function(value) {
if (this._highlightCssClass !== value) {
this._highlightCssClass = value;
this.raisePropertyChanged('highlightCssClass');
}
},
//nohighlightCssClass屬性
get_nohighlightCssClass: function() {
return this._nohighlightCssClass;
},
set_nohighlightCssClass: function(value) {
if (this._nohighlightCssClass !== value) {
this._nohighlightCssClass = value;
this.raisePropertyChanged('nohighlightCssClass');
}
},
initialize: function() {
Demo.StyledTextBox.callBaseMethod(this, 'initialize');
//建立兩個EventHandler
this._onfocusHandler = Function.createDelegate(this, this._onFocus);
this._onblurHandler = Function.createDelegate(this, this._onBlur);
//把這兩個EventHandler加到我們要修飾的控制元件上
$addHandlers(this.get_element(),
{ 'focus': this._onFocus, 'blur': this._onBlur },
this);
this.get_element().className = this._nohighlightCssClass;
},
dispose: function() {
$clearHandlers(this.get_element());
Demo.StyledTextBox.callBaseMethod(this, 'dispose');
},
//如果獲得焦點,把highlightCssClass給到這個element的class上
_onFocus: function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._highlightCssClass;
}
},
//如果失去焦點,把nohighlightCssClass給到這個element的class上
_onBlur: function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._nohighlightCssClass;
}
}
}
Demo.StyledTextBox.registerClass('Demo.StyledTextBox', Sys.UI.Control);
然後建立一個aspx頁面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ScriptControl.aspx.cs" Inherits="Demo13_ScriptControl" %>
<!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>
<style type="text/css">
.NoHighLight
{
border:solid 1px gray;
background-color:#EEEEEE;
}
.HighLight
{
border:solid 1px gray;
background-color:Ivory;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference Path="~/Demo13/StyledTextBox.js" />
</Scripts>
</asp:ScriptManager>
<input type="text" id="textBox" />
<script language="javascript" type="text/javascript">
Sys.Application.add_init(function() {
$create(
Demo.StyledTextBox,
{
highlightCssClass: "HighLight",
nohighlightCssClass: "NoHighLight"
},
null,
null,
$get("textBox"));
});
</script>
</form>
</body>
</html>
程式碼很簡單,和上一講的如出一轍。。。什麼如出一轍,本來就是一回事,文字框獲得焦點,樣式設定為HighLight,失去焦點,樣式設定為NoHighLight。
這裡,我們還是在客戶端進行程式設計的,還沒有做到在服務端編寫在客戶端生效的這樣一個效果
我們開始做一個服務端控制元件
建立一個名為StyledTextBox.cs的類
using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Collections.Generic;
namespace Demo
{
/// <summary>
///StyledTextBox 的摘要說明
/// </summary>
//繼承自TextBox,實現介面IScriptControl
public class StyledTextBox : TextBox, IScriptControl
{
//兩個屬性,分別是控制元件或者焦點和失去焦點時候要設定的樣式
public string HighlightCssClass { get; set; }
public string NoHighlightCssClass { get; set; }
#region IScriptControl 成員
//告訴ScriptManager將如何生成指令碼程式碼
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Demo.StyledTextBox", this.ClientID);//引數1:建立的元件型別,引數2:返回此控制元件在客戶端生成的ID
//新增兩個屬性
descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);
yield return descriptor;
}
//告訴頁面我們要引入的指令碼檔案
public IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Path = this.ResolveClientUrl("~/Demo13/StyledTextBox.js");//ResolveClientUrl方法用於瀏覽器的指定資源的完全限定 URL
yield return reference;
}
#endregion
//以下是開發一個指令碼控制元件,需要重寫Control的兩個方法
protected override void OnPreRender(EventArgs e)
{
//如果不在設計期間
if (!this.DesignMode)
{
//把自身註冊給ScriptManager的ScriptControl
ScriptManager.GetCurrent(this.Page).RegisterScriptControl<StyledTextBox>(this);
}
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
{
//把自身註冊給ScriptManager的ScriptDescriptors
ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
}
base.Render(writer);
}
}
}
然後修改剛才的aspx頁面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ScriptControl.aspx.cs" Inherits="Demo13_ScriptControl" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ Register Namespace="Demo" TagPrefix="demo" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<style type="text/css">
.NoHighLight
{
border:solid 1px gray;
background-color:#EEEEEE;
}
.HighLight
{
border:solid 1px gray;
background-color:Ivory;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<input type="text" id="textBox" />
<asp:ScriptManager runat="server" ID="s"></asp:ScriptManager>
<demo:StyledTextBox ID="StyledTextBox1" runat="server" HighlightCssClass="HighLight" NoHighlightCssClass="NoHighLight" />
</form>
</body>
</html>
注意,這裡我們不需要javascript程式碼,也不需要在頁面中引入我們剛才的js檔案,只需要在頁面中註冊這個指令碼控制元件,然後在頁面中當作服務端控制元件那樣直接使用,設定屬性就可以啦
我們看到StyledTextBox繼承了TextBox,同時擴充套件了TextBox,這個概念和客戶端元件的Control模型很相似,事實上普通的指令碼控制元件包含的指令碼中大多數都是定義了客戶端的Control模型的元件
Extender模型
和客戶端的Behavior模型概念類似的服務端模型是Extender模型,可以為一個伺服器端控制元件附加多個Extender,Extender模型理論上繼承自IExtenderControl即可,實際上開發時候議案繼承自ExtenderControl類,免去一些額外的工作
開發ExtenderControl需要覆蓋一下兩個方法
- IEnumerable<ScriptReference> GetScriptReferences()方法:描述頁面中需要載入在頁面中的指令碼檔案
- IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)方法:需要在目標控制元件的執行的指令碼程式碼
一個擴充套件控制元件的示例:FocusExtender
新建一個類庫專案,新增引用System.Web和System.Web.Extensions
建立一個名為FocusBehavior.js的檔案
Type.registerNamespace('Demo');
Demo.FocusBehavior = function(element) {
Demo.FocusBehavior.initializeBase(this, [element]);
this._highlightCssClass = null;
this._nohighlightCssClass = null;
}
Demo.FocusBehavior.prototype =
{
get_highlightCssClass: function() {
return this._highlightCssClass;
},
set_highlightCssClass: function(value) {
if (this._highlightCssClass !== value) {
this._highlightCssClass = value;
this.raisePropertyChanged('highlightCssClass');
}
},
get_nohighlightCssClass: function() {
return this._nohighlightCssClass;
},
set_nohighlightCssClass: function(value) {
if (this._nohighlightCssClass !== value) {
this._nohighlightCssClass = value;
this.raisePropertyChanged('nohighlightCssClass');
}
},
initialize: function() {
Demo.FocusBehavior.callBaseMethod(this, 'initialize');
$addHandlers(this.get_element(),
{ 'focus': this._onFocus, 'blur': this._onBlur },
this);
this.get_element().className = this._nohighlightCssClass;
},
dispose: function() {
$clearHandlers(this.get_element());
Demo.FocusBehavior.callBaseMethod(this, 'dispose');
},
_onFocus: function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._highlightCssClass;
}
},
_onBlur: function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._nohighlightCssClass;
}
}
}
Demo.FocusBehavior.registerClass('Demo.FocusBehavior', Sys.UI.Behavior);
程式碼和之前的一樣,我就沒添加註釋,看過我之前的文章,看這段程式碼不是問題
然後建立一個名為FocusExtender.cs的類檔案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
//描述我們需要引用的資源
[assembly: WebResource("FocusExtender.FocusBehavior.js", "application/x-javascript")]
namespace FocusExtender
{
[TargetControlType(typeof(Control))]
public class FocusExtender : ExtenderControl
{
public string HighlightCssClass { get; set; }
public string NoHighlightCssClass { get; set; }
protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
{
ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("Demo.FocusBehavior", targetControl.ClientID);
descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);
yield return descriptor;
}
protected override IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Assembly = "FocusExtender";
reference.Name = "FocusExtender.FocusBehavior.js";
yield return reference;
}
}
}
在這裡描述應用資源的時候應該注意,這裡不是檔名,也不是這個類庫的名稱加點然後加檔名
我們點選專案右鍵屬性,開啟屬性頁面
我們的資源名稱,是預設命名控制元件.檔名稱
這裡的程式碼,與前面的示例唯一不同的是,多了一個targetControl,在類名前加一個標識,表示我們這個控制元件作用到那種型別的控制元件上,我們這裡設定為“Control”,表示所有控制元件
還應該注意一點,我們應該在專案生成操作的時候,把js檔案作為嵌入的資源,點選js檔案屬性,然後在屬性對話方塊裡做相應修改
然後我們就可以在我們的網站裡使用它啦
在網站中點選右鍵新增引用,選擇我們建立的FocusExtender專案,會在bin目錄下出現一個FocusExtender.dll,注意要先生成一下
建立aspx頁面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="FocusExtender.aspx.cs" Inherits="Demo13_FocusExtender" %>
<%@ Register Assembly="FocusExtender" Namespace="FocusExtender" TagPrefix="demo" %>
<!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>
<style type="text/css">
.NoHighLight
{
border:solid 1px gray;
background-color:#EEEEEE;
}
.HighLight
{
border:solid 1px gray;
background-color:Ivory;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:TextBox runat="server" ID="textBox" />
<demo:FocusExtender ID="FocusExtender1" runat="server" TargetControlID="textBox" HighlightCssClass="HighLight" NoHighlightCssClass="NoHighLight" />
<asp:Panel Width="200" Height="200" ID="panel" runat="server">Hello World!</asp:Panel>
<demo:FocusExtender ID="FocusExtender2" runat="server" TargetControlID="panel" HighlightCssClass="HighLight" NoHighlightCssClass="NoHighLight" />
</form>
</body>
</html>
這樣,我們把我們建立的控制元件“附加”到了一個文字框和一個Panel上,在同時我們提供了三個屬性,作用的控制元件,和兩個樣式屬性,執行頁面,得到與前面我們的指令碼控制元件相同的效果
指令碼控制元件和Extender模型
- IScriptControl:對應Sys.Component__ScriptComponentDescriptor
- ScriptComtrol:對應Sys.UI.Control__ScriptControlDescript
- ExtenderControl:對應Sys.UI.Behavior__ScriptBehaviroDescriptor
在PostBack中保持狀態
- 與普通伺服器控制元件不同,ScriptControl的精髓在客戶端,在普通的服務端控制元件中使用ViewSate並,它不能保持客戶端狀態
- 元件狀態可能在客戶端被改變
- 需要在PostBack前後保持客戶端狀態
在非同步重新整理中,由於不重新整理整個頁面,因此可以儲存在頁面變數中,但是完整的PostBack需要將狀態從客戶端提交到伺服器端,然後再寫回給客戶端,客戶端向伺服器端提交資訊的方法有以下三種
- Query String(改變URL)
- Cookie(作用域太大)
- Input+Post
那麼,如果我們要儲存頁面的某個狀態,就分兩種情況啦
一種是非同步重新整理,因為非同步重新整理的時候,頁面並沒有銷燬,所以,我們可以把儲存這種狀態的鍵值放在window物件或者一個HiddenField中,但是如果是傳統的更新,頁面是會被銷燬的,則只能儲存在HiddenField中啦
在UpdatePanel中使用內聯指令碼
- UpdatePanel在更新時使用的是設定innerHTML的做法
- 設定innerHTML並不會執行其中的內聯指令碼
- 需要把內聯指令碼提出來,然後eval
為了讓UpdatePanle可以使用內聯指令碼,就需要使用一個內聯指令碼控制元件
內聯指令碼
- 要子啊非同步更新後執行指令碼,唯一的方法就是呼叫ScriptManager的指令碼註冊方法
- 開發一個控制元件,在普通載入時簡單輸出內聯指令碼,在非同步更新時呼叫指令碼註冊方法
一個內聯指令碼的示例
建立一個aspx頁面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="InlineScripts.aspx.cs" Inherits="Demo13_InlineScripts" %>
<!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">
<asp:ScriptManager runat="server" ID="sm" />
<asp:UpdatePanel runat="server" ID="update">
<ContentTemplate>
<%= DateTime.Now %>
<asp:Button runat="server" ID="btnRefresh" Text="Refresh" />
<script language="javascript" type="text/javascript">
alert("Xiaoyaojian");
</script>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
開啟頁面,重新整理頁面,都會彈出提示框,而在我們點選Refresh後,指令碼卻並沒有被執行,這不是我們想要的效果,但是這裡的指令碼在非同步回送的時候確實是被載入啦,那要怎麼做呢 。。。。。
我們建立一個名為InlineScript的類庫專案,新增一個類
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.IO;
namespace InlineScript
{
public class InlineScript : Control
{
//重寫Render方法,每次UpdatePanle更新,這個方法都會被呼叫
protected override void Render(HtmlTextWriter writer)
{
ScriptManager sm = ScriptManager.GetCurrent(this.Page);
if (sm.IsInAsyncPostBack)//如果頁面是非同步更新的情況下
{
StringBuilder sb = new StringBuilder();
base.Render(new HtmlTextWriter(new StringWriter(sb)));
//得到的UpdatePanel中的script標籤的所有內容
string script = sb.ToString();
ScriptManager.RegisterStartupScript(this, typeof(InlineScript), this.UniqueID, script, false);//把這段指令碼註冊到頁面上
}
else
{
base.Render(writer);
}
}
}
}
生成專案,然後和上面一樣,在網站專案中新增對這個專案的引用,然後修改上面的頁面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="InlineScripts.aspx.cs" Inherits="Demo13_InlineScripts" %>
<%@ Register Assembly="InlineScript" Namespace="InlineScript" TagPrefix="demo" %><%--註冊這個控制元件--%>
<!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">
<asp:ScriptManager runat="server" ID="sm" />
<asp:UpdatePanel runat="server" ID="update">
<ContentTemplate>
<%= DateTime.Now %>
<asp:Button runat="server" ID="btnRefresh" Text="Refresh" />
<%--使用註冊的控制元件--%>
<demo:InlineScript runat="server">
<script language="javascript" type="text/javascript">
alert("Xiaoyaojian");
</script>
</demo:InlineScript>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
開啟頁面,重新整理,點選按鈕,都會彈出提示框,對嘛 這才是我們要的效果