2010-05-18 20 views
6

Originalmente, esta pregunta consistía en conseguir que el enlace bidireccional funcionara, pero debido a la falta de respuestas específicas y a un progreso en el camino, lo he estado actualizando - Puedes revisar el historial de edición, pero pensé que esto es mejor para mayor claridad.Enlace de datos bidireccional de un control asp.net con plantillas personalizadas

El listado de código a continuación permite que un solo objeto sea unidireccional de dos vías para un control de plantilla. Me gustaría extender este ejemplo de la manera más simple posible para permitir el anidamiento de controles de plantillas habilitados para enlace de datos bidireccionales de manera similar para propiedades de tipo complejo del objeto raíz. Por ejemplo, SampleFormData tiene una propiedad List<string> Items. Me gustaría poder enlazar a esta lista dentro de la plantilla root-most (de esta lista de códigos) y mostrar los datos de cadena en una lista editable de cuadros de texto, quizás, con comandos para insertar, eliminar, volver a ingresar -cambios (de regreso a la propiedad Lista del objeto enlazado). Además, si se tratara de una lista de un tipo complejo (SampleFormChildData, en lugar de cadena), se podría utilizar un nuevo SampleSpecificEntryForm incrustado dentro de la lista, ligado a cada uno de los elementos de la lista, como un repetidor. Y así sucesivamente hasta las propiedades de hojas simples, si el autor así lo elige. Los ui-fields no necesitan ser generados automáticamente, solo están disponibles para el enlace.

Nota: El caso del List<string> es especial porque incluso los enlaces incorporados no pueden manejar cadena como el DataItem directamente, ya que no es un requisito, pero ciertamente valioso, enlazar a cadenas directamente como elementos de nuestra lista.

Esto es diferente de FormView porque no está diseñado para esperar enlazar a una de una lista de elementos, solo a un solo elemento como persistió en viewstate o donde sea. A diferencia de FormView, esto solo tiene una única plantilla predeterminada similar a EditTemplate de FormView. Del mismo modo, el enlace a una propiedad similar a una colección también solo tendría una vista: editar. No hay selección de la fila y luego edición. Todo es editable todo el tiempo. El objetivo es hacer que las formas enlazadas bidireccionales sean más fáciles de construir.

Me parece que debería haber dos tipos de encuadernación. SingleEntityBinding y CollectionBinding. SingleEntityBinding toma una sola instancia de objeto como fuente de datos (como el prototipo de SampleSpecificEntryForm), mientras que CollectionBinding podría estar vinculado a su padre SingleEntityBinding con atributos de DataSourceID="EntryForm1" DataMember="Items" como en el ejemplo de código para DataList1 a continuación. La anidación de cualquier tipo debe ser compatible con cualquier tipo. La manipulación de la lista como insertar/cambiar/eliminar las operaciones de tipo contra los datos del objeto de respaldo son responsabilidad del autor del formulario; sin embargo, tales mecanismos serían relativamente simples de implementar.

Aquí hay un código, espero que ayude a alguien. 200 puntos están ahí fuera para las mejores sugerencias para alcanzar esta meta diseñados, ...

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; 
     } 
    } 
} 

Con una página ASP.NET lo siguiente:

<%@ 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:

using System; 

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

he implementado IDataSource así, en un intento de ser capaz de anidar un componente de lista como tal (en el):

<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> 

Cualquier idea sobre cómo hacer que esto funcione en forma de cascada sería increíble (en la propiedad Lista de elementos, por ejemplo). Uno de los desafíos aquí es que Bind() no puede hacer referencia al objeto de datos en sí (una cadena en este caso) sino a una propiedad de ese elemento, lo que hace que el enlace a una lista resulte incómodo.

¡Gracias por cualquier ayuda!


Descubrimientos en el camino

Implementado IDataItemContainer. Tenía muchas esperanzas de que esto lo arreglara, pero no. Sin cambios notables. Vaya, lo implementé en la clase incorrecta. Ahora es vinculante, pero los valores no se recuperan al objeto vinculado en la devolución de datos. Hmmm ...

Como this article sugiere, Page.GetDataItem() es la fuente de la excepción. Esta excepción se produce si el _dataBindingContext de la página es nulo o está vacío. El artículo explica esto, pero no dice cómo asegurarse de que el _dataBindingContext de la página esté lleno. Continuaré buscando.

Como dice la documentación de MSDN, DataBoundControl debe implementar PerformDataBinding en lugar de anular DataBind(). Lo he hecho y he hecho un trabajo de enlace a ambos lados. ¿Es necesario este código o debería usar algo incorporado?

Respuesta

0

¿Has probado la sintaxis Databinder.Eval(Container.DataItem,...)?

Ver también este artículo en Bind().

PS. Necesita Databind en cada devolución a menos que esté usando Viewstate para conservar los valores.

+0

Sí, gracias por su respuesta. He probado todas las opciones. Actualicé la publicación con "Descubrimientos en el camino" con la última información. ¿Has trabajado en este problema general antes? –

+1

Sí, he escrito controles personalizados de datos, aunque he estado usando MVC estrictamente por un tiempo. PD. Necesita Databind en Postback a menos que esté usando Viewstate para conservar los valores. – kervin

+0

Bueno, kervin. ¡Tú ganas! –

Cuestiones relacionadas