Chapter 7. Property Declarations

Chapter 6 introduced the idea of the PropertyInfo<T> class from the Csla namespace. This type allows a business object to declare metadata about the object's properties, and most importantly, it helps eliminate the use of a string literal to reference the property name throughout the business class.

While a business developer could use standard .NET property declaration syntax, it would require him to invoke the authorization, business, and validation rules; processing; and data binding behaviors of CSLA .NET manually in each property.

To simplify the code required to utilize all these behaviors, CSLA .NET supports a coding model that is more abstract, allowing the framework to do much of the work automatically. The syntax is similar to that of a dependency property in WPF or WF, but the implementation behind the scenes is quite different.

This chapter will focus on how CSLA .NET manages property metadata and helps a business object manage its property values to simplify and standardize business object code.

Declaring Properties

You have quite a few options for declaring a property in a CSLA .NET business object, and I'll walk you through all the details. You can choose to declare your own private backing fields, or you can allow CSLA .NET to manage the values of your properties. Before getting into those options in detail, I'll briefly walk through one common option so you get a feel for the overall structure.

Most properties are declared like this:

private static PropertyInfo<string> NameProperty =
    RegisterProperty(new PropertyInfo<string>("Name"));
  public string Name
  {
    get { return GetProperty(NameProperty); }
    set { SetProperty(NameProperty, value); }
  }

The RegisterProperty() method registers the property's metadata with CSLA .NET, so the framework is aware of the property. Internally, CSLA .NET maintains a list of all the IPropertyInfo objects for each business object type. These values are static, so all instances of the type share one list.

The field manager subsystem in the Csla.Core.FieldManager namespace maintains the list of IPropertyInfo objects. I'll discuss the field manager later in the chapter.

The GetProperty() and SetProperty() methods help minimize the code you would otherwise have to write to trigger authorization, business, and validation rules; status tracking; and data binding.

Looking at BusinessBase in Csla.Core, you can see that GetProperty() has many overloads, but ultimately does the following:

protected P GetProperty<P>(
    PropertyInfo<P> propertyInfo, Security.NoAccessBehavior noAccess)
  {
    P result = default(P);
    if (CanReadProperty(propertyInfo.Name, noAccess ==
                       Csla.Security.NoAccessBehavior.ThrowException))
      result = ReadProperty<P>(propertyInfo);
    else
      result = propertyInfo.DefaultValue;
    return result;
  }

Note

This technique is similar to the DependencyProperty concept that WPF and WF use.

The method calls CanReadProperty(), which is part of the authorization rules subsystem I'll discuss in Chapter 10. That method checks to see if the current user is allowed to read this property. If the user is not authorized, the method will either throw an exception or return false, depending on the value of the noAccess parameter.

Assuming no exception is thrown (which is the default), then either the value or a dummy default value will be returned. If the user is authorized to read the value, she gets the real value; otherwise, she gets a dummy value.

The ReadProperty() method is used to get the real value if appropriate. ReadProperty() is a protected method of BusinessBase, so it's available in your business class as well. You should use GetProperty() when you want authorization rules to apply, and ReadProperty() when you want to read the property regardless of authorization rules.

Normally, the UI disables the display of values that the user is not authorized to see, so even if a dummy value is returned here, the user will never see it. In fact, this is why the default behavior is to return a dummy value if the user isn't authorized to see the real value. Both Windows Forms and WPF data binding typically read the value, even if the control isn't visible to the user. If the property get throws an exception, then data binding will not work properly.

Looking at the SetProperty() method in BusinessBase, you can see that it is even more complex:

protected void SetProperty<P>(
    PropertyInfo<P> propertyInfo, P newValue,
    Security.NoAccessBehavior noAccess)
  {
    if (CanWriteProperty(propertyInfo.Name, noAccess ==
                         Security.NoAccessBehavior.ThrowException))
    {
      try
      {
        P oldValue = default(P);
        var fieldData = FieldManager.GetFieldData(propertyInfo);
if (fieldData == null)
        {
          oldValue = propertyInfo.DefaultValue;
          fieldData = FieldManager.LoadFieldData<P>(propertyInfo, oldValue);
        }
        else
        {
          var fd = fieldData as FieldManager.IFieldData<P>;
          if (fd != null)
            oldValue = fd.Value;
          else
            oldValue = (P)fieldData.Value;
        }
        LoadPropertyValue<P>(propertyInfo, oldValue, newValue, true);
      }
      catch (Exception ex)
      {
        throw
          new PropertyLoadException(
            string.Format(Properties.Resources.PropertyLoadException,
                                    propertyInfo.Name, ex.Message));
      }
    }
  }

While there's a lot of code here, the process flow can be distilled down to what you see in Figure 7-1.

SetProperty() process flow

Figure 7.1. SetProperty() process flow

The CanWriteProperty() method takes care of the authorization; I'll discuss this in Chapter 10.

The LoadPropertyValue() method is a private helper method that takes care of actually setting the property value. It also does a lot of work, as shown in Figure 7-2.

LoadPropertyValue() process flow

Figure 7.2. LoadPropertyValue() process flow

The LoadProperty() method sets a property's value by ultimately calling LoadPropertyValue(). It is protected, and your business code can use it. You should use SetProperty() when you want to trigger authorization, business, and validation rules, and you should use LoadProperty() when you want to simply set a property value without triggering any of those behaviors.

Table 7-1 lists the methods you can use to get and set property values in your business code.

Table 7.1. Methods That Get and Set Property Values

Method

Description

GetProperty()

Gets a property value after checking authorization

ReadProperty()

Gets a property value without checking authorization

SetProperty()

Sets a property value after checking authorization, then triggers business and validation rules and data binding behaviors

LoadProperty()

Sets a property value without checking authorization or triggering business or validation rules or data binding behaviors

Now that you have a high-level understanding of the way properties work, I'll dig into the specific options.

Property Declaration Options

Property values must be stored somewhere. In a typical object property, values are stored in backing fields, which are simply private fields declared in the class—for example:

private string _name = string.Empty;
  private int _id;

If you use the compact property declaration syntax, the compiler will create a hidden private backing field on your behalf.

public string Name { get; set; }

In this case, you don't know or care about the field name, but the field is there nonetheless.

CSLA .NET allows you to use private backing fields if you would like, or you can allow CSLA .NET to manage the field values automatically.

Using private backing fields offers better performance, but it does require that you declare and initialize the backing field in your code. Managed backing fields incur a performance penalty, because CSLA .NET is actually storing the field values in a collection on your behalf, but they require less code in your business class.

Note

When using CSLA .NET for Silverlight, managed backing fields are serialized automatically. However, you must write extra code to coordinate the serialization of all private backing fields.

You can use private backing fields for any field value required by your object. However, when it comes to storing references to child objects, I strongly recommend using managed backing fields. I'll discuss storing child references later in the chapter, as they are relatively complex. If you choose to store child references in private backing fields, then you'll have to deal with all that complexity yourself. On the other hand, a managed backing field takes care of everything automatically.

As with many features of CSLA .NET, you can choose which technique suits your needs best. In general, I typically use managed backing fields and only switch to private backing fields when dealing with large collections of objects where performance is a bigger issue.

Private Backing Fields

The four property helper methods have overloads that accept private backing fields as parameters. For example, you can call GetProperty() like this:

private static PropertyInfo<string> NameProperty =
    RegisterProperty(new PropertyInfo<string>("Name"));
  private string _name = NameProperty.DefaultValue;
  public string Name
  {
    get { return GetProperty(NameProperty, _name); }
    set { SetProperty(NameProperty, ref _name, value); }
  }

The bold lines indicate the differences with the previous example code. Notice that a field is now declared explicitly and is initialized to a default value.

Warning

You must initialize string fields to a non-null value. Other fields may be optionally initialized, but I recommend initializing all fields as shown.

Also notice how the field is passed as a parameter to GetProperty(). As you can imagine, GetProperty() simply returns the value as a result, but only after checking authorization rules.

The SetProperty() method is more interesting, because the field is passed as a parameter using the ref qualifier. This means the field is passed by reference, so any changes you make to the field inside the SetProperty() method will actually change the value of the field itself.

In this case, SetProperty() still performs the steps shown in Figures 7-1 and 7-2, but if the property's value is ultimately changed, the new value will be put directly into that field, which is passed by reference.

Private Backing Fields with Type Conversion

There are variations on the four property helper methods that can be used to help convert a field from one type to another. For example, you may maintain the field value as an enum or SmartDate type, but declare the property itself to be of type string. This is useful when the user wants to see a friendly name, but the object wants a more computer-friendly data type for the property.

private static PropertyInfo<SmartDate> BirthDateProperty =
    RegisterProperty(new PropertyInfo<SmartDate>("BirthDate"));
  private SmartDate _birthDate = BirthDateProperty.DefaultValue;
  public string BirthDate
  {
  get { return GetPropertyConvert<SmartDate, string>(
    BirthDateProperty, _birthDate); }
  set { SetProperty<SmartDate, string>(
    BirthDateProperty, ref _birthDate, value); }
  }

Rather than calling GetProperty(), this code calls GetPropertyConvert(), which takes two type parameters. The first is the type of the backing field, and the second is the type of the property. The GetPropertyConvert() method is implemented in BusinessBase like this:

protected P GetPropertyConvert<F, P>(PropertyInfo<F> propertyInfo, F field)
  {
    return Utilities.CoerceValue<P>(
      typeof(F), null,
      GetProperty<F>(
         propertyInfo.Name, field, propertyInfo.DefaultValue,
        Security.NoAccessBehavior.SuppressException));
  }

This method delegates the task of getting the field value to the GetProperty() method you've already seen. However, it then uses Utilities.CoerceValue() to coerce the value to the specified property type.

You might wonder how this differs from just using a cast to change the value type. The CoerceValue() method attempts to perform a cast, but it's more aggressive and attempts other techniques of type conversion as well, including using .NET type converters. It also includes functionality to convert enum types into and out of text representations.

The end result is that these versions of the four property helper methods can save you a lot of code and complexity in cases where the property type and backing field type do not match.

Managed Backing Fields

My first property example illustrated how to declare a property that uses a managed backing field.

private static PropertyInfo<string> NameProperty =
    RegisterProperty(new PropertyInfo<string>("Name"));
  public string Name
  {
    get { return GetProperty(NameProperty); }
    set { SetProperty(NameProperty, value); }
  }

Notice that no private field is declared, so there's no field to pass to the GetProperty() or SetProperty() method. Behind the scenes, the field manager stores and retrieves the value from a data structure. I'll discuss the field manager in more detail later.

Managed Backing Fields with Type Conversion

As with private backing fields, four methods get and set managed property values while converting the value to different types. The syntax is similar to what you've already seen.

private static PropertyInfo<CategoryEnum> CategoryProperty =
    RegisterProperty(new PropertyInfo<CategoryEnum>("Category"));
  public string Category
  {
  get { return GetPropertyConvert<CategoryEnum, string>(CategoryProperty); }
  set { SetPropertyConvert<CategoryEnum, string>(CategoryProperty, value); }
  }

Again, rather than calling GetProperty() or SetProperty(), similar methods are called such as GetPropertyConvert(). These methods take two type parameters; the first is the type of the field value, and the second is the type of the property. The GetPropertyConvert() overload looks like this:

protected P GetPropertyConvert<F, P>(
    PropertyInfo<F> propertyInfo, Security.NoAccessBehavior noAccess)
  {
    return Utilities.CoerceValue<P>(
      typeof(F), null, GetProperty<F>(propertyInfo, noAccess));
  }

As with the earlier overload, this one gets the value and then passes it to Utilities.CoerceValue() to coerce the value to a different type.

Child Object Reference Fields

Business objects can contain other business objects. As discussed in Chapter 4, the containing object is a parent object, and the contained object is a child object. In this case, the parent object maintains a reference to the child object.

I'll discuss the issues around parent-child relationships in Chapter 9. For now, you should know that the normal way to create a property that references a child object is to write code like this in your parent object:

private static PropertyInfo<ChildType> ChildProperty =
    RegisterProperty(new PropertyInfo<ChildType>("Child"));
  public ChildType Child
  {
    get
    {
      if (!FieldManager.FieldExists(ChildProperty))
        LoadProperty(ChildProperty, ChildType.NewChild());
      return GetProperty(ChildProperty);
    }
  }

This stores the child reference in a managed backing field, which allows the field manager to automatically take care of all the housekeeping details involved with a child reference.

The RegisterProperty() and GetProperty() calls should be familiar by this point. But these two lines are new:

if (!FieldManager.FieldExists(ChildProperty))
        LoadProperty(ChildProperty, ChildType.NewChild());

The first line uses the FieldManager to determine if this child object has been created. If it has not, then the second line uses LoadProperty() to add a new instance of the object as a child. The call to ChildType.NewChild() is invoking the child object's factory method, which is a concept I discussed in Chapters 4 and 5.

This relatively simple-looking code is hiding some fairly complex object interactions, and I'll discuss them later in Chapter 9 when I cover parent-child relationships.

RegisterProperty and Inheritance

If you look closely at the way RegisterProperty() is called in the example code in this chapter, you'll see that it is called while initializing a static field.

private static PropertyInfo<string> NameProperty =
    RegisterProperty(new PropertyInfo<string>("Name"));

It makes sense that your properties need to be registered with CSLA .NET before they can be used, and if your business class inherits directly from BusinessBase or ReadOnlyBase, then this will happen automatically. The reason is that any attempt to get or set a property will call the GetProperty() or SetProperty() method, which accepts the PropertyInfo field as a parameter—for example:

return GetProperty(NameProperty);

Any attempt to access a static field forces .NET to initialize all the static fields declared in that class.

However, if you use inheritance such that your business class inherits from a class that, in turn, inherits from BusinessBase or ReadOnlyBase, then things will get more complex. This is because of the way .NET initializes static fields, which turns out to be complex and counterintuitive.

The .NET runtime only initializes the static fields for a class when one of the static fields on that class is accessed (read or changed), or if the class has a static constructor. To be very clear, this means that instance methods and properties can be called before the static fields are initialized.

Unless you are absolutely sure that a static field from every class in the inheritance hierarchy has been accessed, you can't be sure that all the static fields have been initialized. The end result is that your properties might be accessed before they have all been registered, which will ultimately cause the field manager to throw an exception.

Note

Remember that this is only an issue if your business classes don't inherit directly from a CSLA .NET base class.

You can use one of two techniques to prevent this issue. You can add a static constructor to each of your custom base classes, or you can ensure that some static field is initialized as each object instance is created.

Adding a static Constructor

Adding a static constructor is easy—for example:

[Serializable]
  public abstract class CustomBase<T> : BusinessBase<T>
    where T : CustomBase<T>
  {
  static CustomBase()
  { }
  }

All static fields are initialized before the static constructor executes, so adding this bit of code ensures that the static fields in this class will be initialized before an instance of this class, or a subclass, can be created.

The downside to this is a performance impact. When you declare a static constructor in a class, the compiler injects code everywhere any method of this class is accessed, checking to ensure that the static constructor has been run before any other code. Obviously, all this extra checking can have a negative impact on performance.

It is actually slightly worse than this, because the compiler also injects code to ensure that the static constructor is only called exactly once, even in a multithreaded environment. So the code it injects is relatively complex and involves potential locking.

Initializing a Dummy static Field

An alternative to declaring a static constructor is to ensure that at least one static field is accessed in each class before an instance of that class, or a subclass, is created. You can do this by declaring a static field in every class and initializing it any time an instance of the object is created.

The trick is to remember that the .NET Framework creates instances two ways. Normal creation of an instance invokes the constructor of each class in the inheritance hierarchy, but deserialization (when using the BinaryFormatter or NetDataContractSerializer) doesn't invoke any constructors.

Fortunately, the BusinessBase and ReadOnlyBase classes include code, so they are notified when they are deserialized. In that case, they invoke a protected method called OnDeserialized(), which a business class can override to be notified that it has been deserialized.

Using this capability, you can force initialization of the static fields in a class by adding the following code to all your business and base classes when using a custom base class:

[Serializable]
  public abstract class CustomBase<T> : BusinessBase<T>
    where T : CustomBase<T>
  {
  private static int _forceInit;

  public CustomBase()
  {
    _forceInit = 1;
  }

  protected override void OnDeserialized(StreamingContext context)
  {
    _forceInit = 1;
  }
  }

When an instance of this class, or a subclass, is created normally, the constructor is invoked. The constructor accesses the static field and ensures that all static fields are initialized. When an instance is created through deserialization, OnDeserialized() is invoked, which again accesses the static field and ensures that all static fields are initialized.

This technique requires a bit more code, but it doesn't incur the performance penalty of implementing a static constructor.

Regardless of which technique you use, the end result is that the static fields declared in each class are initialized before any properties can be accessed. This ensures that all the RegisterProperty() calls occur and that all properties are registered early in the process.

PropertyInfoManager

The PropertyInfoManager is responsible for managing all the properties that have been registered for each business object type using the RegisterProperty() method. This type is found in the Csla.Core. FieldManager namespace. Each time RegisterProperty() is called, it is associating an IPropertyInfo object with a specific business object type.

For each business object type, PropertyInfoManager maintains a list of IPropertyInfo objects that describe the properties registered for that type. This means that it also has a list of all the business object types, which is maintained in a Dictionary, as you can see in the PropertyInfoManager code:

private static Dictionary<Type, List<IPropertyInfo>> _propertyInfoCache;

This Dictionary is indexed by a Type object, representing the type of each business object with registered properties. The value is a List of IPropertyInfo objects, each containing metadata about a property registered to that type.

The hard part about this class is that its methods need to be thread-safe. In many cases, it will be used in a multithreaded environment, such as in ASP.NET, so access to this Dictionary and to each individual List object must be wrapped with locking code.

The PropertyInfoCache property does this for the Dictionary itself:

private static object _cacheLock = new object();

  private static Dictionary<Type, List<IPropertyInfo>> PropertyInfoCache
  {
    get
    {
      if (_propertyInfoCache == null)
      {
        lock (_cacheLock)
        {
          if (_propertyInfoCache == null)
            _propertyInfoCache = new Dictionary<Type, List<IPropertyInfo>>();
        }
      }
      return _propertyInfoCache;
    }
  }

The private field _cacheLock is used to lock the region of code that creates the Dictionary if it doesn't already exist. Notice how the code checks the existence of the Dictionary both before and after the lock statement. This avoids a race condition, where multiple threads could wait on the lock and run the code inside the lock, even though the first thread to reach that point would have already created the Dictionary.

Similarly, the GetPropertyListCache() method protects both the use of the Dictionary and the creation of individual List objects for each business object type.

public static List<IPropertyInfo> GetPropertyListCache(Type objectType)
  {
    var cache = PropertyInfoCache;
    List<IPropertyInfo> list = null;
    if (!(cache.TryGetValue(objectType, out list)))
    {
      lock (cache)
      {
        if (!(cache.TryGetValue(objectType, out list)))
        {
          list = new List<IPropertyInfo>();
          cache.Add(objectType, list);
        }
      }
    }
    return list;
  }

This method uses the PropertyInfoCache property to safely get a reference to the Dictionary. It then uses the TryGetValue() method to attempt to retrieve the specific List<IPropertyInfo> for the business object type. If that is unsuccessful, a lock statement is used to ensure that only one thread can run the code that creates and adds the new List object to the Dictionary. Notice how the TryGetValue() is called inside the lock statement to prevent multiple threads from getting that far and creating duplicate List objects.

The RegisterProperty() method is used to register properties for a business object type by adding an IPropertyInfo object to the correct List. This method also employs locking to avoid threading issues.

public static PropertyInfo<T> RegisterProperty<T>(
    Type objectType, PropertyInfo<T> info)
  {
    var list = GetPropertyListCache(objectType);
    lock (list)
    {
      list.Add(info);
      list.Sort();
    }
    return info;
  }

In this case, the GetPropertyListCache() method is used to safely get a reference to the List object, then a lock statement is used to block access to that specific List object so only one property can be registered at a time.

Notice that the list is sorted as each item is added. This ensures that the list is sorted when all properties have been registered and guarantees that the values are in the same order each time. Later in the chapter, I'll discuss how these values provide a numeric index into the list of managed field values for each business object. The order of the properties is very important.

Of course, the RegisterProperty() methods are called when .NET does its initialization of the static fields on each class. You might expect that those method calls would occur in the same order all the time, thanks to .NET. Unfortunately, I don't trust that to be the case across C# and VB, or between the 32- and 64-bit .NET runtimes. As you'll see later, these values must be in the same order in a client/server situation, even if the client is 32-bit .NET in VB and the server is 64-bit .NET in C#. Sorting the property objects ensures that they're in the same order in the list, regardless of the programming language or the .NET runtime version.

Finally, the GetRegisteredProperties() method returns a list of properties registered for a business object type. Since this method is public, there's no way to know what the calling code will do with the result, so this method doesn't return the actual List. Instead, it returns a copy of the data in a new List.

public static List<IPropertyInfo> GetRegisteredProperties(Type objectType)
  {
    var list = GetPropertyListCache(objectType);
    lock (list)
      return new List<IPropertyInfo>(list);
  }

The original List object is locked to block any RegisterProperty() calls from changing the list while the items are being copied to the result.

BusinessBase and ReadOnlyBase use the PropertyInfoManager to manage all the details around tracking the properties registered for each business type.

Field Manager

The field manager is responsible for storing the values of all managed fields for each object instance. Each BusinessBase and ReadOnlyBase object contains an instance of FieldDataManager, which is the object responsible for storing the managed field values. These two base classes expose the FieldDataManager object as a protected property named FieldManager.

FieldManager Property

The BusinessBase and ReadOnlyBase classes expose a protected property named FieldManager to make the FieldDataManager available to the business object's code. For example, this code is in BusinessBase:

protected FieldManager.FieldDataManager FieldManager
  {
    get
    {
      if (_fieldManager == null)
      {
        _fieldManager = new FieldManager.FieldDataManager(this.GetType());
        UndoableBase.ResetChildEditLevel(
          _fieldManager, this.EditLevel, this.BindingEdit);
      }
      return _fieldManager;
    }
  }

This property is designed to only create an instance of the FieldDataManager on demand. The idea is that if your business class never uses any managed backing fields, no FieldDataManager object will be created. I chose to do this to minimize the overhead involved in creating a business object when managed fields aren't used.

This does complicate the use of the FieldManager property throughout the rest of BusinessBase and ReadOnlyBase. For example, BusinessBase includes this code:

public virtual bool IsDirty
  {
    get {
      return IsSelfDirty || (_fieldManager != null && FieldManager.IsDirty());
    }
  }

A parent object is considered dirty, or changed, if it or any of its child objects have been changed. To know if the object has been changed, the IsDirty property checks the FieldManager to find out if any managed fields or child objects have been changed. But before accessing the FieldManager property, it checks to see if the _fieldManager field is null. This prevents accidental creation of a FieldDataManager instance when there are no managed fields in the business object.

Getting back to the FieldManager property, you should see that a method called UndoableBase.ResetChildEditLevel() is called after the FieldDataManager instance is created. Technically, the FieldDataManager is a child object contained within the business object. Because it is a child object, its edit level for n-level undo must be kept in sync with the business object itself.

I'll discuss the concept of edit levels in Chapter 11. For now, it is enough to know that the ResetChildEditLevel() call ensures that the new child object is in sync with its parent.

FieldDataManager Class

The FieldDataManager class itself is relatively complex. Each instance of this class is a child of a business object. Also, because the field manager is responsible for storing the values of the business object's properties, it must participate in the n-level undo process discussed in Chapter 11. Here's the declaration of the class:

[Serializable()]
  public class FieldDataManager : IUndoableObject, IMobileObject

The class is Serializable, because the data it contains may be serialized when the business object is cloned or moved across the network between a client and application server. It implements the IUndoableObject interface because it must participate in the n-level undo behaviors covered in Chapter 11.

Note

The IMobileObject interface exists to support serialization through the MobileFormatter, which is part of CSLA .NET for Silverlight. CSLA .NET for Silverlight is outside the scope of this book, and IMobileObject has no impact on how CSLA .NET works within the .NET runtime.

The field manager's primary job is to maintain the values of all properties that use managed backing fields. Simplistically, it might seem that you could store these values in a Dictionary, keyed off the property name. That would work technically, but accessing elements in a Dictionary turns out to be a relatively slow operation.

Note

My first implementation of the field manager did use a Dictionary, but the performance was too poor, so I shifted to the implementation I'm discussing here to address the issue.

Instead, the field values are maintained in an array of IFieldData objects.

private IFieldData[] _fieldData;

I'll discuss the IFieldData interface later. For now, I want to discuss how the property values are indexed into this array.

Generating a Consolidated Property List

What algorithm is used to set and get property values from this array in a meaningful manner? In short, each property is assigned a numeric index value between 0 and the number of properties registered for the business object type. Assigning these index values is the challenge, and it is complicated by inheritance.

Earlier in the chapter, I discussed the PropertyInfoManager and how it maintains a list of IPropertyInfo objects for each business object type. Remember that a business object type might be a subclass of some other type. It turns out that any level in the inheritance hierarchy might declare a property and register it by calling RegisterProperty().

This means that to get a consolidated list of all properties declared by a business object, it is necessary to walk through all the types in the inheritance hierarchy, getting the list of IPropertyInfo objects for each of the types. Obviously, that process could be relatively expensive, so it is done only once and the result is cached. FieldDataManager includes a GetConsolidatedList() method that retrieves the consolidated list of properties if it has already been generated, or calls CreateConsolidatedList() to create the list.

The CreateConsolidatedList() method is the interesting part of this process, because it assembles the consolidated list and assigns the numeric index values. Here is the method from the FieldDataManager class:

private static List<IPropertyInfo> CreateConsolidatedList(Type type)
  {
    List<IPropertyInfo> result = new List<IPropertyInfo>();
    // get inheritance hierarchy
    Type current = type;
    List<Type> hierarchy = new List<Type>();
    do
    {
      hierarchy.Add(current);
      current = current.BaseType;
    } while (current != null && !current.Equals(typeof(BusinessBase)));
    // walk from top to bottom to build consolidated list
    for (int index = hierarchy.Count - 1; index >= 0; index—)
      result.AddRange(
        PropertyInfoManager.GetPropertyListCache(hierarchy[index]));
    // set Index properties on all unindexed PropertyInfo objects
    int max = −1;
    foreach (var item in result)
    {
      if (item.Index == −1)
      {
        max++;
        item.Index = max;
      }
      else
      {
        max = item.Index;
      }
    }
    // return consolidated list
    return result;
  }

There's a lot going on here, so let's break it down. The first step is to get a list of all the types in this object's inheritance hierarchy.

Type current = type;
      List<Type> hierarchy = new List<Type>();
      do
      {
        hierarchy.Add(current);
        current = current.BaseType;
      } while (current != null && !current.Equals(typeof(BusinessBase)));

Since the FieldDataManager and the RegisterProperty() methods are declared in the BusinessBase class, there's no sense going any higher than that class. The result of this code is that the hierarchy field has a list of the types in this inheritance hierarchy, with the top-most base class being the last item in the list.

The next step is to loop through all those types, getting the list of any registered properties for each type. This is done from top to bottom, so the deepest base class is processed first.

for (int index = hierarchy.Count - 1; index >= 0; index—)
        result.AddRange(
          PropertyInfoManager.GetPropertyListCache(hierarchy[index]));

Remember that the registered properties for each type are stored in sorted order, so this algorithm guarantees that you end up with a consolidated list in the result field, where the IPropertyInfo objects are sorted within each type, and where the list starts with the deepest base type and moves out to end with the actual business object type.

Since the order is known and consistent in all cases, it is then possible to loop through all the IPropertyInfo objects and assign them a numeric index value, starting at 0 and counting up.

int max = −1;
    foreach (var item in result)
    {
      if (item.Index == −1)
      {
        max++;
        item.Index = max;
      }
      else
      {
        max = item.Index;
      }
    }

Of course, the value is only set if it hasn't been set to start with. In the PropertyInfo class, the index value is initialized to -1, and this loop only changes the value if it is still set to that initial default. This is important, because a given base class could be the base class for numerous business classes, and the index values for that base class should only be set once.

The end result of this work is that there's a consolidated list of all registered properties for the business object type, that those properties are in a consistent order, and that each IPropertyInfo object has a unique numeric index value that you can use to index into the array of IFieldData objects where the actual object's field data is stored.

IFieldData and IFieldData<T> Interfaces

Each field value is stored in an object that implements the IFieldData interface. In other words, each field value is stored in an object that maintains the field value, along with some metadata about the field value.

That interface is declared like this:

public interface IFieldData : ITrackStatus
{
  string Name { get; }
  object Value { get; set; }
  void MarkClean();
}

This interface ensures that each field is stored in an object that exposes the name of the field and the field's value and allows the field to be marked as being unchanged.

This interface inherits from ITrackStatus, which I'll discuss in Chapter 6 and will cover in depth in Chapter 8. The result is that any IFieldData is guaranteed to expose status tracking properties such as IsDirty and IsValid.

There's also a generic version of the interface.

public interface IFieldData<T> : IFieldData
{
  new T Value { get; set; }
}

This generic interface simply extends the interface with a strongly typed Value property that replaces the loosely typed one.

The reason for the generic interface is performance. Most field values are value types, such as int, float, and DateTime. If you store such values in a field of type object, .NET will do what is called boxing and unboxing, meaning that the value will be converted into and out of being an object rather than being a simple value type.

The field manager always attempts to store values in an IFieldData<T> first, only falling back to an IFieldData if necessary. This helps avoid the cost of boxing and unboxing value types.

FieldData<T> Class

Now that you've seen the IFieldData and IFieldData<T> interfaces, it should come as no surprise that the framework includes a default implementation. Only IFieldData<T> is implemented in the framework, because the field manager always stores values using a strongly typed approach.

The primary purpose of the FieldData class is to store a field value, along with important metadata about that field. In particular, it also stores the field's name, type, and a flag indicating whether the field has been changed.

The implementation provided by FieldData is designed for performance over advanced functionality. For example, it determines whether a field has been changed by maintaining a simple flag that is set to true any time the value is changed.

public virtual T Value
  {
    get
    {
      return _data;
    }
    set
    {
      _data = value;
      _isDirty = true;
    }
  }

Even if the field is reset later to its original value, it is considered to have changed. While this is somewhat simplistic, it is also fast and minimizes resource overhead. The primary alternative would be for the object to maintain a copy of the field's original value so it can compare any new value to that original value to decide whether IsDirty should return true or false. Obviously, that would double the amount of memory required by each FieldData object and would double the amount of data transferred across the network in client/server scenarios.

Note

As an extensibility point, it is possible to create your own implementations of IFieldData or IFieldData<T> to replace the default behaviors shown here. For example, you might do this if you want to store the original value of a field and replace the default IsDirty behavior to compare against that value.

You should now understand that the FieldDataManager maintains a list of field values in an array of IFieldData, where the objects are of type FieldData<T>. To conclude this topic, I want to discuss how framework classes retrieve and set the field values.

Getting Field Values

The FieldDataManager class exposes a GetFieldData() method that other framework classes can use to retrieve field values.

internal IFieldData GetFieldData(IPropertyInfo prop)
  {
    try
    {
      return _fieldData[prop.Index];
    }
    catch (IndexOutOfRangeException ex)
    {
      throw new InvalidOperationException(Resources.PropertyNotRegistered, ex);
    }
  }

This method simply uses the index from the IPropertyInfo parameter to find and return the IFieldData object from the array.

The interesting part of this method is the exception handling. Notice how any IndexOutOfRangeException is converted into the more useful InvalidOperationException, with the default message text of "One or more properties are not registered for this type." The most common issue people face when using managed fields is that they register the property incorrectly. The normal result would be an unintuitive IndexOutOfRangeException, so this code ensures that the business developer will get a more useful exception and message.

A field value is retrieved because BusinessBase or ReadOnlyBase needs the value. This means that GetFieldData() is invoked from a GetProperty() or ReadProperty() method in one of those classes. For example, here's the ReadProperty() method in BusinessBase, with the call to GetFieldData() and related code highlighted:

protected P ReadProperty<P>(PropertyInfo<P> propertyInfo)
    {
      P result = default(P);
    FieldManager.IFieldData data = FieldManager.GetFieldData(propertyInfo);
    if (data != null)
    {
      FieldManager.IFieldData<P> fd = data as FieldManager.IFieldData<P>;
      if (fd != null)
        result = fd.Value;
      else
        result = (P)data.Value;
    }
      else
      {
        result = propertyInfo.DefaultValue;
        FieldManager.LoadFieldData<P>(propertyInfo, result);
      }
      return result;
    }

The GetFieldData() method is called to get the IFieldData object from the field manager. Assuming there is a corresponding field, the code then attempts to cast the result to an IFieldData<P> to use the strongly typed interface.

FieldManager.IFieldData<P> fd = data as FieldManager.IFieldData<P>;
        if (fd != null)
          result = fd.Value;

This is always the case when using the default FieldData<T> type provided by CSLA .NET, so the field value will be returned without boxing or unboxing.

If a person has implemented his own IFieldData that doesn't use the generic interface, then the boxed value must be converted to the right type and returned.

else
          result = (P)data.Value;

BusinessBase and ReadOnlyBase use this technique when they need to retrieve managed field values.

Setting Field Values

FieldDataManager has two SetFieldData() methods: one generic and one not. The default is to use the generic overload, which looks like this:

internal void SetFieldData<P>(IPropertyInfo prop, P value)
  {
    var field = GetOrCreateFieldData(prop);
    var fd = field as IFieldData<P>;
    if (fd != null)
      fd.Value = value;
    else
      field.Value = value;
  }

This code retrieves (or creates) the IFieldData object from the array, then attempts to cast it to an IFieldData<T>. Normally this will succeed, because the storage object is a FieldData<T>. It's then possible to store the field value without boxing or unboxing.

The other overload is loosely typed, so it would incur boxing and unboxing costs. Even more, it will do type coercion if needed.

internal void SetFieldData(IPropertyInfo prop, object value)
  {
    Type valueType;
    if (value != null)
      valueType = value.GetType();
    else
      valueType = prop.Type;
    value = Utilities.CoerceValue(prop.Type, valueType, null, value);
    var field = GetOrCreateFieldData(prop);
    field.Value = value;
  }

This overload accepts the input value, then uses the CoerceValue() method to convert the value to the type expected by the field. Normally, you would expect that the inbound value is the same type (in which case CoerceValue() would do no work), but it is possible for a business object author to provide a value of some other type. This helps ensure that the value is converted to the right type before it is stored.

Only BusinessBase supports read-write properties, so only BusinessBase calls these methods. For example, here's the LoadProperty() method from BusinessBase:

protected void LoadProperty<P>(PropertyInfo<P> propertyInfo, P newValue)
  {
    try
    {
      P oldValue = default(P);
      var fieldData = FieldManager.GetFieldData(propertyInfo);
      if (fieldData == null)
      {
        oldValue = propertyInfo.DefaultValue;
        fieldData = FieldManager.LoadFieldData<P>(propertyInfo, oldValue);
      }
      else
      {
        var fd = fieldData as FieldManager.IFieldData<P>;
        if (fd != null)
          oldValue = fd.Value;
        else
          oldValue = (P)fieldData.Value;
      }
      LoadPropertyValue<P>(propertyInfo, oldValue, newValue, false);
    }
    catch (Exception ex)
    {
      throw new PropertyLoadException(
        string.Format(Properties.Resources.PropertyLoadException,
                                propertyInfo.Name, ex.Message));
    }
  }

The majority of this method is centered around retrieving the value from the field manager. It calls GetFieldData() to get the IFieldData object, and then it sets the oldValue field either to some default value or to the value retrieved from the field manager.

All that work is in preparation for a call to LoadPropertyValue(), which does the real work. The complex LoadPropertyValue() method implements the flowchart shown earlier in Figure 7-2. You can look at the full code from the Source Code/Download area of the Apress website (www.apress.com). The key thing to recognize is that it ultimately calls the SetFieldData() method from the field manager:

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

Throughout the entire process, the input value is strongly typed, including during the call to the field manager and storage in the FieldData<T> object.

You may be wondering, then, where that non-generic SetFieldData() overload is invoked. It is invoked from a non-generic overload of SetProperty() implemented in BusinessBase.

protected void SetProperty(IPropertyInfo propertyInfo, object newValue)
  {
    FieldManager.SetFieldData(propertyInfo, newValue);
  }

This overload of SetProperty() exists to support scenarios in which the business developer is loading the fields of the object from an external data source, such as an XML file or database. In that case, the developer might be looping through all the registered properties, loading the field data for each one, and would be unable to use a generic method in that case. Additionally, the input values may or may not be an exact match for the type of the field, which is why it is so important that the SetFieldData() method call CoerceValue() to convert the value to the right type.

At this point, you should have an understanding of how managed backing fields are implemented, including their storage in IFieldData objects, which are managed by the FieldDataManager and consumed in BusinessBase and ReadOnlyBase.

Conclusion

In this chapter, I discussed the options available for declaring and working with properties on your editable and read-only business objects. You should now understand how the framework handles both private backing fields and managed backing fields.

Chapters 8-16 will continue the implementation discussion by providing more detail about the implementation of each major framework feature. From Chapter 17 on, the focus will be on building the simple business application designed in Chapter 3, to illustrate how you can use the classes in the framework 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