最近在做webform开发的时候用到了RadioButtonList的数据绑定的功能。由于SelectedValue属性是支持TwoWay数据绑定的,所以可以使用Bind方法进行双向绑定。虽然在Visual Studio的智能提示中没有出现SelectedValue,但是仍然是可以使用的。在使用Reflector或者ILSpy查看ListControl(RadioButtonList继承自ListControl)的SelectedValue属性就可以证明这一点。
// System.Web.UI.WebControls.ListControl [Bindable(true, BindingDirection.TwoWay), WebCategory("Behavior"), Browsable(false), DefaultValue(""), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Themeable(false), WebSysDescription("ListControl_SelectedValue")] public virtual string SelectedValue ......
所以我创建了用于绑定的数据对象,如下所示
[Serializable] public class CandidateReferee { public System.String Email { get; set; } public Guid? RefereeApproach { get; set; } public System.String Name { get; set; } public System.String Address { get; set; } public System.String Telephone { get; set; } }
接下来我把RadioButtonList放到了GridView TemplateField的ItemTemplate中
<asp:RadioButtonList ID="radApproach" DataSourceID="DataSourceApproach" DataTextField="CodeName" DataValueField="CodeValue" RepeatDirection="Horizontal" runat="server" SelectedValue='<%# Bind("RefereeApproach")%>'> </asp:RadioButtonList>
在数据绑定时我提供的RefereeApproach属性值为null, 但是在程序运行的时候却出现了ArgumentOutOfRangeException
后来经过深入的研究发现,使用ListControl时如果存在了DataSourceID属性的情况下,如果还没有运行到OnPreRender的步骤的话,无论设置什么SelectedValue都不会出错,只有在OnPreRender时调用数据绑定的方法时才去进行验证。而此时它会把SelectedValue的属性值存放在cachedSelectedValue这个field中。如下所示:
[Bindable(true, BindingDirection.TwoWay), WebCategory("Behavior"), Browsable(false), DefaultValue(""), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Themeable(false), WebSysDescription("ListControl_SelectedValue")] public virtual string SelectedValue { get { ...... } set { if (this.Items.Count != 0) { if (value == null || (base.DesignMode && value.Length == 0)) { this.ClearSelection(); return; } ListItem listItem = this.Items.FindByValue(value); bool flag = this.Page != null && this.Page.IsPostBack && this._stateLoaded; if (flag && listItem == null) { throw new ArgumentOutOfRangeException("value", SR.GetString("ListControl_SelectionOutOfRange", new object[] { this.ID, "SelectedValue" })); } if (listItem != null) { this.ClearSelection(); listItem.Selected = true; } } <strong>this.cachedSelectedValue = value;</strong> } }
// System.Web.UI.WebControls.ListControl protected internal override void PerformDataBinding(IEnumerable dataSource) { ...... <strong>if (this.cachedSelectedValue == null) { if (this.cachedSelectedIndex != -1) { this.SelectedIndex = this.cachedSelectedIndex; this.cachedSelectedIndex = -1; } return; }</strong> int num = this.Items.FindByValueInternal(this.cachedSelectedValue, true); if (-1 == num) { throw new ArgumentOutOfRangeException("value", SR.GetString("ListControl_SelectionOutOfRange", new object[] { this.ID, "SelectedValue" })); } if (this.cachedSelectedIndex != -1 && this.cachedSelectedIndex != num) { throw new ArgumentException(SR.GetString("Attributes_mutually_exclusive", new object[] { "SelectedIndex", "SelectedValue" })); } this.SelectedIndex = num; this.cachedSelectedValue = null; this.cachedSelectedIndex = -1; }
在这个时候我想到了,asp.net页面第一次执行的时候都要进行预编译,而这个预编译过程中使用到的所有方法都在System.Web.Compilation命名空间下,所以只要在这个地方去寻找就应该能够结果。果然功夫不负有心人,最终发现在System.Web.Compilation.CodeDomUtility的GenerateConvertToString(CodeExpressionvalue)方法中发现编译的过程中将Bind方法转成调用Convert.ToString(object value, IFormatProvider provider),代码如下所示:
internal static CodeExpression GenerateConvertToString(CodeExpression value) { CodeMethodInvokeExpression expression = new CodeMethodInvokeExpression { Method = { TargetObject = BuildGlobalCodeTypeReferenceExpression(typeof(Convert)), MethodName = "ToString" } }; expression.Parameters.Add(value); expression.Parameters.Add(new CodePropertyReferenceExpression(BuildGlobalCodeTypeReferenceExpression(typeof(CultureInfo)), "CurrentCulture")); return expression; }
而恰恰就是在Convert中将null转成了string.Empty而使ListControl无法对其进行忽略的。如下所示
public static string ToString(object value, IFormatProvider provider) { IConvertible convertible = value as IConvertible; if (convertible != null) return convertible.ToString(provider); IFormattable formattable = value as IFormattable; if (formattable != null) return formattable.ToString(null, provider); if (value != null) return value.ToString(); return string.Empty; }