Create custom control with DataBinding

416 views Asked by At

I've created a SearchResults control that shows the results of a search. This control has a repeating element (displaying the results) but also contains other elements which I've removed for this question.

Currently in the following example DataItem cannot be resolved. I'm trying to work out how to make DataItem resolvable so I can use this control in the following way:

<Controls:SearchResults runat="server" id="Results">
    <RowHeaderTemplate>
        <tr>
            <td>User ID</td>
        </tr>
    </RowHeaderTemplate>
    <RowItemTemplate>
        <tr>
            <td>
                <%#((Models.User)Container.DataItem).ID %>
            </td>
        </tr>
    </RowItemTemplate>
</Controls:SearchResults>

It is used in this way:

var searchResults = GetSearchResults();
Results.DataSource = searchResults ;
Results.DataBind();

Search Results Control Code:

[System.ComponentModel.DefaultBindingProperty("DataSource")]
public partial class Search : UserControl
{
    [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(TemplateControl))]
    public ITemplate RowHeaderTemplate { get; set; }

    [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(TemplateControl))]
    public ITemplate RowItemTemplate { get; set; }

    public IEnumerable<object> DataSource { private get; set; }

    private bool DataBindingComplete { get; set; }

    public override void DataBind()
    {
        if (DataBindingComplete) return;
        foreach (var obj in DataSource)
        {
            var newControl = new PlaceHolder();
            RowItemTemplate.InstantiateIn(newControl);
            RowItemContainer.Controls.Add(newControl);
        }
        DataBindingComplete = true;
        base.DataBind();
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Visible) return;

        OnInit(e);
        if (RowHeaderTemplate != null)
        {
            RowHeaderTemplate.InstantiateIn(RowHead);
        }
    }
}

With the markup:

<asp:PlaceHolder runat="server" ID="Wrapper">

    <h2>Results</h2>    
    <table border="1">
        <asp:PlaceHolder runat="server" ID="RowHead"/>
        <asp:Placeholder runat="server" Visible='<%#TotalResults == 0 %>'>
            <tr>
                <td colspan="100">
                    No results returned!
                </td>
            </tr>
        </asp:Placeholder>
        <asp:Placeholder runat="server" ID="RowItemContainer" Visible='<%#TotalResults > 0 %>'>

        </asp:PlaceHolder>        
    </table>

</asp:PlaceHolder>

Please note this is a trimmed down example, simply using a Repeater control in place of it wont suffice.

1

There are 1 answers

1
Michael Liu On BEST ANSWER

The problem is that the TemplateContainer attribute for the Search control's RowItemTemplate property references TemplateControl, which doesn't have a DataItem property. Instead of using TemplateControl, create a SearchItem control that has a DataItem property:

public class SearchItem : Control, INamingContainer
{
    public object DataItem { get; set; }
}

In the Search class, change the TemplateContainer attribute for the RowItemTemplate property so that it references SearchItem instead of TemplateControl:

[PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(SearchItem))]
public ITemplate RowItemTemplate { get; set; }

Also, instantiate SearchItem instead of PlaceHolder in DataBind:

var newControl = new SearchItem { DataItem = obj };

These are the minimum changes needed to get your example to work. However, your custom control won't automatically persist items across postbacks. To implement that functionality, I recommend that you study the source code for the Repeater class.