Chapter 10. Data Binding

Data binding is a powerful concept that is supported by WPF, Windows Forms, and Web Forms. It is a Microsoft technology that provides an abstract and formal communication layer between the user interface and the business objects, or between the Interface Control layer and Business layer, to use the terms from Chapter 1.

It is important to maintain clear separation of concerns between the Interface Control and Business layers to avoid having business logic creep into the UI, or UI logic creep into the business objects. If either of those things occurs, the maintainability of your application will go down, and the cost of development and maintenance will go up. Having a prebuilt technology like data binding, which provides a powerful abstraction boundary between the two layers, can help a great deal.

At a very basic level, all .NET objects support data binding. However, to take full advantage of everything data binding has to offer, objects need to implement a set of interfaces and behaviors, some of which can be quite complex. I believe the end result is worth it, however, because this complex behavior can be implemented in a framework like CSLA .NET, so it doesn't impact your business objects directly. And with full support for data binding, you'll need to write a lot less code in the UI, and that increases the maintainability of any application.

I've been referring to data binding as though it were one technology. In reality, it is one concept, but the technology is somewhat different in WPF, Windows Forms, and Web Forms. Each type of UI has its own variation of data binding, though in each case the goal and end result are the same: to provide a clean, abstract way to connect the UI to the underlying business objects.

Of the three, Windows Forms data binding is the most mature and powerful. It turns out that if your objects support Windows Forms data binding, they automatically do almost everything required by WPF and Web Forms.

In this chapter, I'll discuss how the CSLA .NET framework supports data binding, first for Windows Forms and then for WPF. I'll wrap up with Web Forms, because it's the least demanding.

Windows Forms

Windows Forms data binding can interact with nearly any .NET object. However, there is a set of interfaces that an object can implement to fully support the data binding features. In fact, there's one set of interfaces for a simple object, and another set for a collection or list.

I'll discuss supporting data binding for a single object first, and then I'll discuss data binding support for collections and lists. I'll wrap up this section by discussing some custom controls provided by CSLA .NET to simplify the use of data binding in Windows Forms.

Object Data Binding

The .NET Framework defines three interfaces in support of data binding that an object should implement to fully support it. Table 10-1 lists the interfaces for a simple object.

Table 10.1. Data Binding Interfaces for a Simple Object

Interface

Description

INotifyPropertyChanged

Defines a PropertyChanged event that should be raised when a property of the object has changed

IEditableObject

Defines three methods that are used to undo or accept changes to the object

IDataErrorInfo

Defines properties that data binding can use to ask the object if it is valid and if each of its properties is valid

There's also an INotifyPropertyChanging interface, which is part of LINQ to SQL. It defines a PropertyChanging event that should be raised directly before a property is changed. Although data binding doesn't use this interface, CSLA .NET implements it, because the concept of an event that is raised before a property is changed can be useful in general.

Not all of these interfaces are implemented directly by BusinessBase itself. Figure 10-1 shows the inheritance hierarchy for BusinessBase.

Inheritance hierarchy for BusinessBase

Figure 10.1. Inheritance hierarchy for BusinessBase

The INotifyPropertyChanged and INotifyPropertyChanging interfaces are implemented by BindableBase. The IEditableObject and IDataErrorInfo interfaces are implemented by BusinessBase.

I'll discuss each of the interfaces in turn.

The INotifyPropertyChanged Interface

The most basic interface, and the one that should be considered the least that any object author should implement, is INotifyPropertyChanged. This interface defines a PropertyChanged event that the business object should raise any time one of its properties is changed.

Warning

The PropertyChanged event is handled differently in Windows Forms and WPF, and your object needs to implement the correct behaviors depending on the UI technology. I'll discuss the details later in this chapter.

This interface is implemented in BindableBase.

[Serializable()]
  public abstract class BindableBase : MobileObject,
  System.ComponentModel.INotifyPropertyChanged,
    System.ComponentModel.INotifyPropertyChanging

This requires that the class declare the PropertyChanged event. This event is implemented using the longer custom event declaration syntax.

[NonSerialized]
private PropertyChangedEventHandler _nonSerializableChangedHandlers;
private PropertyChangedEventHandler _serializableChangedHandlers;

public event PropertyChangedEventHandler PropertyChanged
{
  add
  {
    if (value.Method.IsPublic &&
       (value.Method.DeclaringType.IsSerializable ||
        value.Method.IsStatic))
      _serializableChangedHandlers = (PropertyChangedEventHandler)
        System.Delegate.Combine(_serializableChangedHandlers, value);
    else
      _nonSerializableChangedHandlers = (PropertyChangedEventHandler)
        System.Delegate.Combine(_nonSerializableChangedHandlers, value);
  }
  remove
  {
    if (value.Method.IsPublic &&
       (value.Method.DeclaringType.IsSerializable ||
        value.Method.IsStatic))
      _serializableChangedHandlers = (PropertyChangedEventHandler)
        System.Delegate.Remove(_serializableChangedHandlers, value);
    else
      _nonSerializableChangedHandlers = (PropertyChangedEventHandler)
        System.Delegate.Remove(_nonSerializableChangedHandlers, value);
  }
  }

Before declaring the event itself, the code declares two delegate fields. These fields will hold delegate references to all event handlers registered to receive the PropertyChanged event.

[NonSerialized()]
    private PropertyChangedEventHandler _nonSerializableHandlers;
    private PropertyChangedEventHandler _serializableHandlers;

Notice that one is declared with the NonSerialized attribute, while the other is not. The BinaryFormatter ignores the first one and all objects referenced by that delegate field. Objects referenced by the second field are serialized as normal.

The event declaration uses a block structure, including add and remove sections. Notice how the code in both sections checks to see if the event handler is contained within a serializable object.

if (value.Method.IsPublic && (value.Method.DeclaringType.IsSerializable ||
            value.Method.IsStatic))

If the event handler is contained in a serializable object, it will be added or removed from the serializable delegate; otherwise, it will be added or removed from the nonserialized delegate.

The thing about events and inheritance is that an event can only be raised by code in the class in which it is declared. This is because the event member can only be accessed directly from the class in which it is defined. It can't be raised by code in classes that inherit from this class. This means that business objects can't raise the PropertyChanged event directly, even though that is the goal. To solve this problem, the code follows a standard .NET design pattern by creating a protected method that in turn raises the event.

[EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void OnPropertyChanged(string propertyName)
    {
      if (_nonSerializableHandlers != null)
        _nonSerializableHandlers.Invoke(this,
          new PropertyChangedEventArgs(propertyName));
      if (_serializableHandlers != null)
        _serializableHandlers.Invoke(this,
          new PropertyChangedEventArgs(propertyName));
    }

Any classes that inherit from the base class can call this method when they want to raise the event.

This method is marked with the EditorBrowsable attribute, indicating that this is an advanced method. In C#, this means that the method won't appear in IntelliSense unless the IDE is set to show advanced members. In VB, this means that the method appears in the All tab in IntelliSense, but won't appear in the Common tab.

The OnUnknownPropertyChanged() method covers a special case, different from the OnPropertyChanged() method. Where OnPropertyChanged() raises the PropertyChanged event for a single property, OnUnknownPropertyChanged() raises the event for a property with no name.

[EditorBrowsable(EditorBrowsableState.Advanced)]
protected virtual void OnUnknownPropertyChanged()
{
  OnPropertyChanged(string.Empty);
  }

There are a number of cases in which the object's state will change in such a way that it isn't possible to know which properties actually changed. In that case, this blanket notification approach ensures that data binding is aware that something changed, so the UI updates as needed. Passing a property name of string.Empty or null is a "magic value" that tells data binding to refresh all bound property values.

The result is a base class that allows business objects to raise the PropertyChanged event, thereby supporting data binding and serialization.

The INotifyPropertyChanging Interface

The INotifyPropertyChanging interface is almost identical to INotifyPropertyChanged. The only real difference is the timing of when the PropertyChanging event is raised as opposed to the PropertyChanged event.

When a property value is about to be changed, the PropertyChanging event is raised first. Then the value is actually changed, and then the PropertyChanged event is raised. The methods in BusinessBase, such as the LoadPropertyValue() method, handle this. That method contains this code:

OnPropertyChanging(propertyInfo.Name);
            FieldManager.SetFieldData<P>(propertyInfo, newValue);
            PropertyHasChanged(propertyInfo.Name);

You can see the steps here clearly, including how both events are raised

I won't walk through the code for INotifyPropertyChanging here, because it is so similar to INotifyPropertyChanged. You can look at the code in BindableBase on your own.

The IEditableObject Interface

The IEditableObject interface allows data binding to tell your object to undo recent changes, or accept recent changes to the object's properties. It is used in many scenarios by Windows Forms data binding, including when an object is bound to a form using a BindingSource control, and when a collection of objects is bound to any grid control that supports in-place editing of the data.

The IEditableObject interface appears simple on the surface, but it's actually the most complex interface I'll discuss in this chapter. This is because there are a number of subtle idiosyncrasies in how this interface is called by data binding, and any object implementing the interface must deal with all the resulting edge cases.

The interface defines the three methods listed in Table 10-2.

Table 10.2. Methods Defined by IEditableObject

Method

Description

BeginEdit

Called by data binding to tell the object to take a snapshot of its property values

CancelEdit

Called by data binding to tell the object to undo any changes, restoring the property values to the snapshot taken when BeginEdit() was called

EndEdit

Called by data binding to tell the object to accept any changes, effectively discarding the snapshot taken when BeginEdit() was called

This doesn't sound that hard, until you read the fine print in the documentation and start using the interface and then discover the even finer print that isn't actually in the documentation at all.

Note

Many of the lessons I've learned and the issues I've solved in the data binding implementation in CSLA .NET are due to a lot of hard work and research by the CSLA .NET community at http://forums.lhotka.net. Without the strong support and involvement by numerous people, many of the hard challenges would likely still be unsolved.

For example, it turns out that BeginEdit() may be called any number of times by data binding, but your implementation should only honor the first call. That first call to BeginEdit() must be balanced out by a subsequent call to either CancelEdit() or EndEdit(), at which point the next BeginEdit() call should be honored.

It is also the case that EndEdit() can be called multiple times, though only the first call should be honored. Worse yet, there are scenarios where neither CancelEdit() nor EndEdit() are called at all, leaving the object under the impression that it is still in the process of being edited.

This interface is implemented in BusinessBase. The implementation of this interface is related to the implementation of n-level undo, which I'll discuss in Chapter 13. The n-level undo feature already has the capability of taking a snapshot of the object's property values (actually its field values) and restoring them later if an undo is requested. The only difference is that IEditableObject is a single level of undo, while the n-level undo feature obviously supports multiple levels of undo.

The DisableIEditableObject Property

Some people have found the IEditableObject interface to be extremely challenging, especially if they are trying to create a UI that doesn't want to use all the data binding features. Windows Forms data binding uses this interface in numerous scenarios, and it can sometimes make life harder rather than easier.

To minimize this pain, it is possible to disable the interface by setting a DisableIEditableObject property on a specific business object.

Warning

I don't recommend disabling the interface. If you disable it, data binding will not be able to interact with your object as it expects, and you may have to write extra UI code to compensate.

The BindingEdit Property

In addition to the DisableIEditableObject property, the BindingEdit property is used as a flag to indicate whether the n-level undo behavior was invoked through the BeginEdit() method from the IEditableObject interface. This flag allows the BeginEdit() method to only honor the first call.

The BindingEdit property is implemented in the UndoableBase class, because it is also used by some of the n-level undo behaviors I'll discuss in Chapter 13.

The BeginEdit Method

The BeginEdit() method uses the DisableIEditableObject and BindingEdit properties to determine whether to do any work. Here's the BeginEdit() code:

void System.ComponentModel.IEditableObject.BeginEdit()
  {
    if (!_disableIEditableObject && !BindingEdit)
    {
      BindingEdit = true;
      BeginEdit();
    }
    }

This method only performs real work if the interface is enabled (which it is by default) and if this is the first call to the method when BindingEdit is false. In that case, it sets BindingEdit to true to indicate that the object is reacting to the IEditableObject interface, and it calls the n-level undo BeginEdit() method that I'll discuss in Chapter 13. For now, it is enough to know that this call to BeginEdit() takes a snapshot of the object's current state so it can be restored later if necessary.

The CancelEdit Method

The CancelEdit() implementation works in a similar manner, first checking the properties to determine whether it should do any work.

void System.ComponentModel.IEditableObject.CancelEdit()
  {
    if (!_disableIEditableObject && BindingEdit)
    {
      CancelEdit();
      if (IsNew && _neverCommitted && EditLevel <= EditLevelAdded)
      {
        if (Parent != null)
          Parent.RemoveChild(this);
      }
    }
  }

If this method does do work, it calls another CancelEdit() method, which is part of the n-level undo functionality I'll discuss in Chapter 13. That method restores the object's state to the most recent snapshot—the snapshot taken when BeginEdit() was called.

The next block of code is a bit complex and is necessary to support parent-child relationships. If this object is a child of some other object, then it is possible that it was just added as a child when BeginEdit() was called. If that's the case, then when CancelEdit() is called, this object should be removed as a child.

Consider this from the user's perspective. The user adds an item to a collection and starts editing that item. The user then realizes he doesn't really want to add it, so he presses Esc or performs some other UI gesture to cancel what he's doing. In this case, this new item's state isn't rolled back, because the user expects the new item to just go away entirely.

The call to Parent.RemoveChild() tells the parent object to remove this child entirely, which meets the user's expectation.

The best way to see this is to bind a collection to a DataGrid control, move to the last row so a new item is added, and then press Esc. Notice how the new item just disappears? This code enables that behavior.

Note

In reality, this shouldn't be a common occurrence. Windows Forms 2.0 and higher uses a new interface, ICancelAddNew, that is implemented by BindingList<T>. This interface notifies the collection that the child should be removed, rather than notifying the child object itself. The code in the RemoveItem() method takes care of the ICancelAddNew case automatically, so this code is really here to support backward compatibility for anyone explicitly calling the IEditableObject interface on child objects.

The EndEdit Method

The final method is EndEdit(), which data binding calls when the changes to the object should be accepted. In other words, the snapshot taken by BeginEdit() can be discarded at this point, because there's no going back.

This is the simplest of the three methods:

void System.ComponentModel.IEditableObject.EndEdit()
  {
    if (!_disableIEditableObject && BindingEdit)
    {
      ApplyEdit();
    }
  }

It checks the two property values to see if it should do any work. If so, it calls the ApplyEdit() method from the n-level undo subsystem, telling n-level undo to discard the most recent snapshot of the object's state—the one taken when BeginEdit() was called.

At this point, you should understand how IEditableObject is implemented. This interface is used extensively by data binding, and it enables behaviors widely expected by end users of a Windows Forms application.

The IDataErrorInfo Interface

Windows Forms data binding uses the IDataErrorInfo interface to interrogate a data source for validation errors. This interface allows a data source, such as a business object, to provide human-readable descriptions of errors at the object and property levels. This information is used by grid controls and the ErrorProvider control to display error icons and tooltip descriptions.

The validation subsystem discussed in Chapter 11 provides a list of broken rules for each property on the object, making it relatively easy to implement IDataErrorInfo.

string IDataErrorInfo.Error
  {
    get
    {
      if (!IsSelfValid)
        return ValidationRules.GetBrokenRules().ToString(
          Csla.Validation.RuleSeverity.Error);
      else
        return String.Empty;
    }
  }

  string IDataErrorInfo.this[string columnName]
  {
    get
    {
      string result = string.Empty;
      if (!IsSelfValid)
      {
        Validation.BrokenRule rule =
          ValidationRules.GetBrokenRules().GetFirstBrokenRule(columnName);
        if (rule != null)
          result = rule.Description;
      }
      return result;
    }
  }

The Error property returns a text value describing the validation errors for the object as a whole.

The indexer returns a text value describing any validation error for a specific property. In this implementation, only the first validation error in the list is returned. In either case, if there are no errors, an empty string value is returned—telling data binding that there are no broken rules to report.

It is important to realize that the rules are not checked when this interface is invoked. The rules are checked when a property changes or when the business developer explicitly runs the rules. When IDataErrorInfo is invoked, the rules have already been checked, so this implementation simply returns the precalculated results.

This is important because the IDataErrorInfo interface is invoked frequently. Each time data binding refreshes the UI or receives a PropertyChanged event from the object, it loops through all the bound properties to see if they're valid. You can expect IDataErrorInfo to be invoked dozens or hundreds of times during the lifetime of a single user interaction.

At this point, you should understand the four interfaces implemented by editable objects to support data binding. Some of these interfaces—INotifyPropertyChanged and IEditableObject, in particular—interact with any parent collection that might contain the object. I'll discuss data binding and collections next.

Collection Data Binding

Collections need to implement a set of interfaces to fully participate in data binding, and these interfaces are quite complex. Fortunately, Microsoft provides the BindingList<T> class in the System.ComponentModel namespace, which already implements all the interfaces. To help you fully understand the benefit provided by this class, Table 10-3 lists the interfaces you would otherwise have to implement by hand.

Table 10.3. Data Binding Interfaces for Collections and Lists

Interface

Description

IBindingList

Defines a ListChanged event that should be raised when the list changes, along with methods to support in-place editing in a grid, sorting, and other features

ICancelAddNew

Defines methods used for in-place editing in a grid

IRaiseItemChangedEvents

Indicates that the list will raise a ListChanged event when its child items raise PropertyChanged events

There's also an IBindingListView interface, which is optional. This interface extends IBindingList with extra features such as multicolumn sorting and filtering. By using LINQ, it is typically not necessary to rely on lists or collections to sort or filter themselves, as it is simpler to use a LINQ query to manipulate the data. Due to this, the IBindingListView interface is not as important as it was in older versions of the .NET Framework.

Note

IBindingListView is not implemented in CSLA .NET, but you can find an implementation in the CSLAcontrib library at www.codeplex.com/CSLAcontrib.

Because BusinessListBase and the other CSLA .NET collection base classes ultimately inherit from BindingList<T>, they automatically provide full support for data binding.

It is important to realize, however, that BindingList<T> doesn't do everything necessary for a collection to work within the CSLA .NET framework. For example, BusinessListBase implements extra features to support n-level undo and abstract persistence, as discussed in Chapters 13 and 15.

Also, as I discussed in Chapter 9, BusinessListBase includes code to raise a ListChanged event when one of its child objects raises a PropertyChanged event. This is used after the deserialization of the collection, and is necessary because BindingList<T> doesn't handle the deserialization scenario.

By implementing the interfaces listed in Table 10-1 and inheriting from BindingList<T>, the CSLA .NET base classes provide full support for all the features of Windows Forms data binding. This includes the drag-and-drop concepts in the Visual Studio designer, along with the runtime behaviors for interacting with a BindingSource control and supporting in-place editing in grid controls.

Controls and Helper Objects

Though the interfaces and base classes I've discussed so far provide support for data binding, it turns out that the UI often still contains quite a bit of code to make data binding really work. Some of this code works around quirks in the behavior of data binding itself or in how it interacts with editable business objects. And some extends the UI to deal with the concept of per-property authorization—a feature I'll discuss in Chapter 12.

To minimize the code required in the UI, CSLA .NET includes three custom controls for Windows Forms that address the most common issues. These controls are a type of control called an extender control.

Extender controls are added to a form, and they in turn add properties and behaviors to other controls on the form, thus extending those other controls. A good example of this is the ErrorProvider control, which extends other controls by adding the ability to display an error icon with a tooltip describing the error.

The ReadWriteAuthorization Control

In Chapter 12, I will discuss the authorization feature supported by CSLA .NET business objects that makes them aware of whether each property can be read or changed. A key part of that implementation is the IAuthorizeReadWrite interface defined in Csla.Core. This interface defines CanReadProperty() and CanWriteProperty() to make it possible to determine whether the current user is allowed to get or set each property on the object.

One primary user of this functionality is the UI, which can decide to alter its appearance to give users clues as to whether they're able to view or alter each piece of data. While you could do this by hand for each control on every form, the ReadWriteAuthorization control helps automate the process of building a UI that enables or disables controls based on whether properties can be read or changed.

If a control is bound to a property, and the user does not have read access to that property due to authorization rules, the ReadWriteAuthorization control will disable that control. It also adds a handler for the control's Format event to intercept the value coming from the data source, substituting an empty value instead. The result is that data binding is prevented from displaying the data to the user.

Similarly, if the user doesn't have write access to a property, ReadWriteAuthorization will attempt to mark any controls bound to that property as being read-only (or failing that, disabled), ensuring that the user can't attempt to alter the property value.

Like all Windows Forms components, extender controls inherit from System.ComponentModel.Component. Additionally, to act as an extender control, the ReadWriteAuthorization control must implement the IExtenderProvider interface.

[DesignerCategory("")]
[ProvideProperty("ApplyAuthorization", typeof(Control))]
public class ReadWriteAuthorization : Component, IExtenderProvider
{
public ReadWriteAuthorization(IContainer container)
  { container.Add(this); }
  }

The ProvideProperty attribute is important. It specifies that ReadWriteAuthorization extends components of type Control by adding an ApplyAuthorization property to them. In other words, when a ReadWriteAuthorization control is on a form, all other controls on the form get a dynamically added ApplyAuthorization property. Figure 10-2 shows a text box control's Properties window with the dynamically added ApplyAuthorization property.

The UI developer can set this property to true or false to indicate whether the ReadWriteAuthorization control should apply authorization rules to that particular control. You'll see how this works as the control is implemented.

The DesignerCategory attribute is just used to help Visual Studio decide what kind of visual designer to use when editing the control. The value used here specifies that the default designer should be used.

ApplyAuthorization property added to NameTextBox

Figure 10.2. ApplyAuthorization property added to NameTextBox

The class also implements a constructor that accepts an IContainer parameter. This constructor is required for extender controls and is called by Windows Forms when the control is instantiated. Notice that the control adds itself to the container as required by the Windows Forms infrastructure.

The IExtenderProvider Interface

The IExtenderProvider interface defines just one method: CanExtend().Windows Forms calls this method to ask the extender control whether it wishes to extend any given control. Windows Forms calls CanExtend() automatically for every control on the form.

public bool CanExtend(object extendee)
  {
    if (IsPropertyImplemented(extendee, "ReadOnly")
      || IsPropertyImplemented(extendee, "Enabled"))
      return true;
    else
      return false;
    }

The ReadWriteAuthorization control can extend any control that implements either a ReadOnly or Enabled property. This covers most controls, making ReadWriteAuthorization broadly useful. If the potential target control implements either of these properties, a true result will be returned to indicate that the control will be extended.

The IsPropertyImplemented() method is a helper that uses reflection to check for the existence of the specified properties on the target control.

private static bool IsPropertyImplemented(
    object obj, string propertyName)
  {
    if (obj.GetType().GetProperty(propertyName,
      BindingFlags.FlattenHierarchy |
      BindingFlags.Instance |
      BindingFlags.Public) != null)
      return true;
    else
      return false;
    }

The ApplyAuthorization Property

The ProvideProperty attribute on ReadWriteAuthorization specified that an ApplyAuthorization property would be dynamically added to all controls extended by ReadWriteAuthorization. Of course, the controls being extended really have no knowledge of this new property or what to do with it. All the behavior associated with the property is contained within the extender control itself.

The extender control manages the ApplyAuthorization property by implementing both the GetApplyAuthorization() and SetApplyAuthorization() methods. Windows Forms calls these methods to get and set the property value for each control that has been extended. Windows Forms prepends Get and Set automatically to call these methods.

To manage a list of the controls that have been extended, a Dictionary object is used.

private Dictionary<Control, bool> _sources =
    new Dictionary<Control, bool>();

  public bool GetApplyAuthorization(Control source)
  {
    bool result;
    if (_sources.TryGetValue(source, out result))
      return result;
    else
      return false;
  }

  public void SetApplyAuthorization(Control source, bool value)
  {
    if (_sources.ContainsKey(source))
      _sources[source] = value;
    else
      _sources.Add(source, value);
    }

When Windows Forms indicates that the ApplyAuthorization property has been set for a particular extended control, the SetApplyAuthorization() method is called. This method records the value of the ApplyAuthorization property for that particular control, using the control itself as the key value within the Dictionary.

Conversely, when Windows Forms needs to know the property value of ApplyAuthorization for a particular control, it calls GetApplyAuthorization(). The value for that control is retrieved from the Dictionary object and returned. If the control can't be found in the Dictionary, then false is returned, since that control is obviously not being extended.

The end result here is that the ReadWriteAuthorization control maintains a list of all the controls it extends, along with their ApplyAuthorization property values. In short, it knows about all the controls it will affect, and whether it should be affecting them or not.

Applying Authorization Rules

At this point, the extender control's basic plumbing is complete. It gets to choose which controls to extend, and it maintains a list of all the controls it does extend, along with the ApplyAuthorization property value for each of those controls.

When the UI developer wants to enforce authorization rules for the whole form, she can do so by triggering the ReadWriteAuthorization control. To allow this, the control implements a ResetControlAuthorization() method. This method is public, so it can be called by code in the form itself. Typically, this method will be called immediately after a business object has been loaded and bound to the form, or immediately after the user has logged into or out of the application. It is also a good idea to call it after adding a new business object to the database, since some objects will change their authorization rules to be different for an old object than for a new object.

The ResetControlAuthorization() method loops through all the items in the list of extended controls. This list is the Dictionary object maintained by Get/SetApplyAuthorization, as discussed earlier. The ApplyAuthorization value for each control is checked, and if it is true, then the authorization rules are applied to that control.

public void ResetControlAuthorization()
  {
    foreach (KeyValuePair<Control, bool> item in _sources)
    {
      if (item.Value)
      {
        // apply authorization rules
        ApplyAuthorizationRules(item.Key);
      }
    }
  }

To apply the authorization rules, the code loops through the target control's list of data bindings. Each Binding object represents a connection between a property on the control and a data source, so it is possible to get a reference to the data source through the DataSource property.

private void ApplyAuthorizationRules(Control control)
  {
    foreach (Binding binding in control.DataBindings)
    {
      // get the BindingSource if appropriate
      if (binding.DataSource is BindingSource)
      {
        BindingSource bs =
          (BindingSource)binding.DataSource;
        // get the BusinessObject if appropriate
        Csla.Security.IAuthorizeReadWrite ds =
          bs.Current as Csla.Security.IAuthorizeReadWrite;
if (ds != null)
        {
          // get the object property name
          string propertyName =
            binding.BindingMemberInfo.BindingField;

          ApplyReadRules(
            control, binding,
            ds.CanReadProperty(propertyName));
          ApplyWriteRules(
            control, binding,
            ds.CanWriteProperty(propertyName));
        }
      }
    }
  }

If the data source implements IAuthorizeReadWrite, then both ApplyReadRules() and ApplyWriteRules() methods are called to change the target control's state based on whether the current user is authorized to read and write the property.

Notice that both ApplyReadRules() and ApplyWriteRules() accept the target control, the Binding object, and a Boolean indicating whether the user is authorized to perform the particular operation (read or write). This ensures that these methods have all the information they need to know to alter the target control's appearance.

The ApplyReadRules Method

Finally, we get to the heart of the matter: altering the target control. If the user is not allowed to read the property value, the target control must not display the value. To prevent display of the value, two things are done to the target control: it is disabled, and any values coming from the data source to the control are intercepted and replaced with an empty value.

Disabling the control is easily accomplished by setting its Enabled property to false. All controls have an Enabled property, so this is not an issue. Intercepting all values from the data source before they reach the control is more complex. Fortunately, data binding offers a solution through the Format event. All Binding objects have both Format and Parse events, which can be used to alter data as it moves from the data source to the control and then back to the data source.

The Format event is raised after the data value has been read from the data source, but before the value is provided to the control. The idea is that a UI developer can handle this event and use it to format the value for display. In this case, however, the value will simply be replaced with a default empty value instead, thus ensuring that the control never gets the real value that the user isn't authorized to see.

To handle the Format event, a method is required.

private void ReturnEmpty(
    object sender, ConvertEventArgs e)
  {
    e.Value = GetEmptyValue(e.DesiredType);
  }
private object GetEmptyValue(Type desiredType)
  {
    object result = null;
    if (desiredType.IsValueType)
      result = Activator.CreateInstance(desiredType);
    return result;
    }

The ReturnEmpty() method handles the Format event. It then calls GetEmptyValue() to get an empty value appropriate for the data type of the value read from the data source. That empty value is returned through e.Value. The result is that data binding puts this empty value into the control rather than the original value from the data source.

Within the ApplyReadRules() method, if the user is not authorized to read the property, the control will be disabled and the event handler will be set up.

ctl.Enabled = false;
      binding.Format += new ConvertEventHandler(ReturnEmpty);

      // clear the value displayed by the control
      PropertyInfo propertyInfo =
        ctl.GetType().GetProperty(binding.PropertyName,
          BindingFlags.FlattenHierarchy |
          BindingFlags.Instance |
          BindingFlags.Public);
      if (propertyInfo != null)
      {
        propertyInfo.SetValue(ctl,
          GetEmptyValue(Utilities.GetPropertyType(propertyInfo.PropertyType)),
                                     new object[] { });
        }

Of course, the control might have already contained a value, and if so, that value must be removed. To do this, the type of the property value is retrieved using reflection, and the GetEmptyValue() method is called to get an appropriate empty value. This value is then placed into the control, overwriting any previous value the control may have had.

The reverse of the process occurs if the user is allowed to read the property. In that case, the control is enabled and the Format event handler is removed.

bool couldRead = ctl.Enabled;
      ctl.Enabled = true;
      binding.Format -=
        new ConvertEventHandler(ReturnEmpty);
        if (!couldRead) binding.ReadValue();

Additionally, if the control was disabled before this code was run, it is assumed that the control doesn't contain a valid value. The ReadValue() method on the Binding object is called to force data binding to reload the control with the value from the data source.

The ApplyWriteRules Method

The ApplyWriteRules() method is similar to ApplyReadRules() but takes a slightly different approach. In this case, users may be able to view the data, but they certainly can't be allowed to edit the data. If the control implements a ReadOnly property, then it can be set to false; otherwise, the control must be entirely disabled through the use of its Enabled property.

As an optimization, if the control is a Label, the method will exit immediately. Because Label controls are so common, and they are read-only by definition, it is worth this special check.

The preference is to use the control's ReadOnly property if it is implemented by the control. Reflection is used to get a PropertyInfo object corresponding to the control's ReadOnly property.

// enable/disable writing of the value
    PropertyInfo propertyInfo =
      ctl.GetType().GetProperty("ReadOnly",
        BindingFlags.FlattenHierarchy |
        BindingFlags.Instance |
        BindingFlags.Public);
    if (propertyInfo != null)
    {
      bool couldWrite = (!(bool)propertyInfo.GetValue(
        ctl, new object[] { }));
      propertyInfo.SetValue(ctl, !canWrite, new object[] { });
      if ((!couldWrite) && (canWrite))
        binding.ReadValue();
      }

If a ReadOnly property is found, then it is set to true or false depending on whether the user is allowed or denied write access to the business object property.

propertyInfo.SetValue(ctl, !canWrite, new object[] { });

First, though, the value of the control's ReadOnly property is retrieved. If it is false, that means that the user was already able to edit the control—the user could write, so couldWrite is true. This is important, because if the user was unable to edit the control and now is able to edit the control, data binding needs to be told to reload the data from the data source into the control.

if ((!couldWrite) && (canWrite))
          binding.ReadValue();

Otherwise, it is possible for the user to be placed into an empty control even though there really is a value in the business object's property.

If the control doesn't have a ReadOnly property, then the Enabled property is used as a fallback. The same procedure is used, just with the Enabled property instead.

bool couldWrite = ctl.Enabled;
      ctl.Enabled = canWrite;
      if ((!couldWrite) && (canWrite))
          binding.ReadValue();

The end result is that when the user is denied write access to a business object's property, controls bound to that property are either set to ReadOnly or are disabled. And if the user is denied read access to a business object's property, controls bound to that property are disabled and empty values are placed in the control rather than any real values from the business object.

The BindingSourceRefresh Control

The BindingSourceRefresh control is also an extender control, but its purpose is quite different from the ReadWriteAuthorization control. It turns out that there's a quirk in the way Windows Forms data binding works. The BindingSourceRefresh control helps work around this quirk.

The quirk is that when data is changed in a business object, data binding doesn't always display the changes in the controls on the form. This occurs in the following sequence of events:

  1. The user edits a value in a bound control.

  2. Data binding puts the user's new value into the business object.

  3. The business object alters the value in the property set block.

  4. The business object raises its PropertyChanged event.

You would expect that data binding would handle the PropertyChanged event, realize that the property's data has changed, and then update the control with the new value. And that does happen for all controls except the current control. In other words, the PropertyChanged event causes data binding to refresh all other controls on the form except the control that initiated the change in the first place.

Obviously, this can be problematic. Consider a TextBox control that is bound to a business object property that uses a SmartDate. I'll discuss SmartDate in Chapter 16, but one of its features is to accept the + character as input and to replace it with tomorrow's date. Due to this data binding quirk, when the user enters a + character, that value is put into the business object, which translates it to tomorrow's date—but that new value is not displayed to the user. The user continues to see the + character.

What's even more confusing for users is that if they edit a different control, then the date text box control will be updated with tomorrow's date. Remember that data binding updates everything except the current control when it gets a PropertyChanged event.

This is the problem BindingSourceRefresh is intended to solve. It does so by interacting with the BindingSource control that manages the data binding for a given business object. While ReadWriteAuthorization extends controls like TextBox and Label, BindingSourceRefresh extends BindingSource controls.

The plumbing code in this control is virtually identical to ReadWriteAuthorization, so I won't walk through all the details of the control.

This control only extends BindingSource controls, and if it is enabled, it hooks the BindingComplete event raised by the BindingSource control. This event is raised by a BindingSource control after all controls have had their values updated through data binding—well, all controls except the current one, of course. The Control_BindingComplete() method takes the extra step of forcing the BindingSource control to refresh the value for the current binding as well.

private void Control_BindingComplete(
    object sender, BindingCompleteEventArgs e)
  {
    switch (e.BindingCompleteState)
    {
      case BindingCompleteState.Exception:
        if (BindingError != null)
        {
          BindingError(this, new BindingErrorEventArgs(e.Binding, e.Exception));
        }
        break;
      default:
        if ((e.BindingCompleteContext ==
                     BindingCompleteContext.DataSourceUpdate)
                  && e.Binding.DataSource is BindingSource
                  && GetReadValuesOnChange((BindingSource)e.Binding.DataSource))
        {
          e.Binding.ReadValue();
        }
        break;
    }
    }

The BindingComplete event includes a BindingCompleteEventArgs parameter, and that parameter includes a property indicating whether the binding process completed with an exception or normally.

In the case of an exception, the BindingSourceRefresh control raises its own BindingError event, so the UI developer can be informed that an exception occurred and can take steps. By default, exceptions during binding are silently swallowed by data binding, and the only place you'll see them is in the output window of the debugger in Visual Studio. By raising this event when an error occurs, the control enables the UI developer to more easily detect and troubleshoot data binding issues.

The normal case, however, is that the binding succeeds. In that case, the e parameter includes a reference to the currently active Binding object. It is this Binding object that isn't refreshed automatically when data binding gets a PropertyChanged event from the underlying data source. By calling its ReadValue() method, this code forces data binding to read the value from the data source and update the current control's display as well.

The BindingSourceRefresh control should be used to force data refreshes for all BindingSource controls bound to detail forms. It isn't necessary when only complex controls such as a GridView or ListBox are bound to the object.

The CslaActionExtender Control and Associated Components

When an object is bound to a BindingSource control, the BindingSource control assumes it has full and exclusive control over the object. This means that when an object is data bound, the only interaction with the object should be through the BindingSource control. This includes doing things like saving the object, which is a concept the BindingSource doesn't understand. So how do you save an object? You must first unbind it; you need to disconnect it from the BindingSource.

This is also true for any explicit calls to BeginEdit(), CancelEdit(), or ApplyEdit(). I discuss these n-level undo methods in Chapter 13. The UI commonly calls these methods when it includes a top-level Cancel button on the form.

Unfortunately, unbinding an object from a BindingSource control is not as easy as setting the DataSource property to null. While that breaks the reference from the control to the object, it doesn't end any current edit session where the BindingSource has called BeginEdit() through the IEditableObject interface.

The next thing you might consider is simply calling EndEdit() or CancelEdit() on the BindingSource before setting the value to null.

bindingSource.EndEdit();
    bindingSource.DataSource = null;

However, this won't work either, because as soon as you call EndEdit(), the BindingSource immediately calls BeginEdit(). You need to disconnect the object first, then end the edit session. This helper method demonstrates the process:

protected void UnbindBindingSource(
      BindingSource source, bool apply, bool isRoot)
    {
      System.ComponentModel.IEditableObject current =
        source.Current as System.ComponentModel.IEditableObject;
      if (isRoot)
        source.DataSource = null;
      if (current != null)
        if (apply)
          current.EndEdit();
        else
          current.CancelEdit();
    }

While you could include this code in each form or create a base form from which you inherit, that's complexity that would be nice to avoid.

The complexity is even worse if you have a master-detail display in your form. In that case, you must remember to unbind all child BindingSource controls first, and then unbind the root (master) BindingSource last.

To avoid all this complexity, CSLA .NET includes the CslaActionExtender control. This control extends any control that implements IbuttonControl, so the control automatically understands how to unbind the objects before interacting with them. Like the other controls I've discussed so far, this is an extender control, so it adds extra behaviors to existing controls—in this case, button-style controls.

Behind the scenes, CslaActionExtender uses BindingSourceHelper and BindingSourceNode objects to do the actual work. This is important, because you can use BindingSourceHelper and BindingSourceNode directly to get the same behavior if you're not using a button-style control. For example, if you implement your Save and Cancel buttons on a ToolBar, you'll be unable to use CslaActionExtender, but you'll still be able to use BindingSourceHelper and BindingSourceNode to simplify your UI code.

The BindingSourceNode Class

The BindingSourceNode class is designed to wrap a BindingSource component, which sits on your Windows form. The name of this class is due to the fact that it represents a node in what can be a tree of binding sources.

When binding a form to a business object that is a parent to one or more child objects or collections, the form requires a BindingSource component for each object or collection. This hierarchical set of BindingSource components is represented by a set of BindingSourceNode objects. The BindingSourceNode class has a recursive design. Not only does it contain a property to hold the corresponding BindingSource component, but it also contains a property that contains a list of other BindingSourceNode objects. Each instance also holds a reference to its parent.

It is through this design that a tree of BindingSource components can be represented like this:

private BindingSource _Source;
    private List<BindingSourceNode> _Children;
    private BindingSourceNode _Parent;

These member variables are exposed through public properties. The BindingSourceNode class also provides methods to assist you in any of the following tasks that involves binding or unbinding:

  • Bind

  • Unbind

  • SetEvents

  • ResetBindings

These methods wrap standard functionality that you need to perform in the case of saving a bound business object or invoking n-level undo functionality on a bound business object. As I mentioned earlier, when saving or undoing a business object that is participating in Windows Forms data binding, you must first properly unbind the object before acting upon it. The methods exposed by the BindingSourceNode class help you by performing this functionality for you and, more importantly, by taking into account any child BindingSource components that it may contain. Since the BindingSourceNode class uses a recursive design, the helper methods can be called from within any level of the tree, but more often than not, you will be addressing the top-level node. In fact, to avoid any potential n-level undo parent-to-child mismatches, this is the recommended practice.

Because the act of saving or invoking an undo operation involves more than one of the helper operations exposed by the BindingSourceNode object, additional methods are typically used from the Windows form.

public void Apply()
    {
      SetEvents(false);
      Unbind(false);
    }

    public void Cancel(object businessObject)
    {
      SetEvents(false);
      Unbind(true);
      Bind(businessObject);
    }

    public void Close()
    {
      SetEvents(false);
      Unbind(true);
    }

The BindingSourceHelper Class

The tree of BindingSourceNode objects is built using the BindingSourceHelper component. The BindingSourceHelper component exposes a static method called InitializeBindingSourceTree, which returns an instance of BindingSourceNode. This method builds the entire tree of BindingSource components on your form.

The InitializeBindingSourceTree() method accepts two arguments.

public static BindingSourceNode InitializeBindingSourceTree(
    IContainer container, BindingSource rootSource)

The two arguments correspond to the form's container property and the BindingSource object on your form that binds to your root object. You can find the form's container property in the designer partial class that gets created along with a form and modified dynamically every time you drop controls or components onto the form. The components you place on a form that sit in the component tray get added to the container property, so it is this property that the InitializeBindingSourceTree() method needs.

The functionality contained in BindingSourceHelper could have resided in BindingSourceNode as a static method, but it made more sense to separate these two classes out in the interest of maintaining a strict separation of the node class from the class that creates the actual tree. The design also allows for future enhancements to be added to the BindingSourceHelper class while maintaining a clean BindingSourceNode class.

Usage of these classes is quite simple and can save you lots of confusing code. In a conventional Windows Forms binding situation, you set up your BindingSource components declaratively and then set the DataSource property of the root BindingSource component to the top-level business object. This step is taken care of automatically if you're using the new components, in which case you obtain an instance of a BindingSourceNode class by calling the InitializeBindingSourceTree() method from the BindingSourceHelper component. After this, you simply call the Bind method of the new variable and send your top-level business object into its only argument.

BindingSourceNode _bindingTree = null;

    private void BindUI()
    {
         _bindingTree = BindingSourceHelper.InitializeBindingSourceTree(
            this.Container, orderBindingSource);
         _bindingTree.Bind(order);
    }

You can now use the instance of the BindingSourceNode class, depicted in the previous code by the _bindingTree field, to unbind or rebind to assist you while performing save or undo operations. You use the BindingSourceNode class's Apply() method just before you save your business object. After this, you can incorporate the aid of the object's Bind() method. Use the Cancel method to invoke an undo operation; it rebinds the object afterwards automatically.

I should reiterate that the BindingSourceNode object contains the entire tree of BindingSource components as it corresponds to the parent-child object hierarchy you have designed into the business object that you will be binding. All actions performed on BindingSourceNode object propagate properly to the child nodes, and in fact do so in reverse order, so the lowest child gets hit first and the root gets hit last. Should you have two distinct object hierarchies represented on a Windows form, thus having two root BindingSource components, you would need two separate instances of the BindingSourceNode class. And remember, all interaction with your business object should now take place through these two classes.

The following is a sample of a form's code that uses all the functionality of these two components in a toolbar scenario, with a root object representing an order with some order detail children:

public partial class OrderMaint2 : Form
  {
    public OrderMaint2()
    {
      InitializeComponent();
    }

    public OrderMaint2(Guid orderId)
    {
      InitializeComponent();
      _order = Order.GetOrderWithDetail(orderId);
      BindUI();
    }

    Order _order = null;
    BindingSourceNode _bindingTree = null;

    private void BindUI()
    {
      _bindingTree = BindingSourceHelper.InitializeBindingSourceTree(
      this.components, orderBindingSource);
      _bindingTree.Bind(_order);
    }

    private void toolSave_Click(object sender, EventArgs e)
    {
      if (Save())
        MessageBox.Show("Order saved.");
    }
private void toolSaveNew_Click(object sender, EventArgs e)
    {
      if (Save())
      {
        _order = Order.NewOrder();
        BindUI();
      }
    }

    private void toolSaveClose_Click(object sender, EventArgs e)
    {
      if (Save())
        this.Close();
    }

    private void toolCancel_Click(object sender, EventArgs e)
    {
      _bindingTree.Cancel(_order);
    }

    private void toolClose_Click(object sender, EventArgs e)
    {
      _bindingTree.Close();
    }

    private bool Save()
    {
      bool ret = false;
      _bindingTree.Apply();

      try
      {
        _order = _order.Save();
        ret = true;
      }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message);
      }
      BindUI();
      return ret;
    }
  }

You can use the BindingSourceNode and BindingSourceHelper classes to save you a lot of code and complexity, but you're still responsible for code involving validation checking, broken rules reporting, exception handling, and, of course, the actual functionality to save or undo the object. You'll need to do this when you're using toolbars to allow users to interact with your forms, but if you're going to be using buttons or links, there is a better way.

Using the CslaActionExtender Control

As stated earlier, the CslaActionExtender component is an extender provider that adds functionality to any control that implements the IButtonControl interface. This means that you can use it with any Visual Studio button or link as well as with any third-party button control or link.

The CslaActionExtender makes use of both the BindingSourceNode and the BindingSourceHelper classes and provides an almost no-code approach to functionality for which you would normally have to write quite a bit of code. This functionality includes saving a business object, invoking undo functionality of a business object, and closing a form. Moreover, the saving functionality comes in three flavors, which I'll get to in a minute.

The CslaActionExtender component drags onto your form's component tray just like the ReadWriteAuthorization and BindingSourceRefresh components—and just like a BindingSource component, for that matter. You can hit the ground running on this control in just one step. Simply call the ResetActionBehaviors method of the component and pass your root business object into its one and only argument. This initializes the component and everything within it, just like the ResetControlAuthorization() method in the ReadWriteAuthorization component.

You'll then need some buttons on your form to represent certain actions. The mere presence of the CslaActionExtender component adds several properties to your buttons, all nicely organized in the Csla property browser category. The CslaActionExtender component can extend button or button-like controls to provide save, cancel, or close functionality automatically.

The CslaActionExtender component also includes properties and events of its own, the most important being the DataSource property, which you would point to a root BindingSource on the form. Table 10-4 lists other properties.

Table 10.4. Properties of CslaActionExtender

Property

Description

AutoShowBrokenRules

Allows the control to automatically display any broken rules found on the root object using a standard Windows Forms message box.

WarnIfCloseOnDirty

Allows the control to optionally warn you if you press a Close button while the bound business object is dirty.

DirtyWarningMessage

Used with WarnIfCloseOnDirty, this is the message that will display.

WarnOnCancel

Allows the control to warn you when you attempt to use a Cancel button for an undo operation when the bound business object is dirty.

WarnOnCancelMessage

Used with WarnOnCancel, this is the message that will display.

You can intercept all this functionality using the CslaActionExtender's event model, which I'll describe later.

Besides all these properties, which apply to the component as a whole, there are button-specific properties as well. The most important one is called ActionType and appears with the others in the Csla property browser category as ActionType on cslaActionExtender1.cslaActionExtender1 is the name of the CslaActionExtender component you dropped onto the form.

The default value for this property is None, which means this button provides no extended functionality at all. It also means that this button will not trigger any communication with the associated CslaActionExtender component. The other three values for this property are Save, Cancel, and Close. Setting this property to any one of these values assigns that functionality to the control.

When you set the property to Save, you have an additional option for a post-save action, configured by the PostSaveAction extended property. The possible values for this property are None, AndClose, and AndNew. As you can see, you can provide plenty of functionality for your form with virtually no code. Table 10-5 lists the other extended properties.

Table 10.5. Extended Properties of CslaActionExtender

Property

Description

CommandName

Uniquely identifies the control so that when an event is raised from the CslaActionExtender component, the subscriber can determine which control caused the event to fire

DisableWhenClean

Allows the monitoring of the underlying object graph's "dirty" state and provides automatic enabling and disabling of the button depending on the state

RebindAfterSave

Rebinds your object after a Save action has finished; this is set to false in the case of a Close button or a Save-AndNew button

Note

When using the DisableWhenClean property, pay close attention to the Data Source Update mode for each individual entry control (TextBox, CheckBox, etc.). The default setting of OnValidation in combination with a true setting on the property causes the button to remain disabled until you tab out of the changed text field, which may not be a desired behavior. Alternatively, changing the mode to OnPropertyChanged produces a nice instant enabling effect on the button, but it also updates your BindingSource on every key press, thus setting your object's property on every key press and possibly running validation rules on every key press. This also may not be a desired behavior, depending on the weight of your business rules.

The CslaActionExtender control can keep track of almost everything having to do with your business object(s) except for how to create a new one. You determine how to create a new object when you write a factory method that makes a call to DataPortal.Create.

For this reason, if you configure one of your buttons with a Save value on the ActionType property and an AddNew value on the PostSaveAction property, you must trap the SetForNew event of the extender component. It is here where you need to reset your business object variable to a new instance and call your rebind method. This rebind method sets the BindingSource component's DataSource property, calls the ResetControlAuthorization() method on any ReadWriteAuthorization components you may be using, and calls ResetActionBehaviors on any CslaActionExtender components.

Table 10-6 lists other events of CslaActionExtender.

Table 10.6. Events Raised by CslaActionExtender

Event

Description

BusinessObjectInvalid

Raised when an attempt is made to save an invalid object. Remember that the validity check on the root object reads into all underlying child objects in the object graph. Also remember that information and warning rules do not invalidate an object.

Clicking

Raised immediately upon clicking the button, before any action takes place. The event allows the cancellation of any functionality that may follow.

Clicked

Raised at the very end of the determined action for the button.

ErrorEncountered

Raised when an exception is encountered while CslaActionExtender is executing some behavior.

HasBrokenRules

Raised when an attempt is made to save the object, and the object has one or more broken validation rules of any rule severity.

ObjectSaving

Raised after a successful validity check but before the object gets saved, allowing you to test for nonfatal rules and also for any broken rules down the object graph.

ObjectSaved

Raised immediately after a successful save operation.

Working with Multiple Root Objects

If your form requirements are to manage two (or more) distinct root business objects at the same time, you will require two (or more) CslaActionExtender components, each bound to a different root BindingSource component. It's important to note that this will cause every button or link on your form to display more than one of each of the extender properties. However, they will display along with the name of each CslaActionExtender component.

ActionType on cslaActionExtender1
    ActionType on cslaActionExtender2

You can then assign specific buttons to interact with one CslaActionExtender component or the other, or even both.

Note

CSLA .NET does not propagate broken rules in child objects or child collections up to the root object, so the interaction with broken rules from the CslaActionExtender only applies to the root object, and only when that root object is of type BusinessBase. However, the validity check aggregates the entire underlying object graph when you use managed backing fields for your child objects, or properly overrides the business object's IsValid and IsDirty properties.

The CslaActionExtender component will work with a BindingSource hierarchy where the root business object is either a type of BusinessBase or BusinessListBase, so it can indeed save you a lot of code and provide for an even cleaner UI.

The following is a sample of a form's code that uses the CslaActionExtender component to act upon an order object and its children:

public partial class OrderMaint : Form
  {
    public OrderMaint()
    {
      InitializeComponent();
    }

    public OrderMaint(Guid orderId)
    {
      InitializeComponent();

      _order = Order.GetOrderWithDetail(orderId);
      BindUI();
    }
Order _order = null;

    private void BindUI()
    {
      cslaActionExtender1.ResetActionBehaviors(_order);
    }

    private void cslaActionExtender1_SetForNew(
      object sender, CslaActionEventArgs e)
    {
      _order = Order.NewOrder();
      BindUI();
    }

    private void cslaActionExtender1_ErrorEncountered(
      object sender, ErrorEncounteredEventArgs e)
    {
      MessageBox.Show(e.Ex.Message);
    }
  }

At this point, you should have an understanding of the support provided for Windows Forms data binding within CSLA .NET. I chose to discuss Windows Forms first, because as a general rule, if you support Windows Forms data binding, you support everything else. That used to be true, but it's becoming less true as WPF matures, so I'll talk about WPF next.

WPF

WPF is the newest UI technology discussed in this chapter, and it is evolving rapidly. Even from .NET 3.0 to 3.5, and from 3.5 to 3.5 SP1, there have been substantial changes to the features provided by data binding in WPF. I expect the changes to continue as WPF rapidly matures toward parity with the features of Windows Forms data binding.

WPF data binding is similar in some ways to Windows Forms. Both technologies are rich and interactive, providing immediate, event-driven interaction between the UI and the business objects. WPF supports some of the same data binding interfaces used by Windows Forms, and it has some new ones of its own, most of which are optional or redundant.

I'll discuss supporting data binding for a single object first, and then I'll discuss data binding support for collections and lists. I'll wrap up this section by discussing some custom controls provided by CSLA .NET to simplify the use of data binding in WPF.

Object Data Binding

Binding to a single object in WPF is largely automatic. You can bind to nearly any object in .NET and it just works. As with Windows Forms, however, the data binding experience is enhanced if the object implements the same interfaces listed earlier in Table 10-1.

The INotifyPropertyChanged interface should be considered the bare minimum required to participate in data binding. It notifies data binding that a property of the object has changed. WPF handles the PropertyChanged event somewhat differently from Windows Forms—a topic I'll explore later in this section.

The WPF data grid control uses the IEditableObject interface to roll back changes to a row of data if the user presses Esc. The Windows Forms data grid relies on this same behavior.

Note

At the time of this writing, the WPF data grid control is simply a project on CodePlex (www.codeplex.com) and is not an official part of the product. However, at present, it uses IEditableObject, as described here.

Data binding uses the IDataErrorInfo interface to change the display to visually indicate that the property to which a control is bound is invalid. Unlike Windows Forms, which uses an ErrorProvider control to do this, WPF data binding supports this concept natively, and you manage the appearance of a control with an invalid value by using an Extensible Application Markup Language (XAML) style.

Due to the differences between Windows Forms and WPF, the only interface needing further discussion is INotifyPropertyChanged.

PropertyChanged Event Handling

When WPF data binding handles a PropertyChanged event from an object, it assumes that only that specific property has changed. Because of that assumption, data binding only updates controls that are bound to that specific property. This is fundamentally different from Windows Forms, which updates all controls bound to the same object even if they are bound to different properties.

This means that when BusinessBase raises a PropertyChanged event, it needs to act differently for WPF than for Windows Forms. In the case of Windows Forms, even if multiple properties might have changed, it is most efficient to only raise PropertyChanged for one of the properties, since they'll all get refreshed anyway. However, in WPF, it is important to raise PropertyChanged for every property that changes. This includes properties changed directly by the user, as well as properties changed indirectly through business rules or processing. This also includes properties that didn't actually change, but may have become valid or invalid because some other property changed. For example, in an Invoice object, the AmountDue property might become invalid when the CreditLimit property is changed. If the object only raises PropertyChanged for CreditLimit, the UI won't display any visual cue to indicate that AmountDue is now invalid. Figure 10-3 illustrates this.

Incorrect WPF display when using the Windows Forms model

Figure 10.3. Incorrect WPF display when using the Windows Forms model

In Figure 10-3, the user sets the Total Due to 190 and then changes the Credit Limit from 5,000 to 50. Obviously, the Total Due now exceeds the credit limit and should be invalid, but no visual cue is provided for the user.

The object also needs to raise a PropertyChanged event for AmountDue, so data binding refreshes that property's display. If there's a PropertyChanged event for CreditLimit and TotalDue, the visual display will look like Figure 10-4.

Correct display when using the WPF data binding model

Figure 10.4. Correct display when using the WPF data binding model

In this case, the user did exactly the same thing, but when she reduced the Credit Limit and tabbed out of that control, the Total Due immediately showed that it was in error. You can implement this in BusinessBase, with a configuration setting in Csla.ApplicationContext.

Configuration

There's no universal way for a business object to know that it is data bound to a WPF form or a Windows Forms form, so you need to tell CSLA .NET which type of data binding is being used so the framework can adapt. You can do this by setting a config value in the app.config file in the appSettings section, as shown here:

<add key="CslaPropertyChangedMode" value="Xaml" />

or in code, as shown here:

Csla.ApplicationContext.PropertyChangedMode =
    Csla.ApplicationContext.PropertyChangedModes.Xaml;

If set in code, the value should be set exactly once, as the application starts up.

The default mode is Windows, which is necessary for backward compatibility with existing CSLA .NET code. This means that all WPF applications should set this property as shown, either in the config file or in code.

Raising PropertyChanged in BusinessBase

In the BusinessBase class, the code that raises the PropertyChanged event when a property is changed works differently depending on the PropertyChangedMode setting.

protected virtual void PropertyHasChanged(string propertyName)
    {
      MarkDirty(true);
    var propertyNames = ValidationRules.CheckRules(propertyName);
    if (ApplicationContext.PropertyChangedMode ==
                      ApplicationContext.PropertyChangedModes.Windows)
      OnPropertyChanged(propertyName);
    else
      foreach (var name in propertyNames)
        OnPropertyChanged(name);
    }

If the mode is Windows, then OnPropertyChanged() will be called to raise the event just one time, for the property that was just changed.

if (ApplicationContext.PropertyChangedMode ==
                        ApplicationContext.PropertyChangedModes.Windows)
        OnPropertyChanged(propertyName);

If the mode is Xaml, then the code will loop through a list of all the properties for which validation rules were checked.

foreach (var name in propertyNames)
          OnPropertyChanged(name);

The CheckRules() method, which I'll discuss in Chapter 11, returns a string array containing the names of all the properties for which business or validation rules were invoked. This is effectively the list of all the properties that might need to be refreshed in the WPF interface.

The OnPropertyChanged() method is called for each property name, ensuring that the UI updates properly, even if changing one property affects the validation status of other properties.

Collection Data Binding

WPF supports two different interfaces for binding to collections: INotifyCollectionChanged and IBindingList. The INotifyCollectionChanged interface and the associated ObservableCollection<T> class were introduced with WPF. The INotifyCollectionChanged interface offers a simpler alternative to the relatively complex IBindingList interface.

It is important to realize that WPF supports both IBindingList and INotifyCollectionChanged. If a collection implements either interface, it will work fine with WPF data binding. However, for a collection to be useful in Windows Forms as well as WPF, then IBindingList is the only valid option.

A collection should never implement both interfaces. The IBindingList interface requires implementation of the ListChanged event, and INotifyCollectionChanged requires implementation of the CollectionChanged event. If a collection implements both, then it will raise both events, and WPF will respond to both events. Each action to a list affects data binding twice, so some very unexpected and undesired results would occur.

I've already discussed how the CSLA .NET collection classes inherit from BindingList<T> and thus implement IBindingList. This allows them to work properly with WPF, as well as Windows Forms and Web Forms.

Controls and Helper Objects

WPF data binding is powerful and fun to use. It minimizes the amount of code required in the UI, and it provides a clean layer of abstraction between the UI and your business layer. However, there are some areas where repetitive code can still be required in the UI, and to minimize this, CSLA .NET includes a set of custom WPF controls.

The CslaDataProvider Control

WPF has a data control concept similar to the data source control concept from ASP.NET 2.0. In WPF, these data controls are called data providers, and they allow declarative data access from your XAML code, or your code-behind.

Data provider controls are powerful, because they abstract the concept of data access within a WPF form, and they can support additional behaviors such as providing asynchronous access to data.

As with ASP.NET, WPF provides an ObjectDataProvider control that might, at first glance, appear to be a good way to work with CSLA .NET-style business objects. Unfortunately, the ObjectDataProvider has some of the same limitations as the ASP.NET ObjectDataSource control:

  • It requires a public constructor.

  • It has no way to call a static (or any other type of) factory method.

CSLA .NET-style business objects have non-public constructors, and factory methods are used to create or retrieve the object. Additionally, CSLA .NET objects intrinsically support n-level undo and persistence, and the ObjectDataProvider has no knowledge of those capabilities either.

What's needed is a data provider control that understands how to call static factory methods and how to manage the object's lifetime: interacting with n-level undo and CSLA .NET-style object persistence.

The CslaDataProvider is a WPF data provider control that understands how to interact with CSLA .NET business objects. This control can not only create or retrieve a business object, but it can also manage the object's entire lifetime through to saving (inserting, updating or deleting) the object into the database or canceling any changes made to the object by the user.

Finally, like many other data provider controls, CslaDataProvider supports asynchronous loading of the object by implementing an IsAsynchronous property. This can be a powerful feature in many cases, because it tells the control to create or retrieve the object on a background thread, and the UI updates automatically when the process is complete.

Declaring the Class

The .NET Framework includes a base class that makes it relatively easy to create a data provider control, and CslaDataProvider inherits from that class.

public class CslaDataProvider : DataSourceProvider

In the simplest case, a subclass of DataSourceProvider needs only to override the BeginQuery() method, where it creates or retrieves the object requested by data binding. The CslaDataProvider control does that, and quite a bit more.

The BeginQuery Method

A subclass of DataSourceProvider needs to override the BeginQuery() method, which data binding invokes when it needs the control to create or retrieve an object. This can happen in the following cases:

  • When the WPF form is loaded and IsInitialLoadEnabled is true (the default)

  • When a property of the data provider control is changed (via data binding or code)

The BeginQuery() method must honor some properties from the base class. First, it must support the concept of deferred refresh, which allows the UI code to set many properties of the data provider control and have the query run only once after they've all been set. The IsRefreshDeferred property on the base class controls this. Second, it also must support the IsInitialLoadEnabled property. If this property is false, then the first time BeginQuery() is invoked, it must return without doing any work. Finally, the CslaDataProvider control supports an IsAsynchronous property, and if that is true, then the query is run on a background thread.

Here's the code:

protected override void BeginQuery()
  {
    if (this.IsRefreshDeferred)
      return;

    if (_firstRun)
    {
      _firstRun = false;
      if (!IsInitialLoadEnabled)
        return;
    }

    QueryRequest request = new QueryRequest();
    request.ObjectType = _objectType;
    request.FactoryMethod = _factoryMethod;
    request.FactoryParameters = _factoryParameters;
    request.ManageObjectLifetime = _manageLifetime;

    if (IsAsynchronous)
      System.Threading.ThreadPool.QueueUserWorkItem(DoQuery, request);
    else
      DoQuery(request);
    }

You can see how the IsRefreshDeferred, IsInitialLoadEnabled, and IsAsynchronous properties are used to control the flow of the process.

Ultimately, the DoQuery() method handles the real work, either on a background thread or synchronously as appropriate.

The DoQuery Method

The DoQuery() method is relatively simple, because it just invokes the specified factory method and returns the resulting business object.

To invoke the factory method, it uses a bit of public reflection. This is necessary because the factory method name comes from XAML, so it's obviously just a string value. If you look at the code, you'll see how it first attempts to find a factory method with parameters that match those provided from the XAML.

BindingFlags flags = BindingFlags.Static |
                                         BindingFlags.Public |
                                         BindingFlags.FlattenHierarchy;
      MethodInfo factory = request.ObjectType.GetMethod(
        request.FactoryMethod, flags,
        null,
          MethodCaller.GetParameterTypes(parameters), null);

Hopefully, that will succeed, and the factory can be invoked. However, if a strongly typed match can't be made, another attempt will be made to find any factory with the correct number of parameters.

if (factory == null)
      {
        int parameterCount = parameters.Length;
        MethodInfo[] methods = request.ObjectType.GetMethods(flags);
        foreach (MethodInfo method in methods)
          if (method.Name == request.FactoryMethod &&
                               method.GetParameters().Length == parameterCount)
          {
            factory = method;
            break;
          }
        }

This covers the case where a factory method is defined to accept parameters of type object, for example.

Assuming some matching factory method is found, it is invoked.

result = factory.Invoke(null, parameters);

If the ManageObjectLifetime property is true, then CslaDataProvider is expected to support the advanced save and cancel features. To do this, it must call the n-level undo BeginEdit() method on the object before returning it to data binding.

if (request.ManageObjectLifetime && result != null)
    {
      Csla.Core.ISupportUndo undo = result as Csla.Core.ISupportUndo;
      if (undo != null)
        undo.BeginEdit();
      }

Finally, the result is returned to data binding, along with any exception that may have occurred during the process.

base.OnQueryFinished(result, exceptionResult, null, null);

To be clear, if an exception occurs in DoQuery(), it is caught, and the Exception object is returned as part of this result. The exception isn't allowed to bubble up as normal. This is important, because this code could be running on a background thread. In that case, were the exception to simply bubble up, it would be unavailable to the UI developer, making debugging virtually impossible.

The OnQueryFinished() method returns the resulting object, if any, and the exception, if any, so they can be provided to the UI through data binding.

The CslaDataProviderCommandManager Class

Most data provider controls only create or retrieve data objects. They have no support for saving the object or for canceling changes, nor do they know how to add or remove items from a collection. However, these are common behaviors that are used in many forms, and if the control doesn't support them, then the UI developer must write code to do this work.

The CslaDataProvider control does implement these behaviors, which reduces the amount of code required to create a UI, sometimes even enabling a UI developer to create a fully interactive data editing form with no code behind.

The support for the save, cancel, add new item, and remove item behaviors is handled through the WPF concept of commanding. Some WPF controls can send a command to other controls, and this is often done instead of implementing an event handler. For example, a Button control can send a command rather than having a Click event handler.

The challenge, in this case, is that commands can only be routed to visual UI elements, and a data provider is not a visual element. To overcome this limitation and allow commands to be routed to CslaDataProvider, every instance of CslaDataProvider exposes an instance of CslaDataProviderCommandManager, which is a visual element (though it is never displayed to the user). Figure 10-5 shows this relationship.

CslaDataProvider and CslaDataProviderCommandManager

Figure 10.5. CslaDataProvider and CslaDataProviderCommandManager

This allows controls such as a button to route the appropriate command to the CslaDataProviderCommandManager, which invokes a method on the CslaDataProvider to do the actual work.

For example, this XAML defines a Save button:

<Button
    Command="ApplicationCommands.Save"
    CommandTarget="{Binding Source={StaticResource Project},
                                    Path=CommandManager,
                                    BindsDirectlyToSource=True}"
    HorizontalAlignment="Left" IsDefault="True">Save</Button>

Notice how the CommandTarget property is specified. The Source is a CslaDataProvider control, but the binding path indicates that the command should be routed to the data provider control's CommandManager property, which is an instance of CslaDataProviderCommandManager.

In CslaDataProviderCommandManager is a SaveCommand() method that is invoked by the WPF commanding infrastructure when the Button control is clicked:

private static void SaveCommand(object target, ExecutedRoutedEventArgs e)
  {
    CslaDataProviderCommandManager ctl =
                             target as CslaDataProviderCommandManager;
    if (ctl != null && ctl.Provider != null)
      ctl.Provider.Save();
    }

The CslaDataProviderCommandManager has a Provider property, which is a reference to its parent CslaDataProvider control. You can see how this code delegates the call to its parent:

ctl.Provider.Save();

Finally, in CslaDataProvider, the Save() method does the actual work of saving the business object by calling the object's Save() method.

result = savable.Save();

It is important to notice that the Save() method returns a new instance of the business object. I'll discuss the details in Chapter 15, but what this means here is that the new object must be provided to WPF data binding so the UI can update properly.

To do this, data binding must first be cleared and then provided with the new object.

// clear previous object
      base.OnQueryFinished(null, exceptionResult, null, null);
      // return result to base class
        base.OnQueryFinished(result, null, null, null);

The first OnQueryFinished() call passes a null for the object value. This is necessary, because if the business object is returned before clearing the value, data binding will think it is the original object and it won't update the reference. This is much like the process of clearing and resetting a BindingSource in Windows Forms for the same reason.

The Undo (cancel), New (add new item), and Remove (remove item) commands are implemented using the same technique.

The CslaDataProvider control reduces the amount of UI code required to create a data-oriented WPF form. It can create or retrieve an object so the user can interact with the data. The control allows the UI developer to use commanding to add or remove items from a collection object. Finally, the control can be used to save or cancel the changes made by the user, all entirely through XAML.

The PropertyStatus Control

WPF doesn't have an exact equivalent to the Windows Forms ErrorProvider control or the Web Forms validation controls. As I discussed earlier and illustrated in Figures 10-3 and 10-4, some basic support for IDataErrorInfo is built into WPF data binding. It is possible to use XAML styling to display validation errors from the business object to the user using that technique.

However, that approach still doesn't allow you to easily display all three severities of validation errors that I will cover in Chapter 11: error, warning, and information. WPF also doesn't provide a simple way to indicate that an asynchronous validation rule is currently running for a property that is data bound to a UI control.

In Chapter 12, I'll discuss the authorization support built into CSLA .NET, which includes the ability to specify whether a user is allowed to read or write to specific properties. Ideally, a UI control bound to a property would enable or disable (or even become hidden) based on whether the user was allowed to edit or see the property value. There is no support for such a concept in WPF.

The PropertyStatus control in the Csla.Wpf namespace is designed to address these shortcomings. This control's default appearance is similar to the Windows Forms ErrorProvider control, although as shown in Figure 10-6, it displays all three rule severities. Of course, the control is fully stylable through XAML, so you can change the visual display as needed.

PropertyStatus displaying broken validation rules

Figure 10.6. PropertyStatus displaying broken validation rules

The PropertyStatus control is typically used in a form along with a TextBox control or other data bound control. The PropertyStatus control not only has a display of its own for validation errors, but it can also control the display of the associated UI control for authorization purposes (disabling or hiding the control as necessary). Here's an example of using PropertyStatus:

<StackPanel Orientation="Horizontal">
        <TextBox x:Name="NameTextBox" Text="{Binding Path=Name}" />
      <csla:PropertyStatus Source="{Binding}"
                           Property="Name"
                           Target="{Binding ElementName=NameTextBox}"/>
      </StackPanel>

The Source and Property properties must be set so the control has access to the business object property. The Target property must be set for the authorization support to work, and this is optional. If you don't want automatic disabling of the UI control based on the business object's authorization rules, then don't set the Target property.

Perhaps the most interesting part of the PropertyStatus control's implementation is the fact that it supports visual styling. Its default appearance is defined in the Generic.xaml file contained in the /Themes folder. WPF defines the name of the folder and file; you must use these names when building controls. In Generic.xaml, you'll find a Style element for the PropertyStatus control:

<Style TargetType="{x:Type csla:PropertyStatus}">

I won't go through all the XAML in the style, as it is quite extensive and key parts were created using Expression Blend. However, it's important to understand that this style references two ControlTemplate elements that are also defined in Generic.xaml: DefaultPopupTemplate and BrokenRuleTemplate. These are constituent parts of the overall control UI, and they allow a UI designer to override just parts of the visual display. For example, a designer could replace DefaultPopupTemplate to change the look and feel of the popup that is shown when the user hovers his mouse over an error icon.

The PropertyStatus style also uses a number of other named elements such as some StoryBoard elements and a Grid element. The names of these elements, as defined by x:Key properties, are important for linking the elements to the code that implements the control itself.

In the PropertyStatus control code, attributes are applied to the class to indicate that the control makes use of a series of control templates.

[TemplatePart(Name = "root", Type = typeof(FrameworkElement))]
[TemplatePart(Name = "popup", Type = typeof(Popup))]
[TemplatePart(Name = "errorImage", Type = typeof(FrameworkElement))]
[TemplatePart(Name = "warningImage", Type = typeof(FrameworkElement))]
[TemplatePart(Name = "informationImage", Type = typeof(FrameworkElement))]
[TemplatePart(Name = "busy", Type = typeof(BusyAnimation))]
[TemplatePart(Name = "Valid", Type = typeof(Storyboard))]
[TemplatePart(Name = "Error", Type = typeof(Storyboard))]
[TemplatePart(Name = "Warning", Type = typeof(Storyboard))]
[TemplatePart(Name = "Information", Type = typeof(Storyboard))]
public class PropertyStatus : ContentControl

The control also defines a set of dependency properties, such as the Target property discussed earlier. A dependency property is a special way of declaring properties so they are fully available to data binding in WPF and WF.

public static readonly DependencyProperty
    TargetProperty = DependencyProperty.Register(
      "Target",
      typeof(DependencyObject),
      typeof(PropertyStatus));

  public DependencyObject Target
  {
    get { return (DependencyObject)GetValue(TargetProperty); }
    set { SetValue(TargetProperty, value); }
    }

As you can see, the declaration of a dependency property is not all that different from a CSLA .NET property with a managed backing field, as discussed in Chapter 7. In fact, I drew inspiration from the dependency property syntax when designing the managed backing field syntax for CSLA .NET.

WPF uses the DependencyProperty field to provide metadata about the dependency property, and it uses that metadata to support data binding. The use of dependency properties is required to create bindable properties in any WPF UI component such as a control. It is not required when creating data sources such as a business object.

The Source property is important, because it provides the control with access to the underlying data source object. When this property changes, the control must disconnect from any previous data source and connect to the new data source. This is required because the PropertyStatus control listens for events from the data source object. The SetSource() method coordinates this process.

private void SetSource(object oldSource, object newSource)
  {
    DetachSource(oldSource);
    AttachSource(newSource);
BusinessBase bb = newSource as BusinessBase;
    if (bb != null && !string.IsNullOrEmpty(Property))
      IsBusy = bb.IsPropertyBusy(Property);

    UpdateState();
  }

The UpdateState() method causes an immediate check of the validation status for the bound property from the data source object. This way, the UI is updated immediately to reflect the status of the new data source object. I'll cover the UpdateState() method later in this section of the chapter.

The DetachSource() and AttachSource() methods unhook and hook the event handlers for the data source object's BusyChanged and PropertyChanged events. For example, here's the AttachSource() method:

private void AttachSource(object source)
  {
    INotifyBusy busy = source as INotifyBusy;
    if (busy != null)
      busy.BusyChanged += source_BusyChanged;

    INotifyPropertyChanged changed = source as INotifyPropertyChanged;
    if (changed != null)
      changed.PropertyChanged += source_PropertyChanged;
    }

The BusyChanged event is used to turn on and off a busy animation. The result is that the user has a visual indication that the business object property is currently executing an asynchronous validation rule. The default visual appearance in this case is to display a BusyAnimation control, which I'll discuss later in this chapter.

The PropertyChanged event is used to trigger a check of the source object's authorization and validation rules through a call to the UpdateState() method. The UpdateState() method uses the validation and authorization concepts discussed in Chapters 11 and 12 to ask the business object whether the property has any associated broken validation rules.

List<BrokenRule> allRules = new List<BrokenRule>();
      foreach (var r in businessObject.BrokenRulesCollection)
        if (r.Property == Property)
            allRules.Add(r);

The allRules list is then used to update the icon display and populate the popup if the user hovers his mouse over the icon. This relies on the data source object being a BusinessBase object, and if it is not, then the broken rules processing will be skipped automatically.

There's similar functionality for authorization in the HandleTarget() method. This method is invoked when the control needs to check the authorization rules for the business object property. It does a cast to see if the object implements IAuthorizeReadWrite from the Csla.Security namespace. Since BusinessBase and ReadOnlyBase implement this interface, authorization works with both editable and read-only objects.

Here's the code that determines the read and write authorization for the business object property:

var b = Source as Csla.Security.IAuthorizeReadWrite;
      if (b != null)
      {
        bool canRead = b.CanReadProperty(Property);
          bool canWrite = b.CanWriteProperty(Property);

The canRead and canWrite values are then used to change the display of the Target UI control to ensure that the user can't edit or see data if he isn't authorized.

The PropertyStatus control provides simple access to rich functionality around authorization, validation, and asynchronous rule processing for WPF applications.

The BusyAnimation Control

The data portal and validation rules subsystems in CSLA .NET both support asynchronous operations. These operations run in the background, and the user is able to interact with the application while they execute. In many cases, you'll want to give the user a visual cue that a background operation is executing; otherwise, the user may be unaware that the application is actually busy doing work.

The PropertyStatus control I discussed earlier in the chapter will automatically display a busy animation if an asynchronous validation rule is executing for the property that the PropertyStatus control is monitoring. To show this animation, the PropertyStatus control uses a BusyAnimation control from the Csla.Wpf namespace.

You can also directly use the BusyAnimation control to indicate other asynchronous operations. For example, you might bind this control to a CslaDataProvider control's IsBusy property so the user is aware that an asynchronous data retrieval operation is executing.

The BusyAnimation control is stylable, much like PropertyStatus. This means that its default appearance is defined by XAML in the Generic.xaml file from the /Themes folder in the Csla project. The XAML is quite long and is mostly composed of Storyboard elements to control the animation, but it does set some default values for properties as well.

<Style TargetType="{x:Type csla:BusyAnimation}">
      <Setter Property="Background" Value="Transparent" />
      <Setter Property="BorderBrush" Value="Black" />
      <Setter Property="BorderThickness" Value="1" />
      <Setter Property="Foreground" Value="Tan" />
        <Setter Property="StateDuration" Value="0:0:0.125" />

The animation of the storyboards is handled by a DispatchTimer, which is a special timer control designed to work with WPF UI elements.

_timer = new DispatcherTimer();
    _timer.Interval = StateDuration;
    _timer.Tick += new EventHandler(timer_Tick);

The state of the storyboard is changed with each Tick event of the timer, moving from one frame of animation to the next.

private int _frame = 0;
  void timer_Tick(object sender, EventArgs e)
  {
    _isRunningStoryboard[_frame].Begin(_root);
    _frame = (_frame + 1) % NUM_STATES;
    }

The BusyAnimation control is useful for giving the user an indication that a background task is executing, and you can use it with the asynchronous data portal and the CslaDataProvider control. The PropertyStatus control uses the BusyAnimation control automatically. I'll use this control in Chapter 19.

The DataDecoratorBase Control

WPF has the concept of a decorator control, which is a control that alters the appearance of the control it contains. In other words, a decorator control contains another control and "decorates" it by altering its appearance.

The Authorizer and ObjectStatus controls, which I'll discuss later, are both decorator controls. Their behavior requires that they be aware of when the current DataContext changes, and when the data object raises a changed event (PropertyChanged, ListChanged, or CollectionChanged). Rather than having those controls implement this behavior separately, the DataDecoratorBase control handles all those details.

I won't walk through the code for DataDecoratorBase in detail. It handles the DataContextChanged event so it is aware of when the data context changes.

public DataDecoratorBase()
  {
    this.DataContextChanged +=
      new DependencyPropertyChangedEventHandler(Panel_DataContextChanged);
    this.Loaded += new RoutedEventHandler(Panel_Loaded);
    }

When it detects that the context has changed, it unhooks any events from the old context object and hooks events on the new context object. The goal is to detect when the current data context object raises a changed event such as PropertyChanged.

When that happens, a virtual method named DataPropertyChanged() is called to notify any subclass.

private void DataObject_PropertyChanged(
    object sender, PropertyChangedEventArgs e)
  {
    DataPropertyChanged(e);
    }

The subclass can override DataPropertyChanged(), and can take any appropriate steps in response to the event. The DataDecoratorBase class exists to consolidate this event handling code so it isn't repeated in other controls. It also implements a helper method named FindChildBindings(). This method walks through all the controls contained within the DataDecoratorBase control, and each time it finds a control that is data bound it calls a virtual method named FoundBinding(). The subclass can override FoundBinding(), where it can do any processing necessary related to that binding.

The Authorizer Control

The ReadWriteAuthorization control from the Csla.Windows namespace helps Windows Forms developers build interfaces where the controls on the form alter their appearance based on whether the user is authorized to read or write to the underlying business object property.

The Authorizer control in the Csla.Wpf namespace provides similar functionality for WPF. The Authorizer control is a decorator control and is a subclass of DataDecoratorBase. If you want it to affect the appearance of multiple controls, you can nest those controls within a panel or other container control, then put that container control inside the Authorizer control.

In most cases, developers will probably prefer the PropertyStatus control instead of Authorizer, because it handles not only authorization but also validation and busy status notification. However, Authorizer does provide an alternative, making it easy to implement authorization for a group of data bound controls contained within the Authorizer control.

Authorizer uses the IAuthorizeReadWrite interface from the Csla.Security namespace to interact with the business object. I'll discuss this interface in more detail in Chapter 13. The control uses this interface to determine whether the user is authorized to read or write to each business object property that is data bound to a control contained within the Authorizer control.

Any time the underlying data object (DataContext) is changed or a Refresh() method is called, Authorizer scans all the controls it contains, checking each binding to see if the current user is authorized to read and write the associated property. DataDecoratorBase does much of the hard work.

Controlling Readability

If a property is not readable, Authorizer changes the UI control's Visibility to be Hidden or Collapsed. An attached property defined by Authorizer called NotVisibleModeProperty is used to choose which option should be used for each UI control.

private static readonly DependencyProperty NotVisibleModeProperty =
    DependencyProperty.RegisterAttached(
      "NotVisibleMode",
      typeof(VisibilityMode),
      typeof(Authorizer),
      new FrameworkPropertyMetadata(VisibilityMode.Collapsed),
        new ValidateValueCallback(IsValidVisibilityMode));

The SetRead() method determines whether the user is authorized to read a property, and it alters the bound control's Visibility accordingly.

private void SetRead(Binding bnd, UIElement ctl, IAuthorizeReadWrite source)
  {
    bool canRead = source.CanReadProperty(bnd.Path.Path);
    VisibilityMode visibilityMode = GetNotVisibleMode(ctl);

    if (canRead)
      switch (visibilityMode)
      {
        case VisibilityMode.Collapsed:
          if (ctl.Visibility == Visibility.Collapsed)
            ctl.Visibility = Visibility.Visible;
          break;
        case VisibilityMode.Hidden:
          if (ctl.Visibility == Visibility.Hidden)
            ctl.Visibility = Visibility.Visible;
          break;
        default:
          break;
      }
    else
      switch (visibilityMode)
      {
        case VisibilityMode.Collapsed:
          ctl.Visibility = Visibility.Collapsed;
          break;
        case VisibilityMode.Hidden:
          ctl.Visibility = Visibility.Hidden;
          break;
        default:
          break;
      }
  }

The Visibility property is set back to Visible if the user is authorized to read the value.

Controlling Updates

If a property is not updatable, Authorizer will check to see if the UI control has an IsReadOnly property; if it does, it will set the value to true. Otherwise, it will set the IsEnabled property to false, because all controls have that property. The SetWrite() method handles this.

private void SetWrite(Binding bnd, UIElement ctl, IAuthorizeReadWrite source)
  {
    bool canWrite = source.CanWriteProperty(bnd.Path.Path);

    // enable/disable writing of the value
    PropertyInfo propertyInfo =
      ctl.GetType().GetProperty("IsReadOnly",
      BindingFlags.FlattenHierarchy |
      BindingFlags.Instance |
      BindingFlags.Public);
    if (propertyInfo != null)
    {
      propertyInfo.SetValue(
        ctl, !canWrite, new object[] { });
    }
    else
    {
      ctl.IsEnabled = canWrite;
    }
  }

There is no standard interface you can use to find the IsReadOnly property, so reflection is used. If the property exists, it will be set; otherwise, the code will fall back to disabling the UI control entirely.

The Authorizer control can save a lot of UI code when building a form for editing details, because it manages the status of all data bound detail controls with little or no code required in the UI itself. You can see how this is used in Chapter 19.

The ObjectStatus Control

Editable CSLA .NET business objects that subclass BusinessBase have a set of valuable status properties. These properties are not available for data binding, because they are marked with the [Browsable(false)] attribute, and because they don't raise the PropertyChanged event when they change. These properties were discussed in Chapter 8 and are listed in Table 8-1.

Sometimes, you may need access to these properties within your XAML code. For example, you might want to enable or disable certain controls on the form based on whether the object's IsSavable property returns true or false.

The ObjectStatus control from the Csla.Wpf namespace exposes these properties as bindable properties from a WPF control. The ObjectStatus control takes the properties from its current DataContext and exposes them as dependency properties so they can be used in control-to-control data binding. Additionally, the ObjectStatus control includes code to detect when each of the status properties has changed, so it can raise appropriate PropertyChanged events for them.

This control is relatively simple. Any time the DataContext changes or the data object raises a changed event such as PropertyChanged, it re-reads the object's status values and updates its own bindable property values—for example:

if (IsDeleted != source.IsDeleted)
        IsDeleted = source.IsDeleted;
      if (IsDirty != source.IsDirty)
        IsDirty = source.IsDirty;
if (IsNew != source.IsNew)
        IsNew = source.IsNew;
      if (IsSavable != source.IsSavable)
        IsSavable = source.IsSavable;
      if (IsValid != source.IsValid)
          IsValid = source.IsValid;

It only updates its own property value if that value differs from the business object's value. This is because setting the control's property value raises a PropertyChanged event from the control, and that will likely cause some other UI control to refresh based on a trigger or control-to-control data binding.

The end result is that you can create a control in the UI to display the status of the object. For example, here's a CheckBox control bound to the IsSavable property:

<CheckBox IsEnabled="False"
                    IsChecked="{Binding
                      RelativeSource={RelativeSource FindAncestor,
                      AncestorType=csla:ObjectStatus,
                      AncestorLevel=1},
                        Path=IsSavable}">IsSavable</CheckBox>

The binding expression is quite complex, but the end result is that the control displays the current value of the IsSavable property for the business object referred to by the DataContext.

At this point, you should have an understanding of the support provided for WPF data binding within CSLA .NET. The last technology I'll cover is Web Forms, which is the simplest from a data binding perspective.

Web Forms

Of the three UI technologies discussed in this chapter, ASP.NET Web Forms has the fewest requirements on business objects. In fact, it imposes no specific requirements on your objects or collections at all.

Any object with properties can be bound to controls in a page. Any collection, list, or array that implements IEnumerable (the most basic of all collection interfaces) can be bound to list controls on a page. In short, it just works.

Controls and Helper Objects

However, there's one catch, and that is the way the Web Forms data source controls work—specifically, the ObjectDataSource control. This control imposes some restrictions on the objects it works with, and those restrictions mean it won't work with CSLA .NET objects.

To overcome this limitation, the CslaDataSource control is an ASP.NET data source control that is designed to work with objects containing business logic. This control allows the full use of Web Forms data binding with rich business objects.

The CslaDataSource Control

Data source controls in ASP.NET have two major areas of functionality: runtime and design time. Runtime functionality is the actual data binding implementation—it copies data from the data source to the controls and back again. Design time functionality exists to support Visual Studio 2005 and 2008, allowing developers to graphically create web pages using common controls like DataGridView and DetailsView when they are bound to the data source control.

The detailed design issues around building an ASP.NET data source control are outside the scope of this book. Nonetheless, I'll walk quickly through the code in these classes to call out the highlights of the implementation.

It turns out that implementing runtime functionality is relatively straightforward, but providing design time functionality is more complex. Table 10-7 lists the classes required to implement the CslaDataSource control's runtime and design time support.

Table 10.7. Classes Required to Implement the CslaDataSource Control

Class

Description

CslaDataSource

The data source control itself; the UI developer uses this directly

CslaDataSourceView

Provides the actual implementation of data binding for CslaDataSource

CslaDataSourceConfiguration

Provides a configuration UI form displayed in Visual Studio to configure the control

CslaDataSourceDesigner

The Visual Studio designer for CslaDataSource

CslaDesignerDataSourceView

Provides schema information and sample data for the designer

ObjectSchema

The schema object for a business object, responsible for returning an instance of ObjectViewSchema

ObjectViewSchema

Provides actual information about a business object—specifically, information about all the business object's bindable properties

ObjectFieldInfo

Maintains information about a specific field in the object schema

Figure 10-7 provides a helpful diagram that illustrates how these classes relate to each other.

Relationship between the classes in CslaDataSource

Figure 10.7. Relationship between the classes in CslaDataSource

The UI developer drags a CslaDataSource control onto a Web Form and interacts with it. While in Visual Studio, CslaDataSourceDesigner coordinates all that interaction, although in reality, all the hard work is done by CslaDesignerDataSourceView. The CslaDataSourceDesigner may open the CslaDataSourceConfiguration dialog to allow the user to configure some aspects of the control.

When a control such as GridView is bound to the CslaDataSource, it requests schema information about the data source. This schema information is created and returned by the ObjectSchema, ObjectViewSchema, and ObjectFieldInfo objects.

Finally, at runtime, the web form interacts with CslaDataSource to perform the actual data binding. All the hard work is actually handled by CslaDataSourceView, an instance of which is managed by the CslaDataSource control.

The only bit of functionality that a UI developer will see is that CslaDataSource declares and raises four events. The UI developer must respond to these events to provide the interaction with the business object. Table 10-8 lists the events.

Table 10.8. Events Raised by the CslaDataSource Control

Event

Description

SelectObject

Requests that the UI provide a reference to the business object that is the data source

InsertObject

Requests that the UI insert a new business object based on the data from the form

UpdateObject

Requests that the UI update a business object with the data from the form, based on the key value provided

DeleteObject

Requests that the UI delete the business object based on the key value provided

These four events are directly analogous to the four method names required by the ASP.NET ObjectDataSource. Rather than using reflection to invoke a set of methods, I opted to raise events, as I feel that this is an easier programming model. With the ObjectDataSource, the UI developer must implement four methods (or defer to those in an ADO.NET TableAdapter), while with CslaDataSource, the developer simply handles these four events.

There is a custom EventArgs class for each of the events: SelectObjectArgs, InsertObjectArgs, UpdateObjectArgs, and DeleteObjectArgs, respectively. Each one provides properties that are used within the event handler.

The end result is a fully functional data source control that understands CSLA .NET-style business objects. UI developers can use this control to leverage the data binding support of ASP.NET Web Forms when working with rich business objects.

Conclusion

In this chapter, I discussed how CSLA .NET supports data binding for the Windows Forms, WPF, and Web Forms user interface technologies. For simple objects, this involves the implementation of a number of interfaces, including

  • INotifyPropertyChanging

  • INotifyPropertyChanged

  • IEditableObject

  • IDataErrorInfo

For collections and lists, this involves the use of either BindingList<T> or ObservableCollection<T>. Since all three UI technologies support BindingList<T>, while only WPF supports ObservableCollection<T>, the CSLA .NET framework is based on the use of BindingList<T>.

By providing support for data binding in the framework, CSLA .NET ensures that all business objects created using the framework fully support the features of data binding in all major UI technologies. This helps reduce the complexity of the business objects as well as the amount of code required to create a UI that uses the objects.

Chapters 11 through 16 will continue the coverage of the implementation of CSLA .NET. Then, from Chapter 17 on, the focus will be on building the simple business application designed in Chapter 3 to illustrate how the classes in the framework can be used to build applications based on business objects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset