将ViewState保存在自定义控件的嵌套DropDownList中

时间:2019-05-24 20:44:37

标签: c# asp.net custom-controls viewstate composite-controls

我创建了一个自定义控件(称为BoostrapDropDown),该控件实际上将一堆boostrap标记包装在asp.net DropDownList周围。我正在从控件中“传递” DataSource属性到嵌套的DropDownList,但是回发时,我丢失了所有值。

这是令人尴尬的部分。一个月前,我在网上搜索并能够创建解决方案,但是我没有很好地记录下来。现在,我找不到用于创建解决方案的页面。现在我已经知道它是如何工作的,我希望有人能有所启发。下面是相关的源代码。

public class BootstrapDropDown : BootstrapInputBase
{
    private DropDownList inputControl;

    public string DataTextField
    {
        get => (string)ViewState[ nameof( DataTextField ) ];
        set => ViewState[ nameof( DataTextField ) ] = value;
    }
    public string DataValueField
    {
        get => (string)ViewState[ nameof( DataValueField ) ];
        set => ViewState[ nameof( DataValueField ) ] = value;
    }

    public object DataSource { get; set; }

    ListItemCollection items;
    public virtual ListItemCollection Items
    {
        get
        {
            if ( items == null )
            {
                items = new ListItemCollection();
                if ( IsTrackingViewState )
                {
                    ( (IStateManager)items ).TrackViewState();
                }
            }
            return items;
        }
    }

    protected override void LoadViewState( object savedState )
    {
        var triplet = (Triplet)savedState;
        Value = (string)triplet.Third;
        ( (IStateManager)Items ).LoadViewState( triplet.Second );
        base.LoadViewState( triplet.First );
    }

    protected override object SaveViewState()
    {
        var triplet = new Triplet();
        triplet.First = base.SaveViewState();
        triplet.Second = ( (IStateManager)Items ).SaveViewState();
        triplet.Third = Value;
        return triplet;
    }

    public override string Value
    {
        get
        {
            EnsureChildControls();
            return inputControl.SelectedValue;
        }
        set
        {
            EnsureChildControls();
            inputControl.SelectedValue = value;
        }
    }

    public virtual string Text
    {
        get
        {
            EnsureChildControls();
            return inputControl.SelectedItem?.Text;
        }
    }

    protected override void CreateChildControls()
    {
        /* Added other html markup controls */

        var validatorContainer = new HtmlGenericControl( "div" );
        validatorContainer.Attributes[ "class" ] = "validator-container";

        inputControl = new DropDownList() { CssClass = "form-control selectpicker show-tick " + ID, ID = ID, DataValueField = DataValueField, DataTextField = DataTextField, DataSource = DataSource };

        inputControl.Attributes[ "data-size" ] = "15";
        inputControl.Attributes[ "data-live-search" ] = "true";
        validatorContainer.Controls.Add( inputControl );

        if ( DataSource != null )
        {
            inputControl.DataBind();
            Items.AddRange( inputControl.Items.Cast<ListItem>().ToArray() );
        }

        validatorContainer.Controls.Add( CreateErrorMessage() );

        Controls.Add( validatorContainer );

        /* Added other html markup controls */
    }
}

该控件通过以下方式在标记中使用:

<mh:BootstrapDropDown runat="server" ID="iGroup" Label="Select Group Name" EnableViewState="true" DataSource='<%# Groups %>' DataTextField="Text" DataValueField="Value" />

这是我的困惑...

  1. CreateChildControls期间,DataSource仅存在于原始渲染中。因此,我在嵌套的DropDownList上调用DataBind,以使其首次填充,然后将所有控件Items存储回Items属性。
  2. 我非常确定我了解Items是如何持久保存到ViewState或从ViewState加载的。
  3. 我迷路之处, my Items属性又如何习惯于重新填充DropDownList?我以为可能是因为我添加了Load\SaveViewState(称为base.Load\SaveViewState)才真正解决了我的问题,但是当我注释掉对我的Items属性的所有引用时,再次失去了下拉列表值。

Items在回发时如何重新填充inputControl.Items

1 个答案:

答案 0 :(得分:1)

我知道最终的问题是:

  

Items在世界上如何重新填充inputControl.Items   回发?!

尽管如此,我认为这是一个不需要(或不应)回答的问题,其原因有两个:

  1. 您的初始要求声明:

      

    我创建了一个自定义控件,   在asp.net DropDownList周围包裹一堆boostrap标记。

  2. 您的代码(我指的是您的代码的原始版本,对于我们的讨论来说足够好并且足够长)这一事实包含了许多技术,这些技术与将复杂类型的自定义控件属性持久保存在ViewState(SaveViewStateTripletIStateManagerBootstrapDropDown等),但大多数是 not 在您的情况下是必需的,因为(此时,您的要求声明变得至关重要)

    DropDownList只是一个嵌入Text的复合自定义控件,它可以(并且应该)委托对其起作用!

实际上,您已经为ValueItems属性很好地做到了。为什么也对ListItemCollection属性不这样做?您的控件通过组成起作用。它不需要维护自己的inputControl,更不用说在ViewState中传递它了。

最后但并非最不重要的一点是,记住嵌入式服务器控件将自动管理它们自己的ViewState,这一点非常重要。换句话说,您无需手动管理public class BootstrapDropDown : WebControl, INamingContainer { private DropDownList inputControl; public string DataTextField { get => (string)ViewState[nameof(DataTextField)]; set => ViewState[nameof(DataTextField)] = value; } public string DataValueField { get => (string)ViewState[nameof(DataValueField)]; set => ViewState[nameof(DataValueField)] = value; } public IEnumerable DataSource { get; set; } public virtual ListItemCollection Items { get { EnsureChildControls(); return inputControl.Items; } } public virtual string Value { get { EnsureChildControls(); return inputControl.SelectedValue; } set { EnsureChildControls(); inputControl.SelectedValue = value; } } public virtual string Text { get { EnsureChildControls(); return inputControl.SelectedItem?.Text; } } protected override void CreateChildControls() { /* Added other html markup controls described above */ var validatorContainer = new HtmlGenericControl("div"); validatorContainer.Attributes["class"] = "validator-container"; inputControl = new DropDownList() { CssClass = "form-control selectpicker show-tick " + ID, ID = ID, DataValueField = DataValueField, DataTextField = DataTextField, DataSource = DataSource }; inputControl.Attributes["data-size"] = "15"; inputControl.Attributes["data-live-search"] = "true"; validatorContainer.Controls.Add(inputControl); Controls.Add(validatorContainer); if (DataSource != null) { inputControl.DataBind(); } /* Added other html markup controls described */ } } 的ViewState。

话虽如此,这是一个基于您(原始)代码的示例,该示例可以在不使用黑魔法的情况下工作:

<mh:BootstrapDropDown 
    runat="server" 
    ID="iGroup" 
    Label="Select Group Name" 
    DataSource='<%# Groups %>' 
    DataTextField="Text" 
    DataValueField="Value" />
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" /><br />
<asp:Label ID="Label1" runat="server" Text=""></asp:Label><br />
<asp:Label ID="Label2" runat="server" Text=""></asp:Label>

ASPX

protected System.Collections.ArrayList Groups
{
    get
    {
        var al = new System.Collections.ArrayList();
        al.Add(new ListItem("[Select a Group]", ""));
        al.Add(new ListItem("Group A", "A"));
        al.Add(new ListItem("Group B", "B"));
        return al;
    }
}

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        DataBind();
    }
}

protected void Button1_Click(object sender, EventArgs e)
{
    Label1.Text = iGroup.Text;
    Label2.Text = iGroup.Value;
}

背后的代码

inputControl

还有最后一件事值得一提。请注意,将Controls添加到IStateManager集合中后,将其绑定到数据中。这很重要,因为向集合添加控件也是控件开始跟踪其ViewState的关键。您可以在这篇出色的文章中了解更多(或全部)内容:

https://weblogs.asp.net/infinitiesloop/Truly-Understanding-Viewstate

此外,我在Dino Esposito的这篇文章中找到了对{{1}}机制的参考:

https://www.itprotoday.com/web-application-management/inside-aspnet-control-properties