经常会碰到在前台代码中要使用(或绑定)后台代码中变量值的问题。一般有<%= 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”字符串,生效的属性值是一个被截断的字符串,它从属性后的一串字符串(若未加引号)的第一个非空字符开始,截止到下一个空字符的前一个字符为止(比如对于" 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中通过设置断点来看看,看两者的绑定赋值分别是在什么时候发生的。
尚存的疑问:
1.不知道为什么不能获取到internal修饰的变量?
参考文章:
Eval( " ")和DataBinder.Eval(Container.DataItem, " ")的区别及用法