Monday, January 09, 2006 - Posts

Object Binding (.Net 1.0)

Lately - I have been up to some Presentation Layer programming. I have been making a mechanism to create bind-able things from our entity objects. This will enable us to bind to grids and other UI elements. We need to be able to create N number of views for our Entity objects - and not put code in odd places in our layered architecture. For example; if we have a Trade entity class (and/ or several derived Trade types), - we need to make many different views of this same entity - really entity collections - TradeCollection(s).

That being said - in the past I have done this a few ways -

#1) Implement the interfaces for binding (needs to be done over and over for all objects). Since our objects are sometimes quite deep aggregates of other objects, this amounts to lots of work in this case. It is not very DRY (Dont Repeat Yourself) - you need to do this again and again for each object that you want to bind.

#2) Attributes on the entity classes to define attributes at binding- the limitation here is that you get 1 view definition and it is embedded in the entity object. This also puts code for the Presentation Layer in the Domain Layer. This involves reflection and there are more 'reflectiony variations' on this approach - but they are basically the same.

#3) Manually create datasets, datatables, dataviews, etc. from your entities. This means manually plucking info out of your classes and sticking them into the datasets. This approach results in you loosing your entities and programming to datasets etc. It becomes a mess. You could use many dataset variants - strongly typed datasets, non-strongly typed datasets, yadda yadda….

#4) Our approach in the end was to use metadata to create a bindable something. This metadata is XML that looks something like:

The XML Meta-data for binding looks like this (this is a flat view):

    2 <View>

    3   <Band DisplayName="Trade" Type="Some.Name.Space.Trade, Some.Name.Space">

    4     <Column DisplayName="TradeDetails Id" Path="TradeDetails.Id" Type="System.String" />

    5     <Column DisplayName="TradeDetails Version" Path="TradeDetails.Version" Type="System.Int32" />

    6     <Column DisplayName="TradeDetails Type" Path="TradeDetails.Type" Type="System.String" />

    7     <Column DisplayName="TradeDetails Name" Path="TradeDetails.Name" Type="System.String" />

    8     <Column DisplayName="TradeDetails OriginatingSystem" Path="TradeDetails.OriginatingSystem" Type="System.String" />

    9     <Column DisplayName="TradeDetails IsSales" Path="TradeDetails.IsSales" Type="System.Boolean" />

   10     <Column DisplayName="TradeDetails State" Path="TradeDetails.State" Type="Some.Name.Space.State, Some.Name.Space" />

   11     <Column DisplayName= "Location" Path="TradeDetails.Location" Type="Some.Name.Space.Location, Some.Name.Space" />

   12     <Column DisplayName= "TradingMedium" Path="TradeDetails.TradingMedium" Type="Some.Name.Space.TradingMedium, Some.Name.Space" />

   13     <Column DisplayName="TradeDetails TraderId" Path="TradeDetails.TraderId" Type="System.String" />

   14     <Column DisplayName="TradeDetails CreditApproval" Path="TradeDetails.CreditApproval" Type="System.String" />

   15     <Column DisplayName="TradeDetails Book" Path="TradeDetails.Book" Type="System.Int32" />

   16     <Column DisplayName="TradeDetails DealConditions" Path="TradeDetails.DealConditions" Type="System.String" />

   17     <Column DisplayName= "CounterpartySubAccount" Path="TradeDetails.CounterpartySubAccount" Type="System.String" />

   18     <Column DisplayName="TradeDetails IndependentAmount" Path="TradeDetails.IndependentAmount" Type="System.Double" />

   19     <Column DisplayName="TradeDetails Gap" Path="TradeDetails.Gap" Type="System.String" />

   20     <Column DisplayName="TradeDetails ProductGroup" Path="TradeDetails.ProductGroup" Type="System.String" />

   21     <Column DisplayName="TradeDetails RealCounterParty Ref" Path="TradeDetails.RealCounterParty.Ref" Type="System.String" />

   22     <Column DisplayName="TradeDetails RealCounterParty Id" Path="TradeDetails.RealCounterParty.Id" Type="System.String" />

   23     <Column DisplayName= "TradingStrategy" Path="TradeDetails.TradingStrategy" Type="Some.Name.Space.TradingStrategy, Some.Name.Space" />

   24     <Column DisplayName= "FeeType" Path="TradeDetails.SalesCredit.FeeType" Type="Some.Name.Space.FeeType2, Some.Name.Space" />

   25     <Column DisplayName= "SalesPerson" Path="TradeDetails.SalesCredit.SalesPerson" Type="System.String" />

   26     <Column DisplayName= "Markup Currency" Path="TradeDetails.SalesCredit.Markup.Currency" Type="System.String" />

   27     <Column DisplayName= "SalesCredit Markup Amount" Path="TradeDetails.SalesCredit.Markup.Amount"  Type="System.Double" />

   28     <Column DisplayName="TradeDetails SalesCredit Currency" Path="TradeDetails.SalesCredit.Currency" Type="System.String" />

That Xml is then loaded as part of a IViewDescriptor in IObjectView :

    3     public interface IObjectView

    4     {

    5         ///

    6         /// Gets or sets the source object.

    7         ///

    8         /// The source object.

    9         object SourceObject { get; set; }

   10 

   11         ///

   12         /// Gets or sets the view descriptor.

   13         ///

   14         /// The view descriptor.

   15         IViewDescriptor ViewDescriptor { get; set; }

   16 

   17         ///

   18         /// Gets the bindable object.

   19         ///

   20         ///

   21         object GetBindableObject();

   22     }

Then at runtime you get the IViewDefintion, the source object – and call GetBindableObject() to get something to bind to.

Like so:

  257         private void BindDemoForm_Load(object sender, EventArgs e)

  258         {

  262             // Get a view from the view repository

  263             IObjectView objectView = appContext.ObjectViewRepository.GetByKey( "BindDemoForm.ModelView3" );

  264 

  265             //give the view some data to bind to (the trades in the TradeRepository in this case)

  266             objectView.SourceObject = appContext.TradeRepository.Values;

  267 

  268             // bind to the grid

  269             gridBinder.SetObjectToGrid( grid, objectView );

  270 

  271             // get underlying objects when grid is clicked

  272             gridBinder.RowDoubleClick += new StuckRowClickHandler(gridBinder_RowDoubleClick);

  273         }

After that – we hook up some events so that when we select or click on a row in a grid – we get the underlying Entity object in stead of a row. This means that all of your business operations can happen on your business object – not on a dataset, etc.

This looks something like:

  345         private void gridBinder_RowDoubleClick(object sender, StuckRowClickEventArgs e)

  346         {

  347             // get the underlying item in 1 line of code :)

  348             Trade trade  =  e.BoundObject as Trade;

  349             Debug.Assert( trade != null );   

  350             string formatString = "Your underlying object \nTrade Id = {0}, Trade Version = {1}, Type = [{2}]";

  351             string tradeDescription = string.Format(

  352                     formatString, 

  353                     trade.TradeDetails.Id, 

  354                     trade.TradeDetails.Version,

  355                     trade.GetType().ToString()

  356                 );           

  357             trade.TradeDetails.TraderId = new Random().Next().ToString();

  358             gridBinder.UpdateRowFromObject( e.GridRow, trade );

  359             MessageBox.Show( this, tradeDescription );

  360         }

There are several advantages to this approach: You can flatten your view of a hierarchical object, you can change the look in the hierarchy, or of course – you can use the existing hierarchy. You can define an unlimited number of views. You can do business operations on your objects directly – not on DataSets/ DataTables/ DataRows, etc.

Behind the scenes – in essence – we are inferring a schema from the root object using reflection. From there – we create a series of datatables in a dataset – complete with relations for the hierarchy etc.

It was quite easy to derive classes from DataTable, DataRow and DataColumn. Doing this allowed me to attach the ColumnDescriptor class and the bound object to the DataRow. The ColumnDescriptors keep the translated meta-data at run-time. The underlying object that the row (and child rows) are bound to are in the BoundObject of the DataRow. The DataTable keeps a reference to the translated meta-data – the BandDescriptor and creates new derived DataRows as required.

    9     public class StuckDataTable : DataTable

   10     {

   11         private BandDescriptor bandDescriptor;

   12 

   13         public BandDescriptor BandDescriptor

   14         {

   15             get { return bandDescriptor; }

   16             set { bandDescriptor = value; }

   17         }

   18 

   19         public StuckDataTable()

   20             : base()

   21         {

   22         }

   23 

   24         public StuckDataTable( SerializationInfo info, StreamingContext context )

   25             : base( info, context )

   26         {

   27         }

   28 

   29         public StuckDataTable( string tableName )

   30             : base( tableName )

   31         {

   32         }

   33 

   34         protected override DataRow NewRowFromBuilder( DataRowBuilder builder )

   35         {

   36             return new StuckDataRow( builder );

   37         }

   38     }

    9     public class StuckDataColumn : DataColumn

   10     {

   11         private ColumnDescriptor columnDescriptor;

   12 

   13         public StuckDataColumn()

   14             : base()

   15         {

   16         }

   17 

   18         public StuckDataColumn( string columnName, Type dataType, string expr, MappingType type )

   19             : base( columnName, dataType, expr, type)

   20         {

   21         }

   22 

   23         public StuckDataColumn( string columnName, Type dataType, string expr )

   24             : base( columnName, dataType, expr )

   25         {

   26         }

   27 

   28         public StuckDataColumn( string columnName, Type dataType )

   29             : base( columnName, dataType )

   30         {

   31         }

   32 

   33         public StuckDataColumn( string columnName )

   34             : base( columnName )

   35         {

   36         }

   37 

   38         public ColumnDescriptor ColumnDescriptor

   39         {

   40             get { return columnDescriptor; }

   41             set { columnDescriptor = value; }

   42         }

   43 

   44     }

    8     public class StuckDataRow : DataRow

    9     {

   10         object boundObject;

   11 

   12         public StuckDataRow( DataRowBuilder builder )

   13             : base( builder )

   14         {

   15         }

   16 

   17         public object BoundObject

   18         {

   19             get { return boundObject; }

   20             set { boundObject = value; }

   21         }

   22     }

To generate the Xml to do the binding required I whipped up a simple little UI. This UI allows you to load an assembly, list it’s types and generate the basic Xml Meta-data – either flattened or to match the full object hierarchy. From there – you can re-arrange the Xml, delete some etc. This gets us far along the path of binding very complex objects to grids and other things.

More to come on ObjectBinding – I have flapped enough for tonight. We can chat some more about how Enum binding was handled, the getting and setting of properties using the nifty Object.SubObject.SubObject.Property syntax..... more to come.

-Chris-