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

C# 在自定义控件的嵌套DropDownList中保存ViewState,c#,asp.net,custom-controls,viewstate,composite-controls,C#,Asp.net,Custom Controls,Viewstate,Composite Controls,我创建了一个自定义控件(称为BoostrapDropDown),它基本上围绕asp.net下拉列表包装了一堆boostrap标记。生成的控件层次结构基本上如下所示,除DropDownList外,所有内容都是HtmlGenericControl: <div class="form-group viInputID"> <label for="iInputID" class="control-label liInputID"></l

我创建了一个自定义控件(称为BoostrapDropDown),它基本上围绕asp.net下拉列表包装了一堆boostrap标记。生成的控件层次结构基本上如下所示,除DropDownList外,所有内容都是HtmlGenericControl:

        <div class="form-group viInputID">
            <label for="iInputID" class="control-label liInputID"></label>
            <a style="display: none;" class="vhiInputID" role="button" tabindex="0" data-toggle="popover" data-trigger="click" data-content-selector=".hiInputID" data-placement="top">
                <span class="glyphicon glyphicon-info-sign help-icon"></span>
            </a>
            <a style="display: none;" class="vsiInputID" role="button" tabindex="0">
                <span class="glyphicon glyphicon-volume-up"></span>
            </a>
            <div class="validator-container">
                <asp:DropDownList CssClass="form-control selectpicker show-tick iInputID" data-size="15" ID="iInputID" runat="server" DataSource='<%# DataSource %>' DataTextField="name" DataValueField="key"/>
                <span class="error-msg" data-toggle="tooltip" data-placement="top"></span>
            </div>
            <div class="hiInputIDTitle" style="display: none;"></div>
            <div class="hiInputID" style="display: none;"></div>
        </div>
这就是我的困惑

  • CreateChildControls
    期间,
    DataSource
    只会出现在原始渲染中。因此,我在嵌套的DropDownList上调用
    DataBind
    ,让它第一次填充,然后将所有控件项存储回
    Items
    属性
  • 我非常确定我了解如何将
    持久化到ViewState或从ViewState加载
  • 我迷路的地方是,我的
    项目
    属性如何用于重新填充DropDownList?我想这可能是因为我添加了
    Load\SaveViewState
    (称为
    base.Load\SaveViewState
    )才真正解决了我的问题,但是当我注释掉了对
    Items
    属性的所有引用时,我又丢失了下拉列表值

  • Items
    如何重新填充
    inputControl.Items
    回发

    我知道最终的问题是:

    项目是如何重新填充inputControl.Items的 回邮

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

  • 您的初始要求声明:

    我创建了一个自定义控件 围绕asp.net DropDownList包装一组boostrap标记

  • 事实上,您的代码(我指的是您的代码的原始版本,对于我们的讨论来说已经足够长了)包含了许多与在ViewState中持久化复杂类型的自定义控件属性有关的技术(
    LoadViewState
    SaveViewState
    Triplet
    IStateManager
    等),但在您的案例中,大多数都是不需要的,因为(此时您的需求陈述变得至关重要):

    BootstrapDropDown
    只是一个复合自定义控件,它嵌入了一个
    DropDownList
    ,可以(而且应该)将所有工作委托给它

  • 事实上,对于
    文本
    属性,您已经做得很好了。为什么不对
    属性也这样做呢?您的控件通过组合工作。它不需要维护自己的
    列表项集合
    ,更不用说在ViewState中传递它了

    最后但并非最不重要的一点是,记住嵌入式服务器控件将自动管理自己的ViewState。换句话说,您无需手动管理
    inputControl
    的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 */
        }
    }
    
    ASPX

    <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>
    
    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
    集合后被数据绑定。这一点很重要,因为向集合添加控件也是控件开始跟踪其视图状态的点。您可以阅读更多(或全部)在这篇优秀的文章中:

    此外,我在Dino Esposito的这篇文章中找到了对
    IStateManager
    机制的引用:


    你能告诉我们什么是
    BootstrapInputBase
    吗?我的意思是,
    BootstrapDropDown
    真正继承自哪里?@dpant我没有把我问题的代码整理得足够好。本质上它只是
    网络控制,在一个名为
    的容器中
    BootstrapInputBase
    基本上是负责创建所有
    HtmlGenericControl
    层次结构,并有一个虚拟方法生成
    验证程序容器
    控件,该控件将
    BootstrapDropDownList
    覆盖并实现。对于这个问题,我尝试将所有内容合并到一个psuedo类中,以便于理解。我适当地更新了我的问题,并添加了一个示例control hierarchy output.Thank.我这样问是因为我尽可能多地使用您的代码(假设它是从
    WebControl
    继承的),
    DataSource
    的值过去(现在仍然是)
    null
    (当
    CreateChildControls
    执行时)所以没有数据绑定发生。所以,我认为类层次结构中可能还有其他东西…无论如何,绑定只有在我在
    Page\u Load
    中设置
    DataSource
    属性时才有效,而不是在markup中。好的,我已经添加了在我的asp.net 4.51项目中正确工作的完整代码列表。你的最后一段是他回答说:“是的,我清理了我的code wrt Items属性。但是我更改了逻辑,以确保在调用databind之前,我的“包含的”控件已添加到页面集合中。在将它们添加到Page.controls之前,我的原始流称为databind。我仍然对我的代码的工作方式有点好奇,但看起来您的方式是正确的方式,我会忽略它,很快就会忘记:)。
    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 */
        }
    }
    
    <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>
    
    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;
    }