在上一篇《深入ASPNET数据绑定(上)》中我们分析了在NET中的数据绑定语法的一些内部机理简单说来就是ASPNET在运行时为我们完成了页面的动态编译并解析页面的各种服务器端代码包括数据绑定语法而数据绑定的语法虽是一些<%# %>代码块在生成的代码中仍然使用了服务器端控件以及在DataBinding事件调用DataBinderEval方法来完成数据的绑定工作所有的数据绑定模板控件都使用了这样的机制来进行数据的单向绑定在NET 中新增了双向的数据绑定方式主要用在GridViewDetailsViewFormView等数据容器控件中结合DataSourceControl就可以非常轻松的完成数据的更新和提交工作而不需要我们手工去遍历输入控件的值那在这样的双向数据绑定中ASPNET又是做了哪些工作来为我们透明输入控件与字段的取值与对应关系让我们可以在DataSouceControl中方便得到数据项修改前的值和修改后的值?下面就让我们一起来从一段页面代码开始吧
: <asp:DetailsDataSouce ID=DetailsDataSouce runat=server>
: </asp:DetailsDataSouce>
: <asp:DetailsView ID=detailsView runat=server DefaultMode=Edit DataSourceID=DetailsDataSouce>
: <Fields>
: <asp:TemplateField>
: <HeaderTemplate>
: 电流:</HeaderTemplate>
: <EditItemTemplate>
: <asp:TextBox ID=textBox runat=server Text=<%# Bind([电流{a}]) %>></asp:TextBox>
: </EditItemTemplate>
: </asp:TemplateField>
: </Fields>
: </asp:DetailsView>
在一个页面中定义了如上的一个DetailsView控件为这个控件指定了ID为DetailsDataSource的DataSouceControl控件这个控件是我们自己定义的一个DataSourceControl它返回的数据字段包括ID电流{a}电压(v)备注名称]我并没有设置DetailsView的AutoGenerateRows属性的值默认情况下它是为我们自动的生成这些字段的对应的数据显示和输入控件除此之外我们还另外添加了一个数据模板字段在这个模板中指定了编辑模板在编辑模板中我使用了<%# Bind()%>这样的语法将textBox与[电流{a}]字段双向绑定起来
为什么这里的字段都有一些特殊呢?因为我原先的意图是除了分析绑定语法以外还要测试哪些特殊字符无法使用数据绑定语法来绑定数据的这个在下篇文章中会具体介绍
Bind与Eval不一样这样的Bind并不Page或TemplateControl的一个方法事实上我们应该把它当成一个关键字来看待因为在ASPNET的双向数据绑定当中并没有这样的一个函数存在它的存在是只是告诉ASPNET动态编译页面类时将这个语法编译成一定的代码格式并生成一些函数代理来达到双向数据交流的目的
那么这一段代码动态编译生成的服务器代码又是如何的呢?让我们反编译动态程序集里面会找到用于创建DetailsView的__BuildControldetailsView的私有方法在这里会调用到一些其它内部方法我们不要让这些方法来干扰我们的视线直接找到创建如上模板字段的方法
: [DebuggerNonUserCode]
: private TemplateField __BuildControl__control()
: {
: TemplateField field = new TemplateField();
: fieldHeaderTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this__BuildControl__control));
: fieldEditItemTemplate = new CompiledBindableTemplateBuilder(new BuildTemplateMethod(this__BuildControl__control) new ExtractTemplateValuesMethod(this__ExtractValues__control));
: return field;
: }
这里首先把this__BuildControl__control作为一个代理函数用于创建头部模板的内容也就是如上的电流字段标题然后才是创建EditItemTemplate这个模板又被一些的中介模板所代替我们只需要来关心this__BuildControl__control和__ExtractValues__control即可__BuildControl__control是为了编辑数据字段时将数据字段的值显示在输入控件中(输入控件的初始化即字段值绑定到输入控件中)而__ExtractValues__control则是在提交数据时要找出这个模板内所有的双向绑定字段将这些字段的值以绑定字段名为Key以输入控件的值为Value添加了IOrderedDictionary字典中DetailsView等数据绑定控件调用这些委托代理来收集所有的被双向绑定的字段的最新的值下面分别是两段函数的代码片段
: [DebuggerNonUserCode]
: private TextBox __BuildControl__control()
: {
: TextBox box = new TextBox();
: boxTemplateControl = this;
: boxApplyStyleSheetSkin(this);
: boxID = textBox;
: boxDataBinding += new EventHandler(this__DataBinding__control);
: return box;
: }
: public void __DataBinding__control(object sender EventArgs e)
: {
: TextBox box = (TextBox) sender;
: IDataItemContainer bindingContainer = (IDataItemContainer) boxBindingContainer;
: if (thisPageGetDataItem() != null)
: {
: boxText = ConvertToString(baseEval([电流{a}]) CultureInfoCurrentCulture);
: }
: }
: [DebuggerNonUserCode]
: public IOrderedDictionary __ExtractValues__control(Control __container)
: {
: TextBox box = (TextBox) __containerFindControl(textBox);
: OrderedDictionary dictionary = new OrderedDictionary();
: if (box != null)
: {
: dictionary[[电流{a}]] = boxText;
: }
: return dictionary;
: }
由上面的代码片段可以了解到ASPNET动态编译器是将Bind语法拆分为两部分绑定输出和读取输入控件值绑定输出部分与前篇介绍的机制是完全一样的并且也是调用DataBinderEval方法来绑定数据而读取输入控件值则是会根据页面上控件的类型以及绑定的控件属性名称生成一段强类型的控件属性读取代码并将控件的值保存到dictionay中返回出去而它全然不知容器控件是如何将这些值合并起来传给对应的DataSouceControl控件的
关于数据容器控件而何与DataSouceControl协同工作并不是我们这里要分析的重点但是我们可以简单的描述一下工作流程以DetailsView的数据更新为例大家通过反编译DetailsView的源码会找到名称为HandleUpdate的私有方法在这个方法里面会去处理数据项更新前的值(至于在Web环境中如何保存更新前的值就需要靠ViewState的强大功能了)和更新后的值(通过ExtractRowValues函数调用类似上面生成的__ExtractValues__control代理函数来收集所有双向绑定字段的值存到NewValues里面)并将他们分别保存在两个不同的IOrderedDictionary对象(OldValuesNewValues)中然后将调用对应的DataSouceView的Update方法传入原字段值和新字段值和一些必须的参数即可由我们通过重写DataSourceView的方法来得到所有需要更新字段的原始值和新值并可以对比比较哪些字段值是否发生了变化NBearDataSource控件就是利用了这样的机制来直接重DataSourceControl和DataSourceView来达到数据的全自动修改和添加方案的
这里还有一点不得不说在GridViewDetailsView并不一定需要使用<%# Bind()%>语法来实现数据的双向绑定他们的字段双向绑定可以通过BoundField及它的子控件代替模板控件的绑定语法一样可以达到双向绑定的目的简单但没有模板来得灵活而在存取不同版本的字段值也是类似的机制
由于这部分涉及到的都是动态和内部代码如果没有亲自去阅读这些代码估计还是很难理解最后我们再来简单总结一下ASPNET在模板中双向绑定字段是通过<%# Bind() %>这样的语法但是Bind我们更应该把它理解为是一个关键字而不是一个函数因为在ASPNET的控件中并没有存在这个函数ASPNET运行时在编译页面代码时会把Bind关键字的代码当成两部分来编译一部分是单向绑定代码另一部分而是读取对应输入控件的绑定属性以绑定字段名为Key添加到IOrderedDictionary中收集返回给数据容器控件(GridViewDetailsViewFormView)等让它们处理
总体来说ASPNET 的双向绑定机制给我们在提交数据时带来了极大的方便尽管有些人很排斥DataSourceControl的模式但是我们不可否认合理应用会大大提高我们的开发效率希望通过这两篇的介绍我们能对ASPNET数据绑定机制有更多的认识在下一篇的文章中我们将会介绍一些关于数据绑定方式性能以及对字段名的局限性等相关主题