在这个系列的上篇中介绍了数据绑定语法的原理以及NET中如何实现单向绑定中篇我们简单的介绍了ASPNET 中新增的Bind语法配合DataSourceControl来实现数据的自动双向绑定这两部分的内容相对动态抽象并且不常接触没有很好的源代码支持很难解释清楚要想真正弄清它们的内部原理还需要大家亲自动手去反编译分析动态编译的程序集
在了解了数据绑定语法的原理后我还想来谈谈我中实践过程中遇到的一些问题以及其它实用的绑定技巧首先我们就来说说特殊字段名的问题我们知道在数据库当中如果表名或字段名中包含有一些特殊的不能是合法的字符时都会使用[]将它们引起来以便他们能够正常使用但是在<%# Eval()%>的绑定语句当中同时可以使用[]但是对于字段名中包含 ()[]这个字符却始终运行出错假设像我下面这样来绑定电压(V)
<%# Eval(电压(V))%>
那么就会得到一个运行时错误
DataBinding:SystemDataDataRowView不包含名为电压的属性
表明括号是被认为是一个特殊字符那我们如果给字段名加上[]如下
<%# Eval([电压(V)])%>
此时我们会得到另一个运行时错误
电压(V 既不是表 DataTable 的 DataColumn 也不是 DataRelation
表明即使加上[]也无法解决这个特殊字段名的问题同时字段名中如果也存在中括号也是会出现这样的问题的但是这样的字段名却在GridView的自动生成列中能被正常绑定呢?问题会出现在哪里呢?分析和对比GridView的自动生成列与Eval这样的绑定语法在最终执行绑定代码上的不同我们可以发现GridView的自动生成列取值并不是使用DataBinderEval这个方法它内部有自己的取值方式但是在实现上却是大同小异的那究竟是在哪里出现了问题呢?我们找出DataBinder类的定义
: [AspNetHostingPermission(SecurityActionLinkDemand Level=)]
: public sealed class DataBinder
: {
: // Fields
: private static readonly char[] expressionPartSeparator = new char[] { };
: private static readonly char[] indexExprEndChars = new char[] { ] ) };
: private static readonly char[] indexExprStartChars = new char[] { [ ( };
:
: // Methods
: public static object Eval(object container string expression)
: {
: if (expression == null)
: {
: throw new ArgumentNullException(expression);
: }
: expression = expressionTrim();
: if (expressionLength == )
: {
: throw new ArgumentNullException(expression);
: }
: if (container == null)
: {
: return null;
: }
: string[] expressionParts = expressionSplit(expressionPartSeparator);
: return Eval(container expressionParts);
: }
:
: private static object Eval(object container string[] expressionParts)
: {
: object propertyValue = container;
: for (int i = ; (i < expressionPartsLength) && (propertyValue != null); i++)
: {
: string propName = expressionParts[i];
: if (propNameIndexOfAny(indexExprStartChars) < )
: {
: propertyValue = GetPropertyValue(propertyValue propName);
: }
: else
: {
: propertyValue = GetIndexedPropertyValue(propertyValue propName);
: }
: }
: return propertyValue;
: }
:
: public static string Eval(object container string expression string format)
: {
: object obj = Eval(container expression);
: if ((obj == null) || (obj == DBNullValue))
: {
: return stringEmpty;
: }
: if (stringIsNullOrEmpty(format))
: {
: return objToString();
: }
: return stringFormat(format obj);
: }
:
: public static object GetDataItem(object container)
: {
: bool flag;
: return GetDataItem(container out flag);
: }
:
: public static object GetDataItem(object container out bool foundDataItem)
: {
: if (container == null)
: {
: foundDataItem = false;
: return null;
: }
: IDataItemContainer container = container as IDataItemContainer;
: if (container != null)
: {
: foundDataItem = true;
: return containerDataItem;
: }
: string name = DataItem;
: PropertyInfo property = containerGetType()GetProperty(name BindingFlagsPublic | BindingFlagsInstance | BindingFlagsIgnoreCase);
: if (property == null)
: {
: foundDataItem = false;
: return null;
: }
: foundDataItem = true;
: return propertyGetValue(container null);
: }
:
: public static object GetIndexedPropertyValue(object container string expr)
: {
: if (container == null)
: {
: throw new ArgumentNullException(container);
: }
: if (stringIsNullOrEmpty(expr))
: {
: throw new ArgumentNullException(expr);
: }
: object obj = null;
: bool flag = false;
: int length = exprIndexOfAny(indexExprStartChars);
: int num = exprIndexOfAny(indexExprEndChars length + );
: if (((length < ) || (num < )) || (num == (length + )))
: {
: throw new ArgumentException(SRGetString(DataBinder_Invalid_Indexed_Expr new object[] { expr }));
: }
: string propName = null;
: object obj = null;
: string s = exprSubstring(length + (num length) )Trim();
: if (length != )
: {
: propName = exprSubstring( length);
: }
: if (sLength != )
: {
: if (((s[] == ) && (s[sLength ] == )) || ((s[] == \) && (s[sLength ] == \)))
: {
: obj = sSubstring( sLength );
: }
: else if (charIsDigit(s[]))
: {
: int num;
: flag = intTryParse(s NumberStylesInteger CultureInfoInvariantCulture out num);
: if (flag)
: {
: obj = num;
: }
: else
: {
: obj = s;
: }
: }
: else
: {
: obj = s;
: }
: }
: if (obj == null)
: {
: throw new ArgumentException(SRGetString(DataBinder_Invalid_Indexed_Expr new object[] { expr }));
: }
: object propertyValue = null;
: if ((propName != null) && (propNameLength != ))
: {
: propertyValue = GetPropertyValue(container propName);
: }
: else
: {
: propertyValue = container;
: }
: if (propertyValue == null)
: {
: return obj;
: }
: Array array = propertyValue as Array;
: if ((array != null) && flag)
: {
: return arrayGetValue((int) obj);
: }
: if ((propertyValue is IList) && flag)
: {
: return ((IList) propertyValue)[(int) obj];
: }
: PropertyInfo info = propertyValueGetType()GetProperty(Item BindingFlagsPublic | BindingFlagsInstance null null new Type[] { objGetType() } null);
: if (info == null)
: {
: throw new ArgumentException(SRGetString(DataBinder_No_Indexed_Accessor new object[] { propertyValueGetType()FullName }));
: }
: return infoGetValue(propertyValue new object[] { obj });
: }
:
: public static string GetIndexedPropertyValue(object container string propName string format)
: {
: object indexedPropertyValue = GetIndexedPropertyValue(container propName);
: if ((indexedPropertyValue == null) || (indexedPropertyValue == DBNullValue))
: {
: return stringEmpty;
: }
: if (stringIsNullOrEmpty(format))
: {
: return indexedPropertyValueToString();
: }
: return stringFormat(format indexedPropertyValue);
: }
:
: public static object GetPropertyValue(object container string propName)
: {
: if (container == null)
: {
: throw new ArgumentNullException(container);
: }
: if (stringIsNullOrEmpty(propName))
: {
: throw new ArgumentNullException(propName);
: }
: PropertyDescriptor descriptor = TypeDescriptorGetProperties(container)Find(propName true);
: if (descriptor == null)
: {
: throw new HttpException(SRGetString(DataBinder_Prop_Not_Found new object[] { containerGetType()FullName propName }));
: }
: return descriptorGetValue(container);
: }
:
: public static string GetPropertyValue(object container string propName string format)
: {
: object propertyValue = GetPropertyValue(container propName);
: if ((propertyValue == null) || (propertyValue == DBNullValue))
: {
: return stringEmpty;
: }
: if (stringIsNullOrEmpty(format))
: {
: return propertyValueToString();
: }
: return stringFormat(format propertyValue);
: }
:
: internal static bool IsNull(object value)
: {
: if ((value != null) && !ConvertIsDBNull(value))
: {
: return false;
: }
: return true;
: }
: }
其中我们可以发现有三个静态只读变量
private static readonly char[] expressionPartSeparator = new char[] { };private static readonly char[] indexExprEndChars = new char[] { ] ) };private static readonly char[] indexExprStartChars = new char[] { [ ( };
OK我们先不看代码就应该知道问题就出在这个地方当我们分析哪里用到indexExprEndChars时分找到这个方法
public static object GetIndexedPropertyValue(object container string expr)
我们不需要阅读里面的代码通过下面的expr参数注释我们就可以很快得到答案
expr 从 container 对象到要放置在绑定控件属性中的公共属性值的导航路径此路径必须是以点分隔的属性或字段名称字符串如 C# 中的 Tables[]DefaultView[]Price 或 Visual Basic 中的 Tables()DefaultView()Price 它告诉我们我们不仅可以使用字段名的方式同时还可以使用索引下标的方式来绑定字段值(C#和VB分别使用[]和()来取索引值)正因为如此我们才不可以在字段名中使用括号和中括号如上我们假设电压(V)字段的索引下标是那么我们可以像下面这样绑定来解决特别字段名带来的问题 <td><%# Eval([]))%></td> 上面的注释同时还告诉我们是可以通过一个对象的导航路径如 对象属性子属性 的方式来绑定一个数据项的间接属性这个我们可以通过对expressionPartSeparator静态字段的使用得以验证
: public static object Eval(object container string expression)
: {
: if (expression == null)
: {
: throw new ArgumentNullException(expression);
: }
: expression = expressionTrim();
: if (expressionLength == )
: {
: throw new ArgumentNullException(expression);
: }
: if (container == null)
: {
: return null;
: }
: string[] expressionParts = expressionSplit(expressionPartSeparator);
: return Eval(container expressionParts);
: }
: private static object Eval(object container string[] expressionParts)
: {
: object propertyValue = container;
: for (int i = ; (i < expressionPartsLength) && (propertyValue != null); i++)
: {
: string propName = expressionParts[i];
: if (propNameIndexOfAny(indexExprStartChars) < )
: {
: propertyValue = GetPropertyValue(propertyValue propName);
: }
: else
: {
: propertyValue = GetIndexedPropertyValue(propertyValue propName);
: }
: }
: return propertyValue;
: }
前面的那个Eval重载把expression表达式用expressionPartSeparator字符分隔开然后调用内部的Eval(objectstring[])重载在这个重载中按顺序去一级一级递归遍历属性值最终找到最后的那个绑定字段值所以我们是可以绑定跨级的间接属性和关联DataRowRelation行的值
还想在再来说说其它的绑定方式李涛在它的博客浅谈NET中的数据绑定表达式(二)中提到了绑定数据的七种方式分别为
<%#ContainerDataItem%>
<%#GetDataItem()%>
<%#Eval(字段名)%>
<%#DataBinderEval(ContainerDataItem字段名)%>
<%#((DataRowView)ContainerDataItem)[字段名] %>
<%#((Type)ContainerDataItem)成员 %>
<%#((Type)GetDataItem())成员 %>
如果按要我来分的话我只会分成两类强类型绑定和反射绑定不论是ContainerDataItem还是GetDataItem()都是得到当前的正在绑定的上下文数据对象然后转换成他们的原始类型使用索引或强类型的方式来绑定字段值而Eval就是使用反射的方式来进行通用化的绑定这样我们就完全没有必要关心被绑定的数据源是什么类型在很多场合下这是非常有益的
从性能方式来考虑强类型绑定肯定要比反射绑定性能来得好这其中的原因就不多作解释了但是对于强类型来说是使用ContainerDataItem还是GetDataItem的方式来取得上下文数据对象性能应该差别不大的我们在前面已经提到到在Page的作用域内会把所有的被绑定(遍历的数据项或整个集合)保存在一个堆栈方面我们来读取我们只需要读取堆栈的顶部元素就可以方便的得到当前正在被绑定数据行项而Container而更像是一个动态的关键字作用的变量因为你在绑定不同对象时Container的类型是不一样的假设你当前正在绑定Repeater那么它的类型是RepeaterItem只是为了方便我们强类型取得当前Repeater行对象而产生的动态属性其实它并不是Page的一个公有或私有属性所以我认为两种取得DataItem的方式在性能上实际是没有多大区别的
当然我们在选择是使用强类型绑定还是反射绑定时主要还是取决你的需要我个人认为为了使用解决方案通用化而不必在关心绑定的数据类型是什么类型应尽量使用Eval的方式来绑定字段在实践当中绑定字段的消费上还不是非常多的为了灵活和通用这点性能损失我认为是值得的另外就是如上的特殊字段的情况我当然也可以使用强类型绑定的方式来解决
<%#((SystemDataDataRowView)ContainerDataItem)[电压(a)]%>
特殊字段的解决之道有很多比如我们还可以重写Page的Eval方法达到我们的目的选择哪种方案就是取决于我们实际需要了
上面我们从特殊字段名出发分析了DataBinder在反射取得字段值时所做的一些特殊处理进而引出我们平常可能会被忽略的一些非常有用的绑定方式如索引下标绑定和间接字段绑定而这些对于我们解决一些疑难问题会有很大的帮助特别跨级的字段绑定如果我们没有了解的话可能就需要在服务器代码中做很多的类型转换和处理最后我们还讨论了其它的几种绑定方式以及它们各种的性能和应用场合
三天用篇文章来分析了ASPNET在数据绑定的一个原理其中很多内容并不是我们平常数据绑定时所需要掌握的知识但是掌握了它们却对我们在数据绑定时有更多的把握正因为内容的动态性和过于抽象而本人又无法找到一种最为合适的语言来组织和解释这些知识代码太多全部贴出来又感觉找不到重点贴重要的部分又感觉跨度太大所以三篇下来很多要领解释的不是很清楚大家权当它是一个引子更多的原理还需要大家自己亲自去分析和阅读代码