CSLA 4.x Validation across business objects

CSLA 4.x Validation across business objects

Old forum URL: forums.lhotka.net/forums/t/11340.aspx


jasona22 posted on Monday, April 30, 2012

I am still a noob when it comes to the new version of validation.  I have written a custom validation class and got that working; however I am no pro at this.  That said, there is likely some key term I am not using when searching for what I am trying to do; thus I have not been able to find via forum searches the answer to the following:

How do I execute validation between two objects?  

An example might be that I have an aircraft flight record form.  A person enters flight information post flight.  Along with fields one might guess exist, there are the Departure Date/Time and Arrival Date/Time.  I need to verify that those date times do not overlap with a prior flight by the same plane.  So the validation check needs to verify that the Departure Date/Time is after the previous flight object for this aircrafts Arrival Date/Time plus the "TurnOver" objects elapsed time ( refueling, unloading/loading of passengers, baggage, etc).

One version we have tried in older versions of CSLA was the command object; however, when the two objects are validating against a property bound to a text box, every keystroke is a call to the database and it causes a rather chatty application. 

Has anyone solved a similar problem I can learn from?  

5th grader speak will be appreciated.

Thanks

 

 

JonnyBee replied on Tuesday, May 01, 2012

I'd rather rephrase that question into "how do you validate properties against data in a database".
I assume that you have a rich client (WindowsForms or WPF/SL)?

Validation across business objects may rather be viewed as "how do i validate a child object that depends on values in a parent/grandparent/child..?".

I use these guidelines for myself:

  1. Update BO when you want business rules to execute. For dates I usually prefer OnValidation over OnPropertyChanged. 
  2. Use "sync" format rules at priority 0 to check the input. Any broken rules  (Error) at priority 0 will prevent rules with higher priority to execute.
  3. Add an async business rule that use an async DataPortal call on a CommandObject or ReadOnlyObject to get values from database with Priority > 0. 
  4. If necessary - consider some cache strategy in CommandObject/ReadOnlyObject to prevent too many calls to the database.

For CSLA 4.2 and higher you may also specify that the sync rule is only allowed to run on the client when user edits the property. Make your asyn rule inherit from either PropertyRule og CommonRules.CommonBusinessRule and set

                CanRunAsAffectedProperty = false;       // as affected property of another rule
                CanRunInCheckRules = false;               // in CheckRules
                CanRunOnServer = false;                        // in logical server side, ie: Data Access Layer.

 

jasona22 replied on Tuesday, May 01, 2012

Thank you for your reply. Our product is using WPF and MEF.  CSLA driven middle layer using CodeSmith generation.

I will have to research OnValidation vs OnPropertyChanged.  I've really not done much with validation in a while; my focus has been generation for the past year.  Other than a few simple required, max/min checks and creating a date compare custom rule several months ago, I've not done anything regarding 4.x validation.  And the custom rule was pretty simple as I copied a pattern and made my part work.

Most of what you said in your response is foriegn to me.  However, what I think I heard out of it is that you cannot validate across business objects directly.  Basically I need to retrieve the data directly from the database at the time of validation (whether direct query or readonly CSLA object seems to be up to me).   Additionally, the scenario I am trying to solve does not have parent/child/grandchild type relationships.  More of sibling or even disperate objects.  However, it seems that isn't important since I am going to have to fetch it from the database anyhow.

Thanks

 

tmg4340 replied on Tuesday, May 01, 2012

Depending on your particular use case, you may not need to worry about cross-object validation.

Since you say you have to pull the data "directly from the database", why not simply pull the prior flight's information as part of your business object?  Unless there's a presumption that both will be worked on simultaneously (which makes the whole thing not worth doing), the data you need should already exist in the database and be static.  So add them as properties to your business object, or create read-only child objects you hang directly off your main BO.  Then your validation code doesn't have to reach outside the BO.

If data staleness is an issue, then you can re-check/refresh on the server when you try to save (where the cost of an additional database call would likely be pretty minimal).  This is something you'll potentially have to do anyway...

HTH

- Scott

jasona22 replied on Thursday, May 31, 2012

Scott, this is the approach -- sort of -- that we took.  We created a private class within the BO.  The "validator" (private class mentioned) is responsible for getting data from the database once on creating an editable BO.  Then, on save of the BO, the validator refreshes its data once again to ensure any stale situations are covered.  Here is an example of usage.

BusinessRules.AddRule(New DateComparer (_flightStart, validator.PreviousFlightEnd, Operators.LessThan)

In the example, PropertyInfo's are the data types of _flightStart and PreviousFlightEnd.  This actually errors on index out of range during rule execution. 

What I speculate is happening and I am writing this to see if I can get confirmation/suggestions is that CSLA is adding the PropertyInfo values to a collection for the object.  So the _flightStart PropertyInfo is added to a collection on the "Flight" BO and the PreviousFlightEnd PropertyInfo is added to a collection for the Validator object.  So when the Execute occurs and the validator.PreviousFlightEnd is called for the "Flight" business object, it fails because that PropertyInfo is not associated with the Flight BO, its associated to the Validator.

I would prefer to not have a large number of "for validaton purposes only" properties all over my BO and thus they are encapsulated; however, that isn't working.  The reason we are doing this is to 1) encapsulate our properties and to 2) not make a database call on each key press the user makes as the property changes.  I think CSLA needs to better address cross object validation.  It seems it may have been overlooked a little.

Thanks for any suggestions.

 

JonnyBee replied on Thursday, May 31, 2012

Hi,

The rule engine only support InputProperties from the one and same business object instance.

Any other requirement must be handled by YOUR code in the Execute method or by using Lambda's.

You can f.ex

I do not think there is an easy way to support this in the rule engine.

So for your case I would either

Or why not use a Lambda rule itself (and the Lambda rule extensions)?

BusinessRules.AddRule<Root>(FlightStartProperty, o => o.FlightStart > o.validator.PreviousFlightEnd"{0} must be later than previous flight start");

The Lambda may also be a static bool function in your code or multiple statements so long as it returns a bool (false = broken).

jasona22 replied on Thursday, May 31, 2012

I will have to research what you have said some.  I am not familiar with many of the techniques you suggested. 

 

jasona22 replied on Wednesday, June 06, 2012

I'm back.  So, we created a rule using the Lamda expression pretty much just like what Johnny Bee wrote on the second to the last line above.  However, under no condition can we get the rule to fail.  We even went as far as setting the comparison to false explicitly and no broken rules.   We put break points on the Lamda and see the property values, see that the expression is false (or true sometimes) and the result is never anything but true.  Examples: 

BusinessRules.AddRule(Of Flights)(_flightStartProperty, Function(p as Flights)(p.FlightStartProperty > p.validator.PreviousFlightEnd, ""))

BusinessRules.AddRule(Of Flights)(_flightStartProperty, Function(p as Flights)(False, ""))

Neither of these scenarios fails the rule; BrokenRulesCollection.Count is always 0. 

We wrote a more tradtional rule that does fail and updates the collection.

Any help would be greatly appreciated.

JonnyBee replied on Wednesday, June 06, 2012

You MUST set an error message!!!!

An empty strong is poor explanation to your users. 

jasona22 replied on Wednesday, June 06, 2012

While I agree an empty string is not good for an actual error, we are just trying to get it working at the moment.  If it is a MUST then perhaps a usable error should be thrown when empty string is passed instead of just swallowing the rule and returning true.

 

 

JonnyBee replied on Wednesday, June 06, 2012

An Empty message is the same as "Success".

This is the code from RuleResult:

    public RuleResult(string ruleName, Core.IPropertyInfo property, string description)
    {
      RuleName = ruleName;
      PrimaryProperty = property;
      Description = description;
      Success = string.IsNullOrEmpty(description);
      Severity = Success ? RuleSeverity.Success : RuleSeverity.Error;
    }

This is a requirement in DataBinding and the IDataErrorInfo interface.

http://msdn.microsoft.com/en-us/library/system.componentmodel.idataerrorinfo.error

If Error return string.Empty then there is no Error on that field!!! So for data biding to work there MUST be an Error message to the user.

 

 

 

 

jasona22 replied on Thursday, June 07, 2012

Speaking to an associate here and thinking on this some over night I would like assert that it isn't CSLA's scope to dictate usage for error messages.  

The link you provided to Microsoft does not indicate that a message is required; only that the default is empty string.  The below statement, quoted from the link link you provided, is written to state that there is an error in the object and that this method retreives a message.  Nothing about this message implies a change to the error condition of the object if the message is still empty.

"Gets an error message indicating what is wrong with this object"

It is my position that the result of validation should ONLY be based on the rule defined and not the rule AND its message.  An empty message and a failed rule should still return a failed rule with no message.  How does message testing (what one does if an empty message indicates pass) have anything to do with rule checking?  CSLA has no way of knowing what I as a developer want or need to provide my users. 

It is a separation of concerns issue.  Messages to the user are MY concern and not CSLA's.  CSLA provides a nice mechanism to pass a message through to the user, but if I choose not to, that is up to me. 

I do appreciate your time in this discussion.  We are able to move along; however I just wanted to provide my thoughts and some of those from a fellow associate.

tmg4340 replied on Thursday, June 07, 2012

That's fine - but in this case having a message is a requirement.

Look at the IDataErrorInfo interface - it only has properties that return string values.  How else is the data-binding/validation infrastructure in .NET supposed to know whether the instance in question is valid?  The IDataErrorInfo interface (and, indeed, almost all of the validation interfaces built into .NET) use this very convention - if there is an error, you must provide a message.  If there is no error, then you must provide no message.

If you would prefer that CSLA not require you to provide an error message, fine.  But in order for .NET's validation infrastructure to work, CSLA will have to provide a message of some kind if you don't.  So you would then end up with some sort of default message, which would be equally as unhelpful/unusable.

HTH

- Scott

jasona22 replied on Thursday, June 07, 2012

I suppose.  I am not meaning to be argumentative.  It just seems flawed.  To me IDataErrorInfo is an interface providing info about an error.  The error and the message are decoupled and the interface just provides information.  The error occurs regardless of the message -- such are the ways of pesky little errors. 

However, the CSLA usage seems to tightly couple the error and the message such that you can't have one without the other.  I don't see that same coupling in the Microsoft implementation.  Of course, error isn't exactly right with the CSLA as it is validation rules more than errors.

This kind of makes me think that all we need is a conditional Lamda string for a business rule even if the Lamda business rule passes True as the message seems to be boss.

This entire discussion is because of the several hours of debugging a Lamda business rule where the rule's logic showed failure but no failure ever occured and there was no available documentation to say that message is required (or not obvious in the reading material I have -- Using CSLA 4 Creating Business Objects ).  We hadn't gotten to worrying about a message yet and since there was no error saying we were using it wrong we spun our wheels for a while. 

 Anyhow, enough said on my part I suppose.  I now know what the issue was and will be sure to always have something in that field (even a blank space).  And while I always intended to have a message, I don't always formulate that early in the rule writing.  Sometimes I have to ponder how I want to inform the user of the situation. 

 Thanks again and keep up the good work.

Copyright (c) Marimer LLC