简体   繁体   English

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

[英]Two-way databinding of a custom templated asp.net control

This question was originally about getting two-way binding to work at all, but due to lack of specific answers and otherwise progress along the way, I've been updating it - You can check the edit history, but I figured this is better for clarity. 这个问题最初是关于双向绑定工作,但由于缺乏具体的答案和其他方面的进展,我一直在更新它 - 你可以检查编辑历史,但我认为这是更好的明晰。

The code listing below allows a single object to be two-way databound to a templated control. 下面的代码列表允许单个对象与模板化控件进行双向数据绑定。 I'd like to extend this example in the simplest way possible to allow for nesting of similarly two-way databinding-enabled templated controls for complex-typed properties of the root-most object. 我想以最简单的方式扩展此示例,以允许为最根对象的复杂类型属性嵌套类似的双向数据绑定启用的模板化控件。 For example, SampleFormData has a property List<string> Items . 例如, SampleFormData具有属性List<string> Items I'd like to be able to bind to this list within the root-most template (from this code listing), and either display the string data in an editable list of textboxes, perhaps, with commands for insert, delete, rebind-entered-changes (back to the bound object's List property). 我希望能够在最根本模板(从此代码清单)中绑定到此列表,并在可编辑的文本框列表中显示字符串数据,可能还有插入,删除,重新绑定输入的命令-changes(返回绑定对象的List属性)。 Also, if this were a list of a complex type ( SampleFormChildData , rather than string), a new embedded SampleSpecificEntryForm could be used within the list, bound to each of the list's items, like a repeater. 此外,如果这是一个复杂类型的列表( SampleFormChildData ,而不是字符串), SampleSpecificEntryForm可以在列表中使用新的嵌入式SampleSpecificEntryForm ,绑定到每个列表的项目,如转发器。 And so on down to the leaf-simple properties, if the author so chooses. 如果作者如此选择,那么直到叶子简单的属性。 The ui-fields need not be auto-generated, just available for binding. ui-fields不需要自动生成,只能用于绑定。

Note: The case of the List<string> is special because even the built-in bindings can't handle string as the DataItem directly - binding to strings directly as items in our list is not a requirement, but certainly valuable. 注意: List<string>很特殊,因为即使是内置绑定也不能直接处理字符串作为DataItem - 直接绑定到字符串作为列表中的项目不是必需的,但肯定是有价值的。

This is different from a FormView because it is not built to expect to bind to one of a list of items, only to a single item as persisted in viewstate or where ever. 这与FormView不同,因为它不是为了期望绑定到项目列表中的一个而构建的,而是仅构建为在视图状态中持久保存的单个项目。 Unlike the FormView, this only has a single default template akin to FormView's EditTemplate. 与FormView不同,它只有一个类似于FormView的EditTemplate的默认模板。 Likewise, binding to a collection-like property would also only have one view - edit. 同样,绑定到类似集合的属性也只有一个视图 - 编辑。 There is no selection of the row and then editing. 没有选择行然后编辑。 Everything is editable all the time. 一切都是可编辑的。 The purpose is to make two-way bound forms easier to build. 目的是使双向绑定表单更容易构建。

Seems to me that there ought to be two kinds of binding. 在我看来,应该有两种绑定。 SingleEntityBinding and CollectionBinding . SingleEntityBindingCollectionBinding SingleEntityBinding takes a single object instance as a datasource (as prototyped by SampleSpecificEntryForm ) while CollectionBinding could be bound to it's parent SingleEntityBinding with attributes of DataSourceID="EntryForm1" DataMember="Items" as in the code example for DataList1 below. SingleEntityBinding将单个对象实例作为数据源(由SampleSpecificEntryForm原型),而CollectionBinding可以绑定到它的父SingleEntityBinding ,其属性为DataSourceID="EntryForm1" DataMember="Items"如下面DataList1的代码示例所示。 Nesting of either type should be supported in either type. 任何一种类型都应支持任一类型的嵌套。 List manipulation such as insert/change/delete type operations against the backing-object's data are the responsibility of the form author; 对支持对象的数据进行插入/更改/删除类型操作等列表操作是表单作者的责任; however, such mechanics would be relatively simple to implement. 但是,这种机制实施起来相对简单。

Here's some code, hope it helps somebody. 这是一些代码,希望它能帮到某些人。 200 points are out there for the best suggestions toward this laid-out goal... 对于这个布局目标,最好的建议是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<string> Items { get; set; }

        public SampleFormData()
        {
            SampleString = "Sample String Data";
            SampleInt = 5;
            Items = new List<string>();
        }
    }

    [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<string> 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<string>() { "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;
        }
    }
}

With an ASP.NET page the following: 使用ASP.NET页面如下:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="Default2.aspx.cs" Inherits="EntryFormTest._Default2" EnableEventValidation="false" %>

<%@ Register Assembly="EntryForm" Namespace="System.Web.UI.WebControls.Special" TagPrefix="cc1" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Welcome to ASP.NET!
    </h2>
        <cc1:SampleSpecificEntryForm ID="EntryForm1" runat="server">
    <FormTemplate>
        <asp:TextBox ID="txtSampleString" runat="server" Text='<%# Bind("SampleString") %>'></asp:TextBox><br />
        <asp:TextBox ID="txtSampleInt" runat="server" Text='<%# Bind("SampleInt") %>'></asp:TextBox><br />
        <h3>
            (<%# Container.SampleString %>, <%# Container.SampleInt %>) - aka - 
            (<%# DataBinder.Eval(Container, "SampleString")%>, <%# DataBinder.Eval(Container, "SampleInt")%>)</h3>
        <br />
        <asp:Button ID="btnUpdate" runat="server" Text="Update" /><br />
        <br />
    </FormTemplate>
</cc1:SampleSpecificEntryForm>
</asp:Content>

Default2.aspx.cs: 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();
        }
    }
}

I've implemented IDataSource as well, in an attempt to be able to nest a list component like so (within the ): 我也实现了IDataSource,试图能够嵌套像这样的列表组件(在其中):

<asp:DataList ID="DataList1" runat="server" DataSourceID="EntryForm1" DataMember="Items">
    <EditItemTemplate>
        <asp:TextBox ID="TextBox3" runat="server" Text="<%# Bind(".") %>"></asp:TextBox>
    </EditItemTemplate>
    <FooterTemplate>
        <asp:Button ID="Button2" runat="server" Text="Add" CommandName="Add" />
    </FooterTemplate>
</asp:DataList>

Any thoughts on how to make this work in a cascading way would be awesome (on the Items list property, for example). 关于如何以级联方式进行此工作的任何想法都很棒(例如,在Items列表属性上)。 One of the challenges here is that Bind() can't refer to the databound object itself (a string in this case) but on a property of that item - making binding to a list awkward. 这里的挑战之一是Bind()不能引用数据绑定对象本身(在这种情况下是一个字符串)但是在该项的属性上 - 使得对列表的绑定变得笨拙。

Thanks for any help! 谢谢你的帮助!


Discoveries along the way 沿途发现

Implemented IDataItemContainer. 实现了IDataItemContainer。 I was very hopeful that this would fix it, but no. 我非常希望能解决这个问题,但不会。 No noticable change. 没有明显的变化。 Oops, implemented it on the wrong class. 糟糕,在错误的类上实现它。 Now it is Binding, but the values aren't being rebound to the bound object on postback. 现在它是Binding,但是值不会在回发时反弹到绑定对象。 Hmmm... 嗯...

As this article suggests, Page.GetDataItem() is the source of the exception. 正如 本文所述 ,Page.GetDataItem()是异常的来源。 This exception is thrown if the page's _dataBindingContext is null or empty. 如果页面的_dataBindingContext为null或为空,则抛出此异常。 The article does explain this, but it doesn't say how to ensure that the Page's _dataBindingContext is populated. 本文确实解释了这一点,但没有说明如何确保填充Page的_dataBindingContext。 I'll continue looking. 我会继续寻找。

As the MSDN documentation says, DataBoundControl should implement PerformDataBinding instead of overriding DataBind(). 正如MSDN文档所述,DataBoundControl应该实现PerformDataBinding而不是覆盖DataBind()。 I've done so and made both-way-binding work. 我已经这样做了并且做了双向绑定工作。 Is this code necessary or should I be using something built-in? 这段代码是必要的还是我应该使用内置的东西?

Have you tried Databinder.Eval(Container.DataItem,...) syntax? 你试过Databinder.Eval(Container.DataItem,...)语法吗?

Also see this article on Bind() . 另见Bind()上的这篇文章。

PS. PS。 You need to Databind on every postback unless you are using Viewstate to preserve values. 除非使用Viewstate保存值,否则每次回发都需要Databind。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM