Using CSLA 4 02 Creating Business Objects
137 pages
Reference
Templates: https://github.com/MarimerLLC/csla/tree/master/Support/Templates/cs/Files
Chapter 1: Key Object Concepts (page 11)
Stereotypes
Stereotypes supported by CSLA.NET:
Stereotype | Description | Base Class |
---|---|---|
Editable root | Read/write properties; Retrieved/stored directly | BusinessBase<T> |
Editable child | Read/write properties; Retrieved/stored via parent | BusinessBase<T> |
Editable root list | List object containing editable child objects. List is retrieved/stored directory | BusinessListBase<T,C> BusinessBindingListBase<T,C> |
Editable child list | List is retrieved/stored via parent | BusinessListBase<T,C> BusinessBindingListBase<T,C> |
Dynamic root list | Editable root objects; Retrieved/stored directly | DynamicListBase<C> DynamicBindingListBase<C> |
Command | Executes a command on the server, reports back results | CommandBase<T> |
Unit of work | Combines operations against several objects | ReadOnlyBase<T> CommandBase<T> |
Read-only root | Read properties; Retrieved/stored directly | ReadOnlyBase<T> |
Read-only child | Read properties; Retrieved/stored via parent | ReadOnlyBase<T> |
Read-only root list | List object containing read-only child objects. List is retrieved/stored directory | ReadOnlyListBase<T,C> ReadOnlyBindingListBase<T,C> |
Read-only child list | List is retrieved/stored via parent | ReadOnlyListBase<T,C> ReadOnlyBindingListBase<T,C> |
Name/value list | Read name / value objects | NameValueListBase<K,V> |
Serializers
BinaryFormatter
(DEFAULT) andNetDataContractSerializer
(NDCS) (NOT RECOMMENDED) create true clonesMobileFormatter
clone like functionality for Silverlight, WP7 and .NETSerialization
attribute generally used on each and every objectDataContract
andDataMember
can also be used at a field level - NDCS only - NOT RECOMMENDED - cannot swap to another formatter and will miss out any fields not decoratedBinaryFormatter
is normally faster and more efficient, so shouldn’t be overriden. However theMobileFormatter
avoids use of reflection and so may work in medium trust environments where theBinaryFormatter
is disallowed.
Object Relationships
Relationships are important because the conceptual differences mean the code which is written to implement them is also different.
Relationship | Description | Example |
---|---|---|
Containment | Parent child relationship | an OrderEdit object contains a LineItems collection, which in turn contains LineItemEdit objects. This set of objects is an object graph. The object at the top of the graph is the root object. |
Using | Separate and independent objects which collaborate | LineItemEdit uses a TaxCalculator to calculate tax depending on country. |
Equality
Object equality can mean different things - actually the same object, or two different objects containing the same values. The Equals
method can be overriden, or the IEquatable
interface implemented.
Basic CSLA 4 Property Concepts
Properties can become complex. A getter
requires checking authorization, a setter
requires checking: authorization, is new value different to existing value, PropertyChanging
event, run rules, mark object as changed, PropertyChanged
event.
CSLA defines a standard way to declare properties and backing fields involving:
- metadata descriptions and
- helper methods.
Metadata
A static
PropertyInfo
field contains metadata about the property and is "registered" with the framework using the RegisterProperty
method:
public static readonly PropertyInfo<string> MyTextProperty = RegisterProperty<string>(c => c.MyText);
Declaring metadata in this way is a requirement for using CSLA features.
RegisterProperty
method
The RegisterProperty
method registers the property's metadata with the "field manager", the CSLA.NET property management subsystem (with various overloads). It creates an instance of IPropertyInfo
containing property metadata - which is returned to the class and can be stored in a static
field. It also registers the instance with a static
data structure maintained by the framework.
PropertyInfo<T>
and IPropertyInfo
The RegisterProperty
method either accepts a pre-created IPropertyInfo
(usually a PropertyInfo<T>
) or will create an instance for you. (PropertyInfo is strongly typed and therefore more efficient.)
The PropertyInfo<T>
stores the property metadata (hence can be static at the class level). Elements in IPropertyInfo
include: Name
, Type
, FriendlyName
, DefaultValue
(only use in value or immutable types since will be shared across all instances). The types can be extended to store extra metadata if required (advanced).
Helper Methods
Helper methods encapsulate authorisation, validation and so forth:
public string MyText
{
get { return GetProperty(MyTextProperty); }
set { SetProperty(MyTextProperty, value); } }
with the GetProperty
and SetProperty
methods behaving as outlined above.
Method | Description |
---|---|
GetProperty |
Gets a property, checking authorization rules |
ReadProperty |
Gets a property, no authorization checked |
SetProperty |
Sets a property, authorization, validation etc |
LoadProperty |
Simply sets a property, no auth, valiadtion, events raised etc |
GetPropertyConvert , ReadPropertyConvert , SetPropertyConvert , LoadPropertyConvert |
As above, additionally converting the new value to the correct backing field type |
Backing fields
CSLA 4 allows you to implement properties using a manual backing field, or a "managed" backing field. In the latter case the CSLA .NET base class manages the property’s value on your behalf. Performance is slightly worse using managed backing fields, but generally recommended unless there is a high performance requirement.
Types of Property Declaration
Different base objects implement properties differently e.g. BusinessBase
properties provide full read/write functionality; CriteriaBase
properties are lightweight
Declaration type | Property gets or sets |
---|---|
Read/write | Primitive or standard .NET type |
Read/write with conversion | Converts to and from the type of field containing the value |
Read-only | Non-public setter |
Read-only with conversion | Non-public setter with conversion |
Child | Containment, typically has a non-public setter |
Child with lazy loading | Only loaded or created on demand |
Inter-graph reference | Reference to another object in the same object graph (not child) |
Using reference | Reference to object OUTSIDE the object graph, no setter |
Manual backing fields | Includes read/write and read-only |
Read-Write
- The getter uses the
GetProperty
method, passing in the static IPropertyInfo metadata. If a user is NOT authorised the default value is returned (throwing exceptions will cause UIs to blow up - useCanReadProperty
to check first). - The setter uses
SetProperty
, taking the same metadata field and the new value. Throws aSecurityException
in this case if the user is not allowed to change the value - Child objects have to be taken into account when setting properties (more later)
SetProperty
invokes business rules for this property and any dependent property- Raises the
PropertyChanged
event to support data binding
Read-Write with No Rules
- Might need a field just to "store" a value, but with no rules.
- Required for certain base classes e.g.
CommandBase
. Here theReadProperty
andLoadProperty
methods are used instead. - Property itself can be private, but ensure the metadata is declared public if requiring full support from certain data access models e.g.
ObjectFactory
Read-Write with Value Conversion
- Common example: string property backed by an enum value (note, breaks localization) where a string representation is useful for the UI
- Various mechanisms allow a value of one type to be converted into another type. Conversion will succeed as long as one of those mechanisms works.
- To access the underlying backing field (e.g. the enum here) a private property of the same type (e.g. the enum) is the easiest approach. NOTE
RegisterProperty
is not required a second time (obviously).
public static readonly PropertyInfo<TestEnum> MyStringEnumProperty = RegisterProperty<TestEnum>(c => c.MyStringEnum);
public string MyStringEnum
{
get { return GetPropertyConvert<TestEnum, string>(MyStringEnumProperty); }
set { SetPropertyConvert<TestEnum, string>(MyStringEnumProperty, value); }
}
Read-Only (and Read-Only with Value Conversion)
- If authorisation is required for getting properties, these can be implemented using
GetProperty
andLoadProperty
with the setter private (assumption being that properties do not change over the lifetime of the object) - For base types which don't support authorisation rules, use
ReadProperty
instead
Child Object Reference
- Need to use an overload of
RegisterProperty
helper method to specify the relationship type asRelationshipTypes.Child
- For some situations, the child object will be read only, the property has a private setter and will be created when creating the parent object e.g. with code such as
Addresses = DataPortal.CreateChild<AddressEditList>();
Child Object Reference with Lazy Loading
- Create or load the child object in the getter
- Additionally specify the relationship type
RelationshipTypes.LazyLoad
e.g.
public static readonly PropertyInfo<AddressEditList> AddressesProperty = RegisterProperty<AddressEditList>(c => c.Addresses, RelationshipTypes.Child | RelationshipTypes.LazyLoad);
- The getter may be invoked on the client, but need to converse with the server i.e. data portal in order to load the object. Two methods:
- Directly use the data portal
- Use a seperate "child object creator" e.g.
AddressListCreator
class withResult
property which will be called from the getter (if not already loaded - check this usingFieldManager.FieldExists
)
Properties with Manual Backing Fields
Examples of where you might use manual backing fields include:
-
Inter-graph references
-
Using relationship references
-
Scenarios where you need to apply attributes to fields
-
High-performance scenarios where using managed backing fields is a performance bottleneck
-
Still register a static metadata field for the property, but specify the
RelationshipTypes.PrivateField
-
Initialise the private variable with the
MyProperty.DefaultValue
-
GetProperty
,SetProperty
have overloads to use the manual backing field for consistency -
No need for
ReadProperty
orLoadProperty
- just use the backing field - but loosely typed versions of these methods which can be used if required
Inter-Graph Reference
For example PersonEdit
has a list of child objects of type AddressList
, but also has a reference PrimaryAddress
. This would be an inter-graph reference. The difference is important for how n-level undo works - need to increase edit level for all objects in the object graph ONLY ONCE.
Inter-graph references must use a manual backing field AND must have the [NotUndoable]
attribute applied to that backing field. Since not a child object only the RelationshipTypes.PrivateField
is needed.
public static readonly PropertyInfo<AddressEdit> PrimaryAddressProperty = RegisterProperty<AddressEdit>(c => c.PrimaryAddress, RelationshipTypes.PrivateField);
[NotUndoable]
private AddressEdit _primaryAddress = PrimaryAddressProperty.DefaultValue; public AddressEdit PrimaryAddress
{
get { return GetProperty(PrimaryAddressProperty, _primaryAddress); }
set { SetProperty(PrimaryAddressProperty, ref _primaryAddress, value); }
}
Using Reference
A reference to an object OUTSIDE the object graph. Best way is NOT to keep a reference to this object. Either create a reference when needed, or get a reference and store only in a local variable.
For times when a reference is REQUIRED - this MUST be implemented using a manual backing field and decorated with the [NotUndoable] and [NonSerialized] attributes. Lazy loading of this reference MUST also be implemented - since after deserialization the property value will be null.
Non-Generic LoadProperty Method
Most examples above use the generic overloaded version of the LoadProperty method e.g. LoadProperty(AddressesProperty, value)
.
The non-generic overload is substantially slower than the generic overload, because the value types aren’t known at compile time. Only use if there’s no way to invoke the generic overload.
Method Declarations
Similarly to properties, declaration of methods include the declaration of a static metadata field as well as use of helper methods to check authorization.
public static readonly MethodInfo TestMethod = RegisterMethod(typeof(EditableProperties), "Test");
public void Test()
{
CanExecuteMethod(TestMethod, true); // method author must remember to add this line first
// do some work here
}
Metastate
CSLA objects maintain their own status. Following flags are available:
Property | Description | Stereotypes |
---|---|---|
IsNew |
Object's primary key value doesn't exist in data store | Editable root and child |
IsDeleted |
Object is marked for deletion (and on save will delete rather than update). Generally used for child objects. | Editable root and child |
IsChild |
Child object | Editable root and child, Editable list |
IsDirty |
Has been changed (object or child) | Editable root and child, Editable list |
IsSelfDirty |
Has been changed (this object only) | Editable root and child |
IsValid |
No broken rules (object or child) | Editable root and child, Editable list |
IsSelfValid |
No broken rules (this object only) | Editable root and child |
IsBusy |
Async operations in progress (object or child) | Editable root and child, Editable list |
IsSelfBusy |
Async operations in progress (this object only) | Editable root and child |
IsSavable |
IsDirty && IsValid && !IsBusy AND user is authorised |
Editable root and child, Editable list |
- It is possible to directly alter metastate
- It is possible to override how metastate works (make sure you know what you are doing)
- Various events are raised during an object's lifetime
Event | Description | Stereotypes |
---|---|---|
PropertyChanged |
A property has changed | Editable root and child |
PropertyChanging |
A property is changing | Editable root and child |
ListChanged |
The list or item in the list has changed (BindingList subclasses only) |
Editable list |
CollectionChanged |
The collection or item in the collection has changed (ObservableCollection subclasses only) |
Editable list |
ChildChanged |
An object in the graph has changed | Editable root and child, Editable list |
Saved |
The object graph has been saved | Editable root, Editable root list |
BusyChanged |
The objects busy status has changed | Editable root and child, Editable list |
Accessing Metastate
- via public properties directly from the object itself
- via
ITrackStatus
interface which all business objects implement (useful for polymorphism) - via
INotifyBusy
interface to access busy status as well asBusyChanged
events (useful for polymorphism) - via
INotifyChildChanged
interface forChildChanged
events (useful for polymorphism)
Note: metastate properties do NOT support data binding