自定义模板化asp.net控件的双向数据绑定

这个问题最初是关于双向绑定工作,但由于缺乏具体的答案和其他方面的进展,我一直在更新它 – 你可以检查编辑历史,但我认为这是更好的明晰。

下面的代码列表允许单个对象与模板化控件进行双向数据绑定。 我想以最简单的方式扩展此示例,以允许为最根对象的复杂类型属性嵌套类似的双向数据绑定启用的模板化控件。 例如, SampleFormData具有属性List Items 。 我希望能够在最根本模板(从此代码清单)中绑定到此列表,并在可编辑的文本框列表中显示字符串数据,可能还有插入,删除,重新绑定输入的命令-changes(返回绑定对象的List属性)。 此外,如果这是一个复杂类型的列表( SampleFormChildData ,而不是字符串), SampleSpecificEntryForm可以在列表中使用新的嵌入式SampleSpecificEntryForm ,绑定到每个列表的项目,如转发器。 如果作者如此选择的话,依此类推叶子简单的属性。 ui-fields不需要自动生成,只能用于绑定。

注意: List很特殊,因为即使是内置绑定也不能直接处理字符串作为DataItem – 直接绑定到字符串作为列表中的项目不是必需的,但肯定是有价值的。

这与FormView不同,因为它不是为了期望绑定到项目列表中的一个而构建的,而是仅构建为在视图状态中持久保存的单个项目。 与FormView不同,它只有一个类似于FormView的EditTemplate的默认模板。 同样,绑定到类似集合的属性也只有一个视图 – 编辑。 没有选择行然后编辑。 一切都是可编辑的。 目的是使双向绑定表单更容易构建。

在我看来,应该有两种绑定。 SingleEntityBindingCollectionBindingSingleEntityBinding将单个对象实例作为数据源(由SampleSpecificEntryForm原型),而CollectionBinding可以绑定到它的父SingleEntityBinding ,其属性为DataSourceID="EntryForm1" DataMember="Items"如下面DataList1的代码示例所示。 两种类型都应支持嵌套任何一种类型。 对支持对象的数据进行插入/更改/删除类型操作等列表操作是表单作者的责任; 但是,这种机制实施起来相对简单。

这是一些代码,希望它能帮到某些人。 200分是为了达到这个布局目标的最佳建议……

 using System.ComponentModel; using System.Collections.Specialized; using System.Collections.Generic; namespace System.Web.UI.WebControls.Special { [Serializable] public class SampleFormData { public string SampleString { get; set; } public int SampleInt { get; set; } public List Items { get; set; } public SampleFormData() { SampleString = "Sample String Data"; SampleInt = 5; Items = new List(); } } [ToolboxItem(false)] public class SampleSpecificFormDataContainer : WebControl, INamingContainer, IDataItemContainer { SampleSpecificEntryForm entryForm; internal SampleSpecificEntryForm EntryForm { get { return entryForm; } } [Bindable(true), Category("Data")] public string SampleString { get { return entryForm.FormData.SampleString; } set { entryForm.FormData.SampleString = value; } } [Bindable(true), Category("Data")] public int SampleInt { get { return entryForm.FormData.SampleInt; } set { entryForm.FormData.SampleInt = value; } } [Bindable(true), Category("Data")] public List Items { get { return entryForm.FormData.Items; } set { entryForm.FormData.Items = value; } } internal SampleSpecificFormDataContainer(SampleSpecificEntryForm entryForm) { this.entryForm = entryForm; } #region IDataItemContainer Members public object DataItem { get { return entryForm.FormData; } } public int DataItemIndex { get { return 0; } } public int DisplayIndex { get { return 0; } } #endregion } public class SampleSpecificEntryForm : DataBoundControl, INamingContainer, IDataSource { #region Template private IBindableTemplate formTemplate = null; [Browsable(false), DefaultValue(null), TemplateContainer(typeof(SampleSpecificFormDataContainer), ComponentModel.BindingDirection.TwoWay), PersistenceMode(PersistenceMode.InnerProperty)] public virtual IBindableTemplate FormTemplate { get { return formTemplate; } set { formTemplate = value; } } #endregion public override ControlCollection Controls { get { EnsureChildControls(); return base.Controls; } } private SampleSpecificFormDataContainer formDataContainer = null; [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public SampleSpecificFormDataContainer FormDataContainer { get { EnsureChildControls(); return formDataContainer; } } [Bindable(true), Browsable(false)] public SampleFormData FormData { get { SampleFormData data = ViewState["FormData"] as SampleFormData; if (data == null) { data = new SampleFormData(); ViewState["FormData"] = data; } return data; } } protected override void CreateChildControls() { if (!this.ChildControlsCreated) { this.ChildControlsCreated = true; Controls.Clear(); formDataContainer = new SampleSpecificFormDataContainer(this); Controls.Add(formDataContainer); FormTemplate.InstantiateIn(formDataContainer); } } protected override void PerformDataBinding(Collections.IEnumerable ignore) { CreateChildControls(); if (Page.IsPostBack) { //OrderedDictionary fields = new OrderedDictionary(); //ExtractValuesFromBindableControls(fields, formDataContainer); // Don't know what this would be for foreach (System.Collections.DictionaryEntry entry in formTemplate.ExtractValues(formDataContainer)) { if (((string)entry.Key).Equals("SampleString", StringComparison.Ordinal)) { FormData.SampleString = (string)entry.Value; } if (((string)entry.Key).Equals("SampleInt", StringComparison.Ordinal)) { int i; if (int.TryParse((string)entry.Value, out i)) { FormData.SampleInt = i; } } } } formDataContainer.DataBind(); } public SampleSpecificEntryForm() { this.PreRender += new EventHandler(SampleSpecificEntryForm_PreRender); } void SampleSpecificEntryForm_PreRender(object sender, EventArgs e) { SaveViewState(); } #region IDataSource Members public event EventHandler DataSourceChanged; public DataSourceView GetView(string viewName) { return new PropertyView(this, viewName); } public Collections.ICollection GetViewNames() { return new List() { "SampleString", "SampleInt", "Items" }; } #endregion } // Not yet used ... public class PropertyView : DataSourceView { SampleSpecificEntryForm owner; string viewName; protected override Collections.IEnumerable ExecuteSelect(DataSourceSelectArguments arguments) { if (viewName.Equals("SampleString", StringComparison.Ordinal)) { return new object[] { owner.FormData.SampleString }; } if (viewName.Equals("SampleInt", StringComparison.Ordinal)) { return new object[] { owner.FormData.SampleInt }; } if (viewName.Equals("Items", StringComparison.Ordinal)) { return new object[] { owner.FormData.Items }; } throw new InvalidOperationException(); } public PropertyView(SampleSpecificEntryForm owner, string viewName) : base(owner, viewName) { this.owner = owner; this.viewName = viewName; } } } 

使用ASP.NET页面如下:

      

Welcome to ASP.NET!

<asp:TextBox ID="txtSampleString" runat="server" Text=''>
<asp:TextBox ID="txtSampleInt" runat="server" Text=''>

(, ) - aka - (, )




Default2.aspx.cs:

 using System; namespace EntryFormTest { public partial class _Default2 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { EntryForm1.DataBind(); } } } 

我也实现了IDataSource,试图能够嵌套像这样的列表组件(在其中):

   <asp:TextBox ID="TextBox3" runat="server" Text="">      

关于如何以级联方式进行此工作的任何想法都会很棒(例如,在Items列表属性上)。 这里的挑战之一是Bind()不能引用数据绑定对象本身(在这种情况下是一个字符串),而是引用该项的属性 – 使得对列表的绑定变得笨拙。

谢谢你的帮助!


沿途发现

实现了IDataItemContainer。 我非常希望能解决这个问题,但不会。 没有明显的变化。 哎呀,在错误的类上实现它。 现在它是Binding,但是值不会在回发时反弹到绑定对象。 嗯…

正如本文所述 ,Page.GetDataItem()是exception的来源。 如果页面的_dataBindingContext为null或为空,则抛出此exception。 本文确实解释了这一点,但没有说明如何确保填充Page的_dataBindingContext。 我会继续寻找。

正如MSDN文档所述,DataBoundControl应该实现PerformDataBinding而不是重写DataBind()。 我已经这样做了并且做了双向绑定工作。 这段代码是必要的还是我应该使用内置的东西?

你试过Databinder.Eval(Container.DataItem,…)语法吗?

另见Bind()上的这篇文章。

PS。 除非使用Viewstate保存值,否则每次回发都需要Databind。