none
ListControl数据绑定时所出现的问题 RRS feed

  • 问题

  •  

    最近在做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;
    }
    
    而且当设置的属性值为null时,无论在哪个方法中都会忽略并且执行ClearSelection()方法清除已选择的选项。而问题就在这个地方,为什么我使用null属性值进行双向绑定的时候却没有被忽略,而是抛出了ArgumentOutOfRangeException在我调试之后发现,RadioButtonList在调用Bind方法进行数据绑定之后,实际cachedSelectedValue的值并不是null, 而是string.Empty,这让我很是困惑。所以无奈之下只好继续深入研究,以寻找产生这个问题的根本原因。

     

    在这个时候我想到了,asp.net页面第一次执行的时候都要进行预编译,而这个预编译过程中使用到的所有方法都在System.Web.Compilation命名空间下,所以只要在这个地方去寻找就应该能够结果。果然功夫不负有心人,最终发现在System.Web.Compilation.CodeDomUtility的GenerateConvertToString(CodeExpression value)方法中发现编译的过程中将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;
    }
    
    
    注:所有测试和研究均是在.net framework 4.0中进行,对其他版本的framework不知道是否也存在此问题。

    2011年4月19日 7:45

全部回复