Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic Binding to Custom Business Object at Runtime

I am working on a Web Forms project that loads the results of Sql queries into DataTables.

These DataTables are passed up to the front-end where we bind them to a Repeater web control.

This works great. However, now we'd like to bind to our own custom classes instead of a DataTable. Unfortunately, what I thought was the obvious answer didn't work (implementing IDictionary<string, object> on our class).

What do we need to have Eval bind to Datum without creating a concrete property for every binding? Clearly DataRow doesn't have to concretely implement every property we bind. So, somehow it seems that Eval is able to look up the property values by name on DataRow.

Here is the custom class

public class Datum: IDictionary<string, object>
{
    private Dictionary<string, object> _entries;

    public Datum()
    {
        _entries = new Dictionary<string, object>();
    }

    public object this[string s]
    {
        get
        {
            return this._entries[s];
        }
    }

    ...
}

Here is where the DataSource is set in the aspx.cs file

rptTags.DataSource = new[] { new Datum { {"Count", 1} }, new Datum { {"Count", 2 } };

And here is the binding in the aspx file

<asp:Repeater ID="rptTags" runat="server">
    <ItemTemplate>
        <%# (int)Eval("Count") > 
    </ItemTemplate>
</asp:Repeater>

Using the above example we get an error saying that the property in question doesn't exist, which is true, but it doesn't exist on DataRow either. How can I make it bind like System.Data.DataRow?

like image 621
Mark Rucker Avatar asked Feb 14 '12 22:02

Mark Rucker


1 Answers

I came in this morning with fresh eyes and spent several hours going through the .Net Framework with ILSpy. I was finally able to figure this puzzle out and implement a working solution. I'll list things I learned that are pertinent to the solution and then detail my implementation.

  • When you bind a DataTable to a Repeater each RepeaterItem doesn't bind to a DataRow, like I expected, but to a DataRowView. This really isn't important except that DataRowView implements the ICustomTypeDescriptor interface which is what we need to implement on our class.
  • Even though the MSDN documentation says the Eval method uses reflection to perform late-binding and your expression must evaluate to a public property this is simply not true. The Eval statement uses the GetProperties() method on ICustomTypeDescriptor to evaluate your expression.

With that in mind here are the steps I had to take to create my own custom type that I could dynamically bind to just like DataTable.

  1. Create your own custom class that inherits from PropertyDescriptor.
  2. Implement all of PropertyDescriptor's abstract members. For dynamic binding the most important one is GetValue(). This is where you define how to get the values from the class you are binding to.
  3. On the class you will be binding to, inherit from CustomTypeDescriptor. This is a generic class that implements ICustomTypeDescriptor.
  4. Override the GetProperties() method of CustomTypeDescriptor and return a list of your PropertyDescriptors (created in steps 1-2) for each value you would like to be able to bind to.
  5. Make sure your PropertyDescriptions have their Name property set. This is the property .Net compares your Eval expression to in order to determine which PropertyDescription to use when binding.
  6. Set your CustomTypeDescriptor object (created in steps 3-5) as the DataSource of a WebControl and use Eval() to bind to it dynamically
like image 130
Mark Rucker Avatar answered Oct 03 '22 14:10

Mark Rucker