In CSLA there exists a mixed metaphor a constant struggle between the principals of data centric design and pure object oriented design. This is most apparent when contrasting the ReadOnlyListBase and the BusinessBase class. One is essentially a simple data transfer object, and the other a fully functional Business Object. Often times during the course of a development effort these two paradigms collide. Even with very simple object models this can be a problem. Take for example a simple scenario with a Customer object that contains a CustomerType child object both of the BusinessBase variety.

Customer CustomerType Class Diagram
The domain rules for this model require simply that when creating a new Customer object or editing an existing Customer object a cutomer name and its associated CustomerType must be provided. To support the creation of a new customer I have created a simple screen I call CustomerDetail. This screen allows the user to provide a name for the Customer as well as an associated CustomerType.

Customer Detail
The screen itself appears very simple. Though simple, the ComboBox control holding the possible CustomerTypes gives us our first look at the mixed metaphor. Here is why. When filling the ComboBox control for the CustomerType selection I chose to use a object derived from ReadOnlyListBase named CustomerTypeInfoCollection. For performance reasons this is the approach I will usually use when filling ComboBox controls. The alternative (objects derived from BusinessBase) generally will have much larger object graphs. Aside from the memory footprint they consume often times they make several round trips the database to populate a single instance. Because of these concerns it makes the most sense to me to create ReadOnlyListBase objects to manage these kinds of controls. Looking at the code below you can see I am setting the ItemsSource of the ComboBox to the CustomerTypeList property of the screens DataContext (in this case the DataContext is the windows code behind).
<Label Grid.Row="1" Margin="0,2,0,0" Grid.Column="0">Customer Type:</Label> <ComboBox IsReadOnly="True" ItemsSource="{Binding Path=CustomerTypeList}" SelectedValue="{Binding Path=Customer.CustomerType, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource CustomerTypeToIdConverter}}" SelectedValuePath="Id" DisplayMemberPath="Type" Grid.Row="1" Margin="0,2,0,0" Grid.Column="1" Width="150" Name="TypeControl" Height="24"> </ComboBox>
If you look at the xaml you will notice something is just a bit off. The ItemsSource property of the ComboBox control is bound to a property called CustomerTypeList. As stated earlier CustomerTypeList is a ReadOnlyListBase object. The SelectedValue is bound to a property called CustomerType. CustomerType is a BusinessBase object. Making the xaml more confusing the SelectedValuePath is set to Id (id is an integer from the CustomerTypeInfoItem object). By reading the xaml and considering what the databinding engine is likley to do the inferred pseudo code would look something like this.
int customerTypeId = CustomerTypeList[SelectedItem].Id; Customer.CustomerType = customerTypeId;
By looking at the xaml above it appears that I am telling the databinding system to assign an int to a CustomerType object when the ComboBox selection is changed. This obviously will not work for many reasons. Thankfully to resolve this issue WPF data binding gives us the ability to use ValueConverters and we can use them to manage this conflict. Correcting this issue is requires two things to happen. Firstly when selecting an item in the ComboBox we need to take the Id from the selected ReadOnlyBase CustomerTypeListItem and somehow get a BusinessBase CustomerType with that id. Secondly when loading the screen for editing a Customer we have to somehow give databinding the Id of the CustomerType BusinessBase object so the selection in the ComboBox control is set to the correct item. Converters give the solution for both of these hurdles. Lets take a look.
public class CustomerTypeToIdValueConverter : IValueConverter { #region IValueConverter Members /// <summary> /// Converts a value. /// </summary> /// <param name="value">The value produced by the binding source.</param> /// <param name="targetType">The type of the binding target property.</param> /// <param name="parameter">The converter parameter to use.</param> /// <param name="culture">The culture to use in the converter.</param> /// <returns> /// A converted value. If the method returns null, the valid null value is used. /// </returns> public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { CustomerType customerType = value as CustomerType ; if(customerType != null) { return customerType.Id; } return 0; } /// <summary> /// Converts a value. /// </summary> /// <param name="value">The value that is produced by the binding target.</param> /// <param name="targetType">The type to convert to.</param> /// <param name="parameter">The converter parameter to use.</param> /// <param name="culture">The culture to use in the converter.</param> /// <returns> /// A converted value. If the method returns null, the valid null value is used. /// </returns> public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if((int)value == 0) { return null; } return CustomerType.GetAsChildById((int) value); } #endregion }
Not only does this ValueConvert take care of the briding the gap between a ReadOnly object and a BusinssObject it also allows us to fully leverage the IDataErrorInfo implementation in CSLA. Because our SelectedValue can remain bound to a BusinessObject we can simply tell databinding to ValidateOnDataErrors and we get full support for business rules and validation. And I think that is pretty darn cool.
Thanks to Jim Brown for helping me get my thoughts organized for this post.
Enjoy!