Let me first say that for the most part I am a fan of CSLA. That being said there are some things about the framework that I just really do not like. One of my biggest complaints is that there is no framework support for gathering all of the broken rules in a given object graph. Imagine a parent business object named Customer that has a child collection of invoices.
Imagine you have a user story the requires you to edit a customers invoices through the customer object. A perfectly valid scenario by the way. What you would probably do is create a view that loads a customer by its Id along with the its collection of invoices. Now if you were to say, change some data in a specific Invoice that resulted in a business rule breakage, both the Customer, InvoiceCollection, and the actual Invoice who’s rule your broke would be marked invalid. Because the object graph is now invalid saving the customer object is not possible and rightly so! However the problem arises when you attempt to tell the user what went wrong. The UI of course has access to the BrokenRulesCollection. See below.
Customer.BrokenRulesCollection.ToString();
Sadly however, the BrokenRulesCollection property will only return the broken rules of the Customer object. In our example the Customer has no broken rules! The broken rule(s) are in the child object(s). And there is no framework level support to get them and indicate to the user what went wrong!
So what are you left with?
Reflection?
Framework extension?
Both of these options require some amount of time and planning, and depending on your implementation there is no guarantee of either soultions working with the next version of CSLA.
My Real Gripe
I like to write code, so for me adding this behavior this is not so bad. In fact it is kind of fun. My real issue is a tad more philosophical. If CLSA is going to essentially force you to use the Parent/Child model. If CSLA is going to make framework level decisions based on the specific use of said model. If CSLA is going to prevent the Save() and Delete() methods from executing based on this model. CSLA should supply a simple framework supported way to get an enumerated list of broken rules. Now there may be several perfectly valid reason for why Rocky leaves this out. In fact he has addressed this very question many times on the CSLA forum. However, at the very least give us a method that will do the reflection for us and update this method each time the CSLA framework changes so we don’t have to. After all Rocky is not shy about using reflection. Gripes aside CSLA does not provide this and we need it. So what do we do?
Options
As I see it there are really two approaches. The first is to slide new base class between your CSLA objects and the CSLA framework base classes. This base class can provide a method that will do the needed reflection when asked. This method would have to reflect its fields and recursively traverse the object graph collecting all broken rules in each of its children. This is a very typical approach that I have used with some success on a few projects. Another option is to write an extension method on BusinessBase. The work is essentially the same however this is nice alternative to creating your own set of base classes. Let see how that might look.
/// /// Gets all broken rules of the passed in object and all of its child objects. /// /// The business object. /// public static string GetBrokenRuleString(this BusinessBase businessObject) { //Precondition Check if (businessObject == null) { throw new ArgumentNullException("businessObject", "Parameter data cannot be null."); } string errorMessage = string.Empty; IDataErrorInfo errorInfo; //retrun if valid if (businessObject.IsValid || !businessObject.IsDirty) { return errorMessage; } //Get root rules if (!businessObject.IsSelfValid) { errorInfo = businessObject; errorMessage += errorInfo.Error; } //Lets look at children Type businessObjectType = businessObject.GetType(); PropertyInfo property = businessObjectType.GetProperty("FieldManager", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); FieldDataManager fieldDataManager = (FieldDataManager)property.GetValue(businessObject, null); List children = fieldDataManager.GetChildren(); //Call on children for (int i = 0; i < children.Count; i++) { BusinessBase child = children[i] as BusinessBase; if (child != null) { if (child.IsDirty) { if (string.IsNullOrEmpty(errorMessage)) { errorMessage += GetBrokenRuleString(child); } else { errorMessage += string.Concat(Environment.NewLine, GetBrokenRuleString(child)); } } } else { if (children[i].GetType().BaseType.IsGenericType) { IExtendedBindingList childCollection = (IExtendedBindingList)children[i]; foreach (BusinessBase businessBase in childCollection) { if (businessBase.IsDirty) { if (string.IsNullOrEmpty(errorMessage)) { errorMessage += GetBrokenRuleString(businessBase); } else { errorMessage += string.Concat(Environment.NewLine, GetBrokenRuleString(businessBase)); } } } } } } return errorMessage; }
And that’s all folks.
Enjoy!