C# 在自定义控件的嵌套DropDownList中保存ViewState
我创建了一个自定义控件(称为BoostrapDropDown),它基本上围绕asp.net下拉列表包装了一堆boostrap标记。生成的控件层次结构基本上如下所示,除DropDownList外,所有内容都是HtmlGenericControl: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
<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的
回邮
尽管如此,我认为这是一个不需要(或不应该)回答的问题,原因有二:
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;
}