經常會碰到在前台代碼中要使用(或綁定)後台代碼中變數值的問題。一般有<%= str%>和<%# str %>兩種方式,這裡簡單總結一下。如有錯誤或異議之處,敬請各位指教。
一方面,這裡所講的前台即通常的.aspx檔案,後台指的是與aspx相關聯的CodeBehind,檔案尾碼名為.aspx.cs;另一方面,這裡的綁定是指使用者發出訪問某一頁面指令後,伺服器端在執行過程中就已經將前台代碼進行了賦值,而後產生html格式回傳用戶端顯示,而並非已經顯示到用戶端後,然後通過其他方法(如ajax)去伺服器端擷取相應變數。
備忘:上面說的兩個檔案是常見的程式碼後置(code-behind)模式,還有一種是代碼嵌入(code-beside, inline)模式,那就是只存在aspx一個檔案,而後台代碼則寫入此檔案的<script type="text/javascript" runat="server"></script>之中(還有一些文法上區別),這對於本文討論的問題略有影響,因為代碼嵌入是聲明性代碼與C#/VB.NET代碼都一起編譯到一個類裡面,而程式碼後置則將聲明性代碼與C#/VB.NET代碼分開幾次進行翻譯/編譯,因此前者是局部與局部(partial)的關係後者基類與衍生類別的關係,但這僅僅影響所能綁定變數的範圍(與修飾符有關),下面會提到。以下均以程式碼後置模式為例。
一般來說,在前台代碼的三種位置可能會用到(綁定)後台變數:
伺服器端控制項屬性或HTML標籤屬性
JavaScript代碼中
Html顯示內容的位置(也就是開始標籤與結束標籤之間的內容,如<div>這裡</div>(Html標籤)或者<asp:Label ID="Label2" runat="server" Text="Label">這裡</asp:Label>(伺服器端控制項),它作為預留位置把變數顯示於符號出現的位置)
對於第一種位置,有一些約束條件:
(1)一般的屬性要求是字串型或數值型(下面會提到有些伺服器端屬性支援屬性為資料集合);
(2)並不是所有的屬性都可以綁定變數,有些屬性例如runat屬性必須是"server"常量,即使綁定的字串是server,也會導致分析器分析時出錯;
(3)有一種屬性,他要求屬性值有約束(類型約束,比如伺服器端控制項要求TabIndex屬性是short類型,或者字串內容有約束),也應該在綁定時滿足,否則依然可能編譯時間報錯;
(4)還一種屬性,雖然屬性本身有約束,但即使綁定的變數不滿足約束,也可以編譯通過,比如input的checked屬性,它只有checked字串是合法的,但如果通過綁定擷取到的字串不是checked,那麼這些屬性將有自己內部處理機制,來保證可以正常使用;
(5)還要注意,即使對於同一類屬性,伺服器端和HTML的屬性的處理機制也不同,同樣是TabIndex(tabIndex),前者如果不滿足,則分析器錯誤,後者則忽略這一問題。
對於第二種位置,一般只要綁定的後台變數和JavaScript中資料類型相容即可。
對於第三種位置,如果綁定出現的位置不在伺服器端控制項內部,則沒有約束條件,只要是常量字串可以出現的位置,均可以綁定。但是對於置於伺服器端控制項內部,也就是上面那種<asp:Label ID="Label2" runat="server" Text="Label">這裡</asp:Label>的方式,則有約束條件。通過總結,歸納為四類伺服器端控制項,如果綁定的代碼出現在這些控制項的開始和結束標籤之間(這裡所說的控制項,是指如果綁定代碼外有多層的嵌套控制項包圍,則是指包圍綁定代碼的最內層控制項),有不同的顯示結果:
(1)約束型控制項:這類控制項要求它的開始標籤和結束標籤中只能包含指定的子控制項,因此如果在這裡出現代碼塊,將編譯錯誤。例如:
<asp:DataList runat="server"></asp:DataList>,在它之間,要求必須嵌套<ItemTemplate></ItemTemplate>。
(2)非嵌套類控制項:這類控制項,不允許在內部嵌套其他控制項或標籤,只能是常量字串,它會將開始標籤和結束標籤中常量字串內容作為他的屬性。例如上面提到的TextBox,它會將標籤間內容作為它的Text屬性值。
(3)嵌套類控制項:這類控制項,可以嵌套其他任意控制項,也可以包含字串,因此可以正常顯示綁定代碼塊所表示的字串內容。例如Label控制項、Panel等。
(4)資料繫結類控制項:這類控制項是ASP.NET提供的伺服器端控制項,除了可以綁定普通的變數類型,也可以綁定一個資料集合(只能採取下面的第二種方式實現)。
關於是否加引號:在以上三個位置使用時,是否應該將<%= str%>或<%# str %>置於單引號或雙引號中呢?對於在不同位置,處理的方式是不同的:(具體請在下面兩種方式的具體介紹時,加以體會)
(1)對於第一種位置,由於JavaScript是弱類型的,如果綁定時加引號,顯然就認為就當做字串來處理,這始終是正確的;如果綁定時不加引號,它將認為這是個數值型的,那麼如果擷取的真是數值,當然可以,如果是非數值型,則將產生指令碼錯誤,這即使對於JavaScript賦值常量時,也是同樣的:
以下為引用的內容:
var test1 = 123b;//運行時報錯
var test2=123;//正確,是數值型
var test3="123b";//正確,字串型
(2)對於第二種位置,經過測試,無論是對於伺服器端控制項屬性還是HTML標籤屬性,加引號總是正確的;如果不加引號,則兩種屬性的處理方式不同:
對於伺服器端控制項屬性,如果綁定的代碼塊不加引號,則編譯時間會提示“驗證(ASP.NET):特性值前後必須加引號”的警告資訊,但是產生為HTML後,對應產生的HTML 屬性已經被加上引號並擷取了正確的綁定結果,因此加不加引號不會影響使用,但是建議對於規範的代碼,還是加上為好;
對於HTML標籤屬性,如果不加引號,則編譯時間會提示“驗證(XHTML 1.0 Transitional): 特性值前後必須加引號”的警告資訊,並且產生為HTML 屬性也確實沒有加上引號,那麼雖然屬性後面確實是沒有加上引號的正確的綁定值,但是不一定能展示出想要看到的結果。比如對於input標籤的value屬性,如果綁定的字串是" hello world from variable”,則在用戶端的input顯示出的內容實際上只是"hello”字串,生效的屬性值是一個被截斷的字串,它從屬性後的一串字串(若未加引號)的第一個非Null 字元開始,截止到下一個Null 字元的前一個字元為止(比如對於" hello world”,結果將是"hello”),因此,加上引號是必須的。
(3)對於第三種位置,加與不加引號,擷取的值及其顯示均不受影響。
因此建議,所有綁定運算式都加上引號,作為字串擷取,然後根據實際需求,用相應函數進行轉換,得到所需要的類型。
另外,這裡所說的後台變數是泛指的,包括如下:
成員變數
方法或屬性的傳回值
運算式,也就是所有後台能夠執行的代碼,運行後所得到的值(也就是直接將後台代碼寫在前台代碼中,記得使用完全限定名或在後台中using相關namespace)
資料集合
後台變數有一些約束條件,需要滿足:
(1)變數修飾符要求。變數是靜態或者執行個體欄位均可。對於程式碼後置模式的ASP.NET,以上的所述的變數必須為public或protected類型(因為是基類與衍生類別的關係),private或者internal都不行,而代碼嵌入模式則任何修飾符的變數均可訪問(一個類內部的關係)。
(2)變數類型要求。由於前台屬性一般是字串類型,而JavaScript基本類型也就是字串型、數字型、布爾型,因此對應的變數應該也是這幾種方式,其餘類型如果不被支援(如複雜類型、數組、參考型別等),前台擷取的就是調用了變數的ToString()方法所得到的字串。因此,在綁定時,要根據情況看是否能進行隱式類型轉換,必要時還要用相關函數來強制轉換,以保證前台可以獲得正確的值。當然,對於資料繫結類控制項,它的有些屬性可以為資料集合,但這時的綁定只能通過下面第二種方式才被支援。
以上是一些概念和基本約束,這些都是兩種方式都應該滿足的,下面具體介紹兩種方式,來實現前台代碼中(以下稱為代碼塊)綁定後台變數的功能。
一. <%= str%>
此種方式其實是ASP 時代就支援的,ASP 通過包含在 < % 和 %>中的運算式將執行結果輸出到客戶瀏覽器 , 如:< % =test %>就是將變數test的值發送到客戶瀏覽器中。在ASP.NET中,這個運算式依然可以使用,並可以出現在前台代碼的上述三個位置,但是要注意,除了上述的一般性約束外,對於控制項屬性,還必須是綁定到非伺服器端控制項的屬性。另外,它只能綁定上面講的前三種變數類型,不支援綁定資料集合。例子如下:
後台代碼:
以下為引用的內容:
public partial class WebForm2 : System.Web.UI.Page
{
public string GetVariableStr;//注意變數的修飾符
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GetVariableStr = "hello world from variable";
}
}
protected string GetFunctionStr()//注意傳回值的修飾符
{
return "hello world from Function";
}
}
前台代碼:
以下為引用的內容:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
function fun() {
var str = '<%= DateTime.Now %>';
//前台位置1,綁定的是第三種變數類型(也是第二種方式,?因為Now是個屬性)
alert(str);
}
</script>
</head>
<body onload="fun()">
<form id="form1" runat="server">
<div>
<input type="text" value="<%= GetVariableStr %>" />
<%--前台位置2,綁定的是成員變數--%>
"<%= GetFunctionStr() %>"
<%--前台位置3,綁定的是一個方法的傳回值>--%>
</div>
</form>
</body>
</html>
一些錯誤的使用:
之所以說第一種綁定方式要用於非伺服器端控制項的屬性,是因為如果應用於這些伺服器端屬性時,這些代碼實際上不被解析。 比如:
以下為引用的內容:
<asp:Label ID="Label1" runat="server" Text="<%= GetVariableStr %>"></asp:Label>
<asp:TextBox ID="TextBox1" runat="server" Text="<%= GetVariableStr %>"></asp:TextBox>
則顯示出來的Label1的文本是空,而TextBox中文本是"<%= GetVariableStr %>”,所以記住,對伺服器端控制項的屬性加這樣的代碼塊,將不被解析,而是將這一字串直接作為屬性值了,所以不是想要的結果。如果引號也不加上,將會編譯錯誤,提示“伺服器標記不能包含 <% ... %> 構造。”。
這裡結合開篇提到的關於將綁定代碼快置於“Html顯示內容的位置”時,如果在伺服器端控制項內,那四類控制項如何顯示的問題。如下:
以下為引用的內容:
<asp:Label ID="Label1" runat="server" >"<%= GetVariableStr %>"</asp:Label>
<asp:TextBox ID="TextBox1" runat="server" >"<%= GetVariableStr %>"</asp:TextBox>
其中,Label1屬於嵌套類控制項,Label1確實顯示了正確的結果,TextBox屬於非嵌套類控制項,TextBox如果用這種方式,將會產生編譯錯誤,提示“此上下文中不支援代碼塊。”
二. <%# str %>
ASP.NET 引入了一種新的聲明文法 <%# %>。該文法是在 .aspx 頁中使用資料繫結的基礎,所有資料繫結運算式都必須包含在這些字元中。這裡從用法和適用範圍等方面與第一種綁定方式進行區分。
從出現的位置來看,除了能出現在第一種代碼塊出現的所有位置外,他還可以出現在伺服器端控制項的屬性中。
從綁定的變數類型上看,他還可以配合ASP.NET的資料繫結類控制項,來綁定上述的第四種“變數”類型,就是資料集合(DropDownList,DataList,DataGrid,ListBox這些是資料繫結類控制項,資料集合包括ArrayList(數組),Hashtable(哈稀表,DataView(資料檢視),DataReader等)。
從用法上看,在前台代碼中除了在相應位置寫上<%# %>外,在後台代碼中,還需要使用DataBind()方法。以下是執行個體:
前台代碼:
以下為引用的內容:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
function fun() {
var str = '<%# DateTime.Now %>';
alert(str);
}
</script>
</head>
<body onload="fun()">
<form id="form1" runat="server">
<div>
<input type="text" value="<%# GetVariableStr %>" /><br />
"<%# GetVariableStr %>"
<asp:Label ID="Label1" runat="server" Text="<%# GetVariableStr %>"></asp:Label>
<%--此種方式可以綁定伺服器端控制項的屬性--%>
<asp:DropDownList ID="DropDownList1" runat="server" DataSource='<%# arraylist %>'>
<%-- 將集合綁定到資料繫結類控制項,通過DataSource屬性來實現,從而在下拉框看到集合中的內容--%>
</asp:DropDownList>
<asp:DataList ID="DataList1" runat="server" DataSource='<%# dt %>'>
<%-- 同上,綁定了DataTable資料集合?--%>
<ItemTemplate>
<table border="1" cellpadding="0" cellspacing="0">
<tr>
<td>
<asp:Label ID="Label2" runat="server" Text='<%# Bind("row0")%>'></asp:Label>
<%--由於繫結資料集合具有多列,並且此資料繫結類控制項支援模板,
因此需要在模板中指定需要綁定的列以及格式--%>
</td>
<td>
<%# Eval("row1")%>
</td>
</tr>
</table>
</ItemTemplate>
</asp:DataList>
</div>
</form>
</body>
</html>
可以看出,這種方式在使用時,不但可以實現(取代)<%=... %>所滿足的功能,還可以綁定伺服器控制項屬性(如上面的Label1),也可以將集合類型綁定到支援的資料繫結類控制項。在用法上,前台代碼除了對資料繫結類控制項綁定資料集合外有所差別,其他的使用上與第一種沒區別。在綁定類控制項的模板中,如何使用Eval、Bind、DataBinder.Eval等,不在此文討論中,可以參考下面連結的參考文章。
後台代碼:
以下為引用的內容:
public partial class WebForm2 : System.Web.UI.Page
{
public string GetVariableStr;
public ArrayList arraylist;
public DataTable dt;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GetVariableStr = "hello world from variable";
arraylist = new ArrayList();
arraylist.Add("選?項?1");
arraylist.Add("選?項?2");
dt = new DataTable();
dt.Columns.Add("row0");
dt.Columns.Add("row1");
DataRow dr1 = dt.NewRow();
dr1[0] = "1.1";
dr1[1] = "1.2";
DataRow dr2 = dt.NewRow();
dr2[0] = "2.1";
dr2[1] = "2.2";
dt.Rows.Add(dr1);
dt.Rows.Add(dr2);
Page.DataBind();
//DropDownList1.DataBind();
//DataList1.DataBind();
}
}
}
在後台代碼中,與第一種方式唯一不同的,就是需要調用DataBind方法。只有執行了相應控制項的DataBind方法,前台代碼中這些控制項中使用<%# %>的綁定才會發生(並且控制項內部的所有綁定也會發生,比如又嵌套了一個綁定後台資料的控制項),否則得話將不會被賦值,而是預設空值。上面我們用的是Page的DataBind方法,那麼整個頁面所有綁定都會執行。當然,如果我們只執行DataList1或者DropDownList1的DataBind方法,那麼只有相應控制項的綁定才會發生。需要注意的是,這裡說的需要執行DataBind包括了顯示和隱式執行,有些資料繫結類控制項,當它們通過 DataSourceID 屬性綁定到資料來源控制項時,會通過隱式調用 DataBind 方法來執行綁定。這時就不必顯示的再次調用了。
兩者區別:
兩種綁定方式上,他們的約束基本相同,都要求與屬性匹配,出現在他們可以出現的位置。後者的使用位置更廣泛,尤其是支援伺服器端控制項和綁定資料集合。後台代碼方面,後者需要調用DataBind才能完成綁定,前者則沒有這方面要求。這裡主要區別一下兩者在執行機制上的區別:<%=...%>是在程式執行時調用(應該是在頁面的RenderControl事件程序中完成的,也就是通常情況下我們可以看到的後台代碼都執行完畢後再去到前台代碼中進行賦值綁定),而<%#... %>是在DataBind()方法之後被調用,一旦調用了DataBind(),則它對應的控制項將綁定變數,因此,請注意:如果在DataBind()後再修改變數,那麼綁定的就不是最新值了,這就需要在完成變數的賦值後,再去DataBind()。其實這兩種方式,它的運行過程可以在VS中通過設定斷點來看看,看兩者的綁定賦值分別是在什麼時候發生的