阿不

不抛弃,不放弃

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  157 随笔 :: 0 文章 :: 1982 评论 :: 53 Trackbacks

在ASP.NET我们在使用Repeater,DetailsView,FormView,GridView等数据绑定模板时,都会使用<%# Eval("字段名") %>或<%# Bind("字段名") %>这样的语法来单向或双向绑定数据。但是我们却很少去了解,在这些语法的背后,ASP.NET究竟都做了哪些事情来方便我们使用这样的语法来绑定数据。究竟解析这样的语法是在编译时,还是运行时?如果没有深入去了解,我们肯定不得而知。这个简短的系列文章就是带我们大家一起去深入探究一下ASP.NET绑定语法的内部机理,以让我们更加全面的认识和运用它。

事件的起因是,我希望动态的为Repeater控件添加行项模板,我可以通过实现ITempate接口的方式来动态添加行模板。并希望它通过普通的页面绑定语法来完成数据字段的绑定功能,如下就是一个简单的例子:

   1: /// <summary>
   2: /// Summary description for DynamicTemplate
   3: /// </summary>
   4: public class DynamicTemplate : ITemplate
   5: {
   6:     public DynamicTemplate()
   7:     {
   8:         //
   9:         // TODO: Add constructor logic here
  10:         //
  11:     }
  12:     #region ITemplate Members
  13:  
  14:     public void InstantiateIn(Control container)
  15:     {
  16:         TextBox textBox = new TextBox();
  17:         textBox.Text = @"<%# Eval(""ID"") %>";
  18:         container.Controls.Add(textBox);
  19:     }
  20:     #endregion
  21: }

在这个例子中,我在模板中添加了一个TextBox控件,并指定它的绑定字段是“ID”。但是这做法,能否实现我们实现我们需要的功能呢?答案是否定,每一行的TextBox的值都是"<%# Eval(""ID"") %>",而不会像我们希望的那样去绑定ID字段。从结果来分析原因,我们可以非常容易得出,这段绑定语法并没有得到ASP.NET运行时的承认,那么页面中使用相同的语法为什么可以呢?故事就是从这里开始的。

我们首先要去了解下,在页面中使用这样的语法ASP.NET都为我们做了哪些事情呢?要了解这个,我们要找到.aspx文件在首次运行时动态编译的程序集。

Thumbsup我们都知道,在ASP.NET运行时,也会把.aspx文件编译成一个动态类,这个类是继承于.aspx的Page指令中Inherits属性指定的类并且同时也直接实现了IHttpHandler接口。这个动态类会负责创建页面中使用的各种服务器端控件的实例,并且ASP.NET运行时会负责解析的编译.aspx中存在的服务器端代码(包括绑定语法)并将这些代码编译到这个页面类。WebSite工程和Web Application在页面文件上有些不同,WebSite工程的每个页面最多可以有两个文件:.aspx和.aspx.cs文件;而在Web Application还可以包括.aspx.designer.cs文件,这个文件所起的作用也非常有限,也就是为了能在页面代码中使用服务器端、控件实例而定义的一个实例变量,仅此而已。所以在设计时WebSite具备更多的动态行为,而在运行时WebSite工程和Web Application并没有太大区别。

Thumbsup如何得到页面的动态类呢?要首先得到这个页所在的动态程序集,在Vista以前的操作系统上,一般是在:%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files 文件夹下,而在Vista中,而会在:%USERPROFILE%\AppData\Local\Temp\Temporary ASP.NET Files下。那么如何快速得到程序集的路径和名称?你可以让你的Web工程动态编译出错(比如重复的类名),就可以快速定位到当前动态程序集的目录了。

动态类中会有很多的内容,我们不作更多的分析,我们把目光集中绑定代码上。假设现在页面上有这么一段Repeater绑定代码:

   1: <asp:Repeater runat="server" ID="repeater">
   2:     <HeaderTemplate>
   3:         <table>
   4:             <tr>
   5:                 <td>
   6:                     ID
   7:                 </td>
   8:                 <td>
   9:                     电流{a}
  10:                 </td>
  11:                  <td>电压(V)</td>
  12:                 <td>
  13:                     备注'
  14:                 </td>
  15:                 <td>
  16:                     名称]
  17:                 </td>
  18:             </tr>
  19:     </HeaderTemplate>
  20:     <ItemTemplate>
  21:         <tr>
  22:             <td>
  23:                 <%# Eval("ID")%>
  24:             </td>
  25:             <td>
  26:                 <%# Eval("电流{a}")%>
  27:             </td>
  28:             <td><%# Eval("电压(V)")%></td>
  29:             <td>
  30:                 <%# Eval("备注'")%>
  31:             </td>
  32:             <td>
  33:                 <%# Eval("名称]")%>
  34:             </td>
  35:         </tr>
  36:     </ItemTemplate>
  37:     <FooterTemplate>
  38:         </table>
  39:     </FooterTemplate>
  40: </asp:Repeater>

那么在动态类中,相应的会有这样的一段函数,是用来创建ID为repeater的控件实例:

   1: [DebuggerNonUserCode]
   2: private Repeater __BuildControlrepeater()
   3: {
   4:     Repeater repeater = new Repeater();
   5:     base.repeater = repeater;
   6:     repeater.HeaderTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control4));
   7:     repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control5));
   8:     repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control7));
   9:     repeater.ID = "repeater";
  10:     return repeater;
  11: }
  12:  
  13:  

CompiledTempateBuilder和BuildTemplateMethod只是模板实例化的一个中介,真正用于添加模板内容的是后面的那些私有函数,如ItemTempate的模板内容实例的创建就在__BuildControl__control5函数中,这个函数原型定义是:

   1: [DebuggerNonUserCode]
   2: private void __BuildControl__control5(Control __ctrl)
   3: {
   4:     DataBoundLiteralControl control = this.__BuildControl__control6();
   5:     IParserAccessor accessor = __ctrl;
   6:     accessor.AddParsedSubObject(control);
   7: }
   8:  

在这个函数里,调用了另一个私有函数this.__BuildControl__control6,这个函数返回的一个DataBoundLiteralControl对象,并将对象输出添加到__ctrl参数。事实上,只要我们去阅读CompiledTempateBuilder就发现在,这里的__ctrol对象就是我们在实例化模板时传入的对象,也就是ITemplate中的InstantiateIn方法的那个container参数对象。

Thumbsup为什么使用的是AddParsedSubObject方法,使用这个方法添加子控件相当于告诉父控件,这是一个已经解析好的子控件对象,不需再去将控件解析成HTML代码,而在输出时直接输出Text属性的值即可。从这里我们还可以得知DataBoundLiteralControl的对象,事实上就是承担了字符串拼接的职责,这一点我们可以在后面的分析中得以验证。

__BuildControl__control6私有函数的定义如下:

   1: [DebuggerNonUserCode]
   2: private DataBoundLiteralControl __BuildControl__control6()
   3: {
   4:     DataBoundLiteralControl control = new DataBoundLiteralControl(5, 4);
   5:     control.TemplateControl = this;
   6:     control.SetStaticString(0, "\r\n                <tr>\r\n                    <td>\r\n                        ");
   7:     control.SetStaticString(1, "\r\n                    </td>\r\n                    <td>\r\n                        ");
   8:     control.SetStaticString(2, "\r\n                    </td>\r\n                    \r\n                    <td>\r\n                        ");
   9:     control.SetStaticString(3, "\r\n                    </td>\r\n                    <td>\r\n                        ");
  10:     control.SetStaticString(4, "\r\n                    </td>\r\n                </tr>\r\n            ");
  11:     control.DataBinding += new EventHandler(this.__DataBind__control6);
  12:     return control;
  13: }

在这个函数里面,创建了一个DataBoundLiteralControl对象,并将页面上定义的模板的静态HTML代码添加到该的静态字符串数组里,并且设置了它的绑定事件代理函数__DataBind__control6,该函数的定义:

   1: public void __DataBind__control6(object sender, EventArgs e)
   2: {
   3:     DataBoundLiteralControl control = (DataBoundLiteralControl) sender;
   4:     RepeaterItem bindingContainer = (RepeaterItem) control.BindingContainer;
   5:     control.SetDataBoundString(0, Convert.ToString(base.Eval("ID"), CultureInfo.CurrentCulture));
   6:     control.SetDataBoundString(1, Convert.ToString(base.Eval("电流{a}"), CultureInfo.CurrentCulture));
   7:     control.SetDataBoundString(2, Convert.ToString(base.Eval("备注'"), CultureInfo.CurrentCulture));
   8:     control.SetDataBoundString(3, Convert.ToString(base.Eval("名称]"), CultureInfo.CurrentCulture));
   9: }

在这个函数中,我们看到了真正的数据绑定代码了,它调用了TemplateControl的Eval方法来将当前数据项的相应字段的值取出,并按一定的格式转化后添加到DataBoundLitreralControl对象中,并在DataBoundLiteralControl将StaticString和DataBoundString字符串数组按一定的顺序拼接起来,作为Text属性的输出值。而容器控件则直接向客户端输这段HTML。

下面,我们还有必要来分析下TemplateControl中的Eval方法,这个方法有两种重载,简单起见,我们来分析较为简单的重载:

   1: protected internal object Eval(string expression)
   2: {
   3:     this.CheckPageExists();
   4:     return DataBinder.Eval(this.Page.GetDataItem(), expression);
   5: }

这个方法,使用了DataBinder.Eval静态方法来得到绑定表达式(字段名)的值,它的数据是通过this.Page.GetDataItem()这样的一个方法得到的。那么为什么this.Page.GetDataItem()就可以得到当前正在被绑定的数据项呢?原来,在页面绑定数据时,它会有一个堆栈来保存它所有的绑定控件绑定时用到的数据项,我们只需要取得堆栈顶部的那个元素,就可以在页面的作用域内的任何一个位置得到当前正在被绑定的数据项。如上的例子,我们就可以取得当前绑定的RepeaterItem的DataItem的数据项,因此我们不需要与RepeaterItem有任何的联系。

如果硬要用上面的代码来描述数据绑定的全过程,跨度过大。但是有了以上的分析,我们再用文字的形式再来总结下,应该就会一个比较完整的印象了:在ASP.NET的数据模板控件中,可以使用<%# %>这样的语法来将字段值作为一个占位符,用在HTML代码中,可以方便我们设计和生成最终的HTML代码,不需要很多的字符拼接工作。而ASP.NET运行时在首次执行页面时,会为页面编译一个动态类,在这个动态类中会实例化所有的服务器端控件,编译和解析绑据模板控件的绑定语法,并用一些对象和操作来完成数据绑定的字符串接拼接行为。因此绑定语法的解析事实上是编译时的行为,只不过这个编译时是延迟到页面的首次执行时。这就可以解释为什么在我们想在动态添加模板中使用<%# %>这样的绑定语法时,无法解析的原因。

而对于DataBinder.Eval方法,这是ASP.NET提供的一个数据绑定辅助方法。通过这个方法,我们可以方便的从种不同的数据项,如自定义对象或DataRow取出对象的字段(属性值)。从而为我们屏蔽很多不必要的数据来源类型的判断。同时DataBinder这个类还提供了其它的绑定辅助方法,大家可以从MSDN查看更多有用的帮助。

对数据绑定语法的分析,就先到此为一个段落。在上面,我们主要讨论了Eval的单向数据绑定,在接下来的一篇文章中我们会来探讨ASP.NET通过Bind函数(关键字)

来实现数据双向绑定的机理。

系列导航:

深入ASP.NET数据绑定(中)——数据双向绑定机理

深入ASP.NET数据绑定(下)——多样的绑定方式

阿不 http://hjf1223.cnblogs.com
posted on 2008-05-03 23:05 阿不 阅读(6407) 评论(33)  编辑 收藏 所属分类: .Net相关技术经验总结

评论

#1楼  2008-05-03 23:41 andyzqj [未注册用户]
很好,正需要这个
  回复  引用  查看    

#2楼  2008-05-04 00:00 nicholas.sun      
good article!!``
  回复  引用  查看    

#3楼  2008-05-04 05:50 freeze      
看不懂也得看,学不会也得学!!!!
  回复  引用  查看    

#4楼  2008-05-04 07:29 王德水      
你的代码是怎么插进去的?谢谢
  回复  引用  查看    

#5楼  2008-05-04 08:18 brightwang      
DataBinder.Eval确实不错
  回复  引用  查看    

#6楼 [楼主] 2008-05-04 08:23 阿不      
@王德水
使用Windows Live Writer发布的文章,插入代码使用WLW的插件。
  回复  引用  查看    

#7楼  2008-05-04 08:27 李战      
路过,学习!
  回复  引用  查看    

#8楼 [楼主] 2008-05-04 08:35 阿不      
@ 李战
李老师是每贴必占啊!
  回复  引用  查看    

#9楼  2008-05-04 09:26 白发先生      
看不懂
  回复  引用  查看    

#10楼  2008-05-04 14:02 peace      
我一般用gridview 动态添加模板列比较实用,能给个小demo就好了
我看到公司有的项目一个aspx页面放4 5个repeater 如果可以做到动态话 一个就够了
  回复  引用  查看    

#11楼 [楼主] 2008-05-04 15:01 阿不      
@peace
Demo倒是有写,不过关于动态模板的例子并没有写太多。本文的目的是为了解析和解释,ASP.NET绑定语法的内部机制。并不希望牵扯太多的话题,如果需要会另启一文详细介绍如何在Repeater使用动态模板。
  回复  引用  查看    

#12楼  2008-05-04 16:50 李涛      
不错!
我以前也写过2篇相关的文章!
互相学习
  回复  引用  查看    

#13楼  2008-05-04 16:58 badnewfish      
@peace
我这里面有个带膜:
http://www.cnblogs.com/badnewfish/archive/2008/03/21/1115544.html
  回复  引用  查看    

#14楼  2008-05-04 18:01 alisx      
看不明白 学习 支持
  回复  引用  查看    

#15楼  2008-05-05 00:33 seyon      
阿不:
你反编译程序集用的什么工具?
  回复  引用  查看    

#16楼 [楼主] 2008-05-05 08:46 阿不      
@ seyon
你搜.NET反编译工具或Reflector
  回复  引用  查看    

#17楼  2008-05-05 11:40 Longkin      
听君一席话,在读十年书!
  回复  引用  查看    

#18楼  2008-05-05 12:25 镜涛      
--引用--------------------------------------------------
阿不: @ 李战
李老师是每贴必占啊!
--------------------------------------------------------

  回复  引用  查看    

#19楼  2008-05-05 13:48 天生俪姿      
你插入的代码用的什么插件啊?
  回复  引用  查看    

#20楼 [楼主] 2008-05-05 18:13 阿不      
@ 天生俪姿
看这里:http://gallery.live.com/liveItemDetail.aspx?li=d4409446-af7f-42ec-aa20-78aa5bac4748&bt=9&pl=8
  回复  引用  查看    

#21楼  2008-05-06 10:22 Klesh Wong      
写得很好,支持.
  回复  引用  查看    

#22楼  2008-05-06 10:24 Klesh Wong      
对了,既然中篇/下篇出了,何不在上篇与中篇加上链接以方便读者?更好更强大...
  回复  引用  查看    

#23楼 [楼主] 2008-05-06 10:36 阿不      
@ Klesh Wong
谢谢建议,已经加上。
  回复  引用  查看    

#24楼  2008-05-06 10:49 黑羽飘舞      
很好,很有深度。对于阿不兄文章中提到的动态程序集过去重来没留意,现在发现的确需要深入的了解下了。
  回复  引用  查看    

#25楼  2008-05-06 12:46 Cat Chen      
其实对于Eval来说,最重要的概念是Page.GetDataItem(),只要理解了这里有一个堆,剩下的就容易理解了。
  回复  引用  查看    

#26楼  2008-05-06 17:40 alisx      
很好 继续关注
  回复  引用  查看    

#27楼  2008-05-12 08:22 小哈      
學習~
  回复  引用  查看    

#28楼  2008-05-20 18:51 旭少--烧猪      
学习
  回复  引用  查看    

#29楼  2008-05-29 17:55 qq13237810775      
--引用--------------------------------------------------
旭少--烧猪: 学习
--------------------------------------------------------

  回复  引用  查看    

#30楼  2008-05-31 13:24 张荣华      
受教了
  回复  引用  查看    

一直关注中.....很好,很强大.....学习了..
  回复  引用  查看    

#32楼  2008-06-23 20:14 saler [未注册用户]
想找个人帮小弟介绍一下MVP

www.aihua58.cn
  回复  引用  查看    

#33楼  2008-06-26 14:16 Sev7n! [未注册用户]
[img]http://www.cnblogs.com/Emoticons/yoyocici/223852199.gif[/img]
学习!
  回复  引用  查看