伺服器控制項中的用戶端功能
在 Web 程式設計中,用戶端功能傳統上是由網頁開發人員負責,且不會封裝成伺服器元件。ASP.NET 則捨棄了這種作法,它使伺服器控制項可以發出用戶端指令碼,允許伺服器控制項將用戶端處理和伺服器端處理結合在一起。
用戶端功能最常見於 Web 伺服器控制項透過控制項的 Attributes 屬性,來呈現用戶端事件的事件處理常式。下列範例將說明衍生自 System.Web.UI.WebControls.Button Web 伺服器控制項的控制項 (ClientClickButton
),並為用戶端的按一下事件提供一個事件處理常式。
注意 網頁開發人員也可以使用 Web 伺服器控制項的 Attributes 集合,輕易地提供用戶端事件處理常式。這個範例的目的在於說明控制項本身如何封裝這類功能。若要建置這個範例,請參閱伺服器控制項範例中的說明。
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CustomControls
{
public class ClientClickButton : Button
{
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute("onclick", "alert('Thanks');");
}
}
}
[Visual Basic]
Option Explicit
Option Strict
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Namespace CustomControls
Public Class ClientClickButton
Inherits Button
Protected Overrides Sub AddAttributesToRender(writer As HtmlTextWriter)
MyBase.AddAttributesToRender(writer)
writer.AddAttribute("onClick", "alert('Thanks');")
End Sub
End Class
End Namespace
下列網頁將使用 ClientClickButton
自訂控制項。若要檢查 ClientClickButton
上的 onClick
屬性是否已呈現於用戶端上,請在瀏覽器中瀏覽該網頁並查看原始檔。
<%@ Register TagPrefix="Custom" Namespace="CustomControls" Assembly = "CustomControls" %>
<html>
<body>
<form runat=server>
This button handles a client-side click event.
<Custom:ClientClickButton Id = "Button" BackColor = "Red" runat=server/> <br> <br>
</form>
</body>
</html>
前述範例是一個非常簡單的用戶端功能說明。ASP.NET 可以使控制項參和更複雜的用戶端案例,如下列清單所示:
- 提供用戶端指令碼程式庫。
- 在網頁的頭尾發出指令碼。
- 確保指令碼區塊在一個網頁只會出現一次 (即使網頁上的控制項擁有多個執行個體)。
- 使控制項可以將事件處理常式和表單的用戶端送出事件建立關聯 (如網頁已含有表單)。
- 將由控制項呈現的用戶端項目加入至在用戶端宣告的陣列變數。
這些案例都是經由 System.Web.UI.Page 類別所公開的方法來啟用,並可讓 ASP.NET 伺服器控制項透過它的 Page 屬性來存取。下列表格將列出可提供用戶端功能的 Page 類別方法。
方法 | 說明 | 使用時機 |
---|---|---|
Page.RegisterClientScriptBlock | 使控制項發出指令碼區塊 (包含內嵌指令碼或指定指令碼檔案的位置)。指令碼區塊將呈現於網頁的上方且和該網頁一同註冊 (使用索引鍵),使指令碼區塊只能發出一次,即使網頁上的控制項擁有多個執行個體。
注意 由網頁上方發出的指令碼區塊在 ASP.NET 網頁中稱為用戶端指令碼區塊。 |
當您想包含的指令碼程式庫或呈現指令碼區塊,其中含有一般的函式 (網頁稍後將叫用的函式) 時。特別是,在呈現階段會呼叫的函式必須位於網頁的上方。 |
Page.RegisterStartupScript | 使控制項發出指令碼區塊 (包含內嵌指令碼或指定指令碼檔案的位置)。指令碼區塊將呈現於網頁的底部且和該網頁一同註冊 (使用索引鍵),使指令碼區塊只能發出一次,即使網頁上的控制項有多個執行個體。
注意 由網頁底部發出的指令碼區塊在 ASP.NET 網頁中稱為啟動指令碼區塊。 |
發出指令碼所呼叫網頁或指令碼的項目需要在啟動時執行。因為這個指令碼是由網頁底部發出,所以它參考的項目在指令碼被執行之前一定存在。 |
Page.RegisterArrayDeclaration | 使控制項可以使用一個指定的名稱將它本身加入至用戶端陣列變數中。陣列名稱將和包含的網頁一同註冊 (使用索引鍵),因此只有使用該名稱的陣列才可呈現於網頁上的用戶端指令碼。 | 您想讓控制項呈現的項目成為用戶端指令碼中的陣列變數。陣列可以使相同型別的項目組成群組以利於用戶端指令碼存取。例如,驗證器控制項會將其本身加入至一個名為 Page_Validators 的陣列。 |
Page.RegisterOnSubmitStatement | 將事件處理常式和表單的用戶端送出事件建立關聯,並將它和包含的網頁一起註冊 (使用索引鍵)。處理常式是呈現為表單項目的 onSubmit 屬性。註冊處理常式可以確保控制項的多個執行個體不會發出多個處理常式。 | 您想讓用戶端處理常式在送出表單時可以被叫用。
注意 您可以將內嵌指令碼作為引數直接傳遞給這個方法,或將呼叫傳遞給事件處理常式。當您將呼叫傳遞給事件處理常式時,該處理常式必須已在指令碼區塊或指令碼程式庫內單獨定義。 |
Page.IsClientScriptBlockRegistered | 決定用戶端指令碼區塊和指定的索引鍵是否要和包含的網頁一同註冊。 | 使用這個方法的傳回值來決定是否需要叫用 Page.RegisterClientScriptBlock。 |
Page.IsStartupScriptRegistered | 決定啟動指令碼區塊和指定的索引鍵是否要和包含的網頁一同註冊。 | 使用這個方法的傳回值來決定是否需要叫用 Page.RegisterStartupScript。 |
Page.RegisterHiddenField | 使伺服器控制項在它包含的網頁上可以註冊隱藏欄位,並在呈現網頁後即發出。用戶端指令碼可以存取這個欄位;當表單回傳至伺服器時,該隱藏欄位即可由伺服器作為回傳資料使用。 | 當伺服器控制項需要送出可由用戶端指令碼存取的隱藏變數時。(當無法將變數呈現為控制項的屬性或用戶端處理所需的變數和該控制項無關時,這種方式就非常有用)。如需範例的資訊,請參閱保存非表單控制項的用戶端變更。 |
驗證器控制項是唯一可以完整活用 ASP.NET 所提供用戶端功能的 ASP.NET 伺服器控制項。因為不同瀏覽器所支援的文件物件模型並不一致,因此大部份 .NET Framework SDK 隨附的伺服器控制項都盡量減少使用用戶端指令碼,大部份都將它用於自動回傳上。(如需使用指令碼以回傳的詳細資訊,請參閱產生回傳的用戶端指令碼)。然而,當您只使用特定的瀏覽器,或瀏覽器符合特定的文件物件模型時,您可以在控制項內使用用戶端指令碼以提供更豐富的用戶端經驗並可儘量避免伺服器往返。
使用由網頁公開的指令碼方法
下列的程式碼片段取材自基底驗證器控制項範例,其中使用數個由 Page 類別公開的用戶端指令碼方法。在範例中,引動的用戶端指令碼方法將以粗體格式顯示。範例初始程式碼定義的字串常數包含了要置於網頁上方的指令碼檔案其檔案名稱、將指令碼區塊和網頁一起註冊的索引鍵、要置於網頁底部的指令碼區塊以及一個提供格式化資訊的字串。
private const string ValidatorFileName = "DomValidation.js";
// The key to register a script key. In this example, the same key
// is used for registering both the script at the top of the
// page (called the client-side script block) and the script
// that is rendered at the bottom of the page (called startup
// script).
private const string ValidatorIncludeScriptKey = "DomValidatorIncludeScript";
// The script block that is rendered at the bottom of the page.
private const string ValidatorStartupScript = @"
<script language=""javascript"">
<!--
var Page_ValidationActive = false;
if (typeof(Page_DomValidationVer) == ""undefined"")
alert(""{0}"");
else
ValidatorOnLoad();
function ValidatorOnSubmit() {{
if (Page_ValidationActive) {{
return ValidatorCommonOnSubmit();
}}
}}
// -->
</script>
";
// Provides formatting information for emitting the script
// block at the top of the page.
private const string IncludeScriptFormat = @"
<script language=""{0}"" src=""{1}{2}""></script>";
// This method is called from OnPrerender in the
// Base Validator Control Sample.
protected void RegisterValidatorCommonScript() {
string location = null;
if (!Page.IsClientScriptBlockRegistered(ValidatorIncludeScriptKey)) {
// Provide the location of the script file.
location = Page.Request.ApplicationPath + "/script/";
// Create client script block.
string includeScript = String.Format(IncludeScriptFormat, "javascript", location, ValidatorFileName);
Page.RegisterClientScriptBlock(ValidatorIncludeScriptKey, includeScript);
}
if (!Page.IsStartupScriptRegistered(ValidatorIncludeScriptKey)) {
if (location == null) location = Page.Request.ApplicationPath + "/script/";
// Provide error message, which is localized.
string missingScriptMessage = "Validation script is missing '" + location + ValidatorFileName + "'";
// Create startup script block.
string startupScript = String.Format(ValidatorStartupScript, new object [] {missingScriptMessage,});
Page.RegisterStartupScript(ValidatorIncludeScriptKey, startupScript);
}
Page.RegisterOnSubmitStatement("ValidatorOnSubmit", "return ValidatorOnSubmit();");
}
[Visual Basic]
Private Const ValidatorFileName As String = "DomValidation.js"
' The key to register a script key. In this example, the same key
' is used for registering both the script at the top of the
' page (called the client-side script block) and the script
' that is rendered at the bottom of the page (called startup
' script).
Private Const ValidatorIncludeScriptKey As String = "DomValidatorIncludeScript"
' The script block that is rendered at the bottom of the page.
Private Const ValidatorStartupScript As String = ControlChars.CrLf & _
"<script language=""javascript"">" & ControlChars.CrLf & _
"<!--" & ControlChars.CrLf & _
"var Page_ValidationActive = false;" & ControlChars.CrLf & _
"if (typeof(Page_DomValidationVer) == ""undefined"")" & ControlChars.CrLf & _
" alert(""{0}"");" & ControlChars.CrLf & _
"else" & ControlChars.CrLf & _
" ValidatorOnLoad();" & ControlChars.CrLf & ControlChars.CrLf & _
"function ValidatorOnSubmit() {{" & ControlChars.CrLf & _
" if (Page_ValidationActive) {{" & ControlChars.CrLf & _
" return ValidatorCommonOnSubmit();" & ControlChars.CrLf & _
" }}" & ControlChars.CrLf & _
"}}" & ControlChars.CrLf & _
"// -->" & ControlChars.CrLf & _
"</script>"
' Provides formatting information for emitting the script
' block at the top of the page.
Private Const IncludeScriptFormat As String = ControlChars.CrLf & _
"<script language=""{0}"" src=""{1}{2}""></script>"
' This method is called from OnPrerender in the
' Base Validator Control Sample.
Protected Sub RegisterValidatorCommonScript()
Dim location As String = Nothing
If Not Page.IsClientScriptBlockRegistered(ValidatorIncludeScriptKey) Then
' Provide the location of the script file.
' When using a script library, deployment can be
' a problem because the runtime is
' tied to a specific version of the script file.
' This sample takes the easy way out and insists that
' the file be placed in the /script subdirectory
' of the application.
' In other cases, you should place it where it
' can be shared by multiple applications and is placed
' in a separate directory so that different versions
' of a control library can run side by side.
' The recommended pattern is to put script files in the
' path /aspnet_client/<assembly name>/<assembly version>/".
location = Page.Request.ApplicationPath + "/script/"
' Create the client script block.
Dim includeScript As String = [String].Format(IncludeScriptFormat, "javascript", location, ValidatorFileName)
Page.RegisterClientScriptBlock(ValidatorIncludeScriptKey, includeScript)
End If
If Not Page.IsStartupScriptRegistered(ValidatorIncludeScriptKey) Then
If location Is Nothing Then
location = Page.Request.ApplicationPath + "/script/"
End If
' Provide an error message, which is localized.
Dim missingScriptMessage As String = "Validation script is missing '" & location & ValidatorFileName & "'"
' Create the startup script block.
Dim startupScript As String = [String].Format(ValidatorStartupScript, New Object() {missingScriptMessage})
Page.RegisterStartupScript(ValidatorIncludeScriptKey, startupScript)
End If
Page.RegisterOnSubmitStatement("ValidatorOnSubmit", "return ValidatorOnSubmit();")
End Sub
下列程式碼片段取材自基底驗證器控制項範例,其中說明如何叫用 Page.RegisterArrayDeclaration 方法 (範例中是以粗體格式加上反白顯示),將由控制項呈現的項目加入至由用戶端指令碼呈現的陣列變數 (Page_Validators
)。將陣列變數和網頁一起註冊 (使用索引鍵 Page_Validators
) 可以確保網頁上使用這個名稱的變數只有一個。
// This method is called from Render in the
// Base Validator Control Sample.
protected virtual void RegisterValidatorDeclaration() {
string element = "document.getElementById(\"" + ClientID + "\")";
Page.RegisterArrayDeclaration("Page_Validators", element);
}
[Visual Basic]
' This method is called from Render in the
' Base Validator Control Sample.
Protected Overridable Sub RegisterValidatorDeclaration()
Dim element As String = "document.getElementById(""" & ClientID & """)"
Page.RegisterArrayDeclaration("Page_Validators", element)
End Sub
存取用戶端指令碼中的控制項
基底 Control 類別會公開一個名為 ClientID 的屬性,它會將要呈現的項目呈現 (以 HTML 格式) 為項目的 ID 屬性。ASP.NET 會動態產生控制項的 ClientID,使網頁上每一個控制項的 ClientID 都一定是唯一的。控制項 (即由控制項呈現的項目) 即可由用戶端在文件物件模型中使用它的 ID 來存取。控制項也可以使用 ClientID 為它要呈現的其他項目 (例如隱藏欄位) 產生唯一性的名稱。
發出 ClientID 的值並將它插入內嵌指令碼 (或插入指令碼程式庫的程式碼) 需要相當的技巧,因為必須將 ClientID 插入字串變數的右邊。下列範例將使用逸出字元 (Escape Character) 將 ClientID 插入構成內嵌指令碼的字串。
string element = "document.getElementById(\"" + ClientID + "\")";
Page.RegisterArrayDeclaration("Page_Validators", element);
[Visual Basic]
Dim element As String = "document.getElementById(""" & ClientID & """)"
Page.RegisterArrayDeclaration("Page_Validators", element)
您也可以使用 String 類別的多載 Format 方法來撰寫使用 ClientID 的用戶端指令碼。如需格式化的詳細資訊,請參閱格式化型別。
部署指令碼檔案
由已啟用指令碼的控制項所發出的指令碼區塊可以含有內嵌指令碼,或它可以提供指令碼檔案的位置。本主題前述內容中已使用範例說明這兩種情形。如果區塊可以提供指令碼檔案的位置,您必須部署指令碼檔案,使它可以由其他的應用程式使用並且不會產生版本衝突。在這個範例中,指令碼檔案是位於 Web 應用程式虛擬根目錄的 script
子目錄。
// Provides the location of the script file.
location = Page.Request.ApplicationPath + "/script/";
[Visual Basic]
' Provides the location of the script file.
location = Page.Request.ApplicationPath & "/script/"
雖然範例中是使用這個位置,但是由其他應用程式使用的指令碼檔案建議位置應如下所示。
/aspnet_client/<your assembly name>/<your assembly version>/
目錄 aspnet_client 是一個虛擬根 Web 應用程式目錄,即安裝 .NET Framework SDK 或 Visual Studio .NET 時在您的電腦上所建立的目錄。例如,ASP.NET 隨附的指令碼檔案位於下列的位置。
/aspnet_client/system_web/<version of SDK installed>
如果您安裝了多個版本的 SDK,您將會在 aspnet_client/system_web 底下看到多個子目錄。因為控制項程式庫是繫於特定版本的指令碼檔案,所以建議的部署模式可允許並存執行不同版本的控制項程式庫。
請同時注意電腦上裝載的每一個網站都會建立一個 aspnet_client 目錄。通常一個伺服器只會裝載一個網站。然而,裝載一個以上的網站也是被允許的,而多個網站也會產生多個 aspnet_client 目錄複本。
檢查是否要呈現指令碼
已啟用指令碼的控制項應該讓它的消費者可以選擇是否要呈現用戶端指令碼。ASP.NET 中的驗證器 Web 伺服器控制項會公開名為 EnableClientScript 的布林 (Boolean) 屬性,以決定控制項是否要對用戶端呈現指令碼。您可以定義一個屬性以提供這個功能,如下所述。
public bool EnableClientScript {
get {
object o = ViewState["EnableClientScript"];
return((o == null) ? true : (bool)o);
}
set {
ViewState["EnableClientScript"] = value;
}
}
[Visual Basic]
Public Property EnableClientScript() As Boolean
Get
Dim o As Object = ViewState("EnableClientScript")
If o Is Nothing Then
Return True
Else
Return CBool(o)
End If
End Get
Set
ViewState("EnableClientScript") = value
End Set
End Property
在呈現之前,已啟用指令碼的控制項應該檢查用戶端的能力,同時也需檢查使用者 (網頁開發人員) 是否已關閉指令碼。這些檢查通常是在呈現前以及呈現階段執行。下列程式碼片段是取材自基底驗證器控制項範例,它會在呈現階段前執行檢查。
private boolean renderUplevel;
protected override void OnPreRender(EventArgs e) {
base.OnPreRender(e);
...
// Work out uplevelness now.
renderUplevel = DetermineRenderUplevel();
if (renderUplevel) {
// Helper method that creates script blocks
// and registers them with the page.
RegisterValidatorCommonScript();
}
}
// Helper method to check whether script should be rendered.
protected virtual bool DetermineRenderUplevel() {
// Must be on a page.
Page page = Page;
if (page == null || page.Request == null) {
return false;
}
// Check whether the user has turned off scripting and
// check browser capabilities. This custom control
// needs the W3C DOM level 1 for control manipulation
// and at least ECMAScript 1.2.
return (EnableClientScript
&& page.Request.Browser.W3CDomVersion.Major >= 1
&& page.Request.Browser.EcmaScriptVersion.CompareTo(new Version(1, 2)) >= 0);
}
[Visual Basic]
Private _renderUplevel As Boolean
Protected Overrides Sub OnPreRender(e As EventArgs)
MyBase.OnPreRender(e)
_preRenderCalled = True
' Force a re-query of properties for render.
_propertiesChecked = False
' Work out uplevelness now.
_renderUplevel = DetermineRenderUplevel()
If _renderUplevel Then
RegisterValidatorCommonScript()
End If
End Sub
' Helper method to check whether script should be rendered.
Protected Overridable Function DetermineRenderUplevel() As Boolean
' Must be on a page.
Dim page As Page = Page
If page Is Nothing Or page.Request Is Nothing Then
Return False
End If
' Check the browser capabilities.
' This is how you can get automatic fallback to server-side
' behavior. These validation controls need
' the W3C DOM level 1 for control manipulation
' and need at least ECMAScript 1.2 for the
' regular expressions.
Return EnableClientScript AndAlso _
page.Request.Browser.W3CDomVersion.Major >= 1 AndAlso _
page.Request.Browser.EcmaScriptVersion.CompareTo(New Version(1, 2)) >= 0
End Function
如需在呈現階段執行檢查的資訊,請參閱基底驗證器控制項範例中的 Render 方法。
呈現於用戶端的指令碼
若要查看由控制項封裝的指令碼如何呈現於用戶端,請編譯並部署驗證器控制項範例所提供的範例、要求驗證器測試網頁範例所提供的網頁並在您的瀏覽器中檢視網頁的原始檔。您將會在下列範例中看到 HTML 和指令碼。(由控制項發出的指令碼在範例中是以粗體格式加上反白顯示)。網頁上有數個會發出指令碼的控制項,所有的控制項都衍生自基底驗證器控制項範例中所描述的基底控制項。因此,來源會包含一些在本主題中將不會說明的指令碼。請注意當您在瀏覽器中檢視原始檔時,應用程式的目錄名稱 (範例中為 samples
) 將會被應用程式的虛擬目錄名稱取代。
<HTML>
<HEAD>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<meta content="Microsoft Visual Studio .NET" name=GENERATOR>
<meta content=C# name=CODE_LANGUAGE>
<meta content="JavaScript (ECMAScript)" name=vs_defaultClientScript>
<meta content="Internet Explorer 3.02 / Navigator 3.0" name=vs_targetSchema>
<title>DOM Validators Test</title>
</HEAD>
<body>
<form name="ValTest" method="post" action="valtest.aspx" language="javascript" onsubmit="return ValidatorOnSubmit();" id="ValTest">
<input type="hidden" name="__VIEWSTATE" value="dDwxOTkwOTM1MDA5O3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDEzPjs+O2w8dDw7bDxpPDE+O2k8Mz47aTw1PjtpPDc+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPElFOz4+Oz47Oz47dDxwPHA8bDxUZXh0Oz47bDw1LjU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDEuMjs+Pjs+Ozs+O3Q8cDxwPGw8VGV4dDs+O2w8MS4wOz4+Oz47Oz47Pj47Pj47Pj47Pg==" />
<script language="javascript" src="/samples/script/DomValidation.js"></script>
<P><FONT face=Verdana size=5>DOM Validators Test</FONT></P>
<P>
<TABLE cellSpacing=1 cellPadding=1 width=602 border=0 height=131>
<TR>
<TD style="WIDTH: 82px">Name:</TD>
<TD style="WIDTH: 164px"><input name="txtName" type="text" id="txtName" /></TD>
<TD><span id="valRequired" controltovalidate="txtName" errormessage="Required." evaluationfunction="RequiredFieldValidatorEvaluateIsValid" initialvalue="" style="color:Red;visibility:hidden;">Required.</span></TD>
</TR>
<TR>
<TD style="WIDTH: 82px">Postal Code:</TD>
<TD style="WIDTH: 164px"><input name="txtPostcode" type="text" id="txtPostcode" /></TD>
<TD><span id="valRegex" controltovalidate="txtPostcode" errormessage="Postcode must be 9999." evaluationfunction="RegularExpressionValidatorEvaluateIsValid" validationexpression="\d{4}" style="color:Red;visibility:hidden;">Postcode must be 9999.</span></TD>
</TR>
</TABLE></P>
<P>
<input type="submit" name="cmdSubmit" value="Submit" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="cmdSubmit" />
<input type="submit" name="cmdCancel" value="Cancel" id="cmdCancel" />
</P>
<div id="Panel1" style="border-color:#00C000;border-width:2px;border-style:Solid;height:55px;width:197px;">
<P>
Browser: <span id="lblBrowserName">IE</span><br>
Version: <span id="lblBrowserVersion">5.5</span><br>
Script Version: <span id="lblScriptVersion">1.2</span><br>
DOM Version: <span id="lblDomVersion">1.0</span><br>
Submit clicks: <span id="lblSubmitCount"> 0 </span><br>
Cancel clicks: <span id="lblCancelCount"> 0 </span><br>
</P>
</div>
<script language="javascript"><!-- var Page_Validators = new Array(document.getElementById("valRequired"), document.getElementById("valRegex")); // --></script><script language="javascript"><!--var Page_ValidationActive = false;if (typeof(Page_DomValidationVer) == "undefined") alert("Validation script is missing '/samples/script/DomValidation.js'");else ValidatorOnLoad();function ValidatorOnSubmit() { if (Page_ValidationActive) { return ValidatorCommonOnSubmit(); }}
// -->
</script>
</form>
</body>
</HTML>