The Caffeine Model Project
The Caffeine Model is a small framework designed to provide a view model base class and relating infrastructure that can rapidly add robust support for otherwise difficult to implement features in a typical MVVM project. Using
the framework is simple and fairly intuitive. There are only two real requirements that you must satisfy to use it: All view models must inherit from the ViewModel base class and all traceable properties must be registered as ViewModelProperty<T>
or ViewModelCollection<T>. Using the framework instantly gives you the following capabilities...
1) View model change tracking: The ability to track any changes that are made directly against a view model. The ViewModel base class provides an IsDirty flag. This is a stateful flag,
meaning that retrieving its value requires no work. The ViewModel and ViewModelProperty<T> classes are tightly integrated with one another. The ViewModelProperty<T> type is used to abstract a value that is monitored. Whenever
the value changes, the ViewModelProperty<T> updates the IsDirty flag of its parent ViewModel.
2) View model graph change tracking: The ability to track any changes on any child view model at any depth. By using the ViewModel and ViewModelProperty<T> types to create view models and register
traceable view model properties, respectively, you can instantly receive notification that a child somewhere in your object graph has been changed. The ViewModel base class has a stateful IsDirtyWithin property which signals that a property of a child
view model (at any level in the view model graph) has been changed. Just like IsDirty, this too is a stateful value. As properties are changed, the IsDirtyWithin value propagates up the view model graph until it reaches the top of the graph.
This is an important edition to the change recognition system as it allows you to discern changes made directly against a view model versus changes made to its children. Once the IsDirtyWithin flag is set to true, how do know what's changed?
The ViewModelExtensions class provides extension methods that allow you to traverse the view model graph both up and down based on view model state. 'View model state' means a view model's state with respect to the view model graph, which is: Added,
Removed or Committed. You can easily retrieve all added and removed view model children with a call like this: myViewModel.GetChildren(ViewModelState.Added | ViewModelState.Removed);
3) View model validation: In addition to the change recognition IsDirty and IsDirtyWithin flags, there is also a pair of IsInvalid and IsInvalidWithin flags that operate in a very similar fashion.
Validation is done per property. It does not integrate with the validation application block, however ViewModelProperty<T> does support IDataErrorInfo. 'Valid' is a relative term that can obviously change depending on circumstances.
Property validation can also become complex and involve considering more than just the property's value itself (for example, a phone number's area code must be valid for a zip code entered). To accommodate for these more contrived situations, the ViewModelProperty<T>
has a ValidationMethod property of type Func<T, ViewModelProperty<T>, List<String>, Boolean> that accepts a delegate responsible for validating the property. The delegate returns whether or not the property is valid and
optionally a list of errors if necessary. Performing validation like this at the property level can be overly verbose for simple validation, which is why the ValidationHelper class was created to ease the creation of these delegates.
For example, to create a validation method that bounds checks a ViewModelProperty<DateTime> property, you would simply use ValidationHelper.GetValueTypeValidationMethod(false, DateTime.Now.AddDays(-1), DateTime.Now.AddDays(1)). The first parameter
(which is passed in as false) is the ignoreCommitted parameter. A lot of times when building large enterprise applications, validation requirements change. However if a user opens a data form and only wants to persist one item, they shouldn't be
forced to update the entire form. For this reason, every method on the ValidationHelper class has an ignoreCommitted parameter as the first parameter. If set to true, validation will not happen for the property if it is already committed.
4) Commit and Reset: The ViewModel class comes with Commit and Reset methods that either commit all ViewModelProperty<T> properties or resets them all to their last committed value, respectively.
Parallel to these methods are the ViewModelExtension methods CommitGraph and ResetGraph which do the same thing for every view model in the object graph starting with the view model they're called against.
5) Graph traversal: The Caffeine Model also has robust support for traversing a view model graph. Graph traversal is exposed by the ViewModelExtension class. To go up the graph, use GetParent.
GetParent allows you to move up to a direct parent, or to a parent of a specific type at any level. GetChildren allows you to recurse through all child view models. You can specify a certain state a view model must be in to be returned, the maximum
search depth, or the name a view model must have.
6) Deep copy: The Caffeine Model base types are all serializable. As long as your view model subtypes and their properties are adorned with SerializableAttribute, you can use the ViewModelExtension.CloneGraph<T>()
method which will create a bit for bit copy of the entire view model graph. This was introduced to support both a background auto-save feature where user work is semi-frequently captured and pushed to disk in case of a crash and a changeset feature where
entire changesets of data can be persisted for later retrieval.
These are the main features supported by the current release of the Caffeine Model. I created this framework to use in my next WPF contract and it's just me working on it at the moment, so I can't promise changes will happen quickly. I
unit tested the framework extensively, however the tests are cluttered and do not do a good job of communicating intended use. As far as the tests are concerned I have to focus on code coverage for the moment. I'll try to create a demonstration
application soon that illustrates the framework at play and a few quick starts. I'll also try to clean up the unit tests in the near future. Let me know what you think! While I made every effort to vet the framework in a black box manner,
please be mindful this is still in alpha and I have not personally used it on the job.