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-