Setting SuppressRuleChecking to true should clear existing errors

Setting SuppressRuleChecking to true should clear existing errors

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


rweyrauch posted on Monday, May 17, 2010

In CSLA 3.6, I notice that if I set SuppressRuleChecking = false, the current list of errors remains, even if I call CheckRules() after that. I would have assumed that changing this setting to false would make all rules defacto "not in error" and so it should clear the current error list. But, that may not be quite enough - in order to get DataBinding to re-check the field, the framework needs to notify property changed on every property currently in error that is becoming "not in error" as a result of the supression. And, conversely, if all I am doing is turning the suppression off, a supsequent call to CheckRules() needs to notify on each property found to be in error.

Is this something that might be different in the current release of CSLA? Is there anyone out there that understands my request that can help me out? Basically, I have a BO that can go "offline" and when offline, it is not in any state of error. When the BO comes back online, I need to re-run the rules and notify changes because that is the only time DataBinding seems to come and ask for that properties errors.

RockfordLhotka replied on Monday, May 17, 2010

The scenario SuppressRuleChecking is designed to support is batch loading of an object - specifically it was added for web-based scenarios where you are handing a page postback or a web service call and you are loading data from the call into the object. It is designed for this sort of flow:

  1. Get the inbound data (from postback or service call)
  2. Create an instance of the business object
  3. Suppress rule checking
  4. Load all the properties of the business object
  5. Stop suppressing rule checking
  6. Call CheckRules() to invoke all rules

The reason for this feature is to prevent multiple runs of the rules - especially expensive ones - that might otherwise occur as each property is set. If you have dependent properties, many of your rules may end up running many times, which is great in a smart client scenario, but very inefficient in a batch load scenario like the web.

rweyrauch replied on Monday, May 17, 2010

That is all very well, but does not preclude the SuppressRuleChecking property from being usable in an Online/Offline mode as well. That is, they are two distinct features that can be accomplished via that one property. Additionally, I would guess that you might agree that a business object should be "in the appropriate state" after successfully setting one of its properties, no? And there is no restriction from setting SuppressRuleChecking = true any old time and just leaving it that way (albeit, I understand, not its intended purpose). So, if I can set the property and leave it, come back later and set it back and leave it, the business object should correctly reflect those changes in state, and in this case it can without losing any of the current functionality. I had used the Ocean Framework once and easily modified that framework to it. I am not as well versed in CSLA, but it looked on the surface that it should be easy in it as well.

rweyrauch replied on Monday, May 17, 2010

Here is a quick little app hacked to show the basic conditions. NOTE: I grabbed as little as possible from here and there to make this work - sorry if those parts and not pretty or perfect. You have to change the reference to the CSLA project - I did not upload that cause I figured I didn't need to.

What I would expect to happen, is for my error adorners to jump back and forth with the enabled TextBoxes. I believe there are two parts to making that happen. One, is to change the what  SuppressRuleChange does when changed and the other is that you need to fire (I believe) a NotifyPropertyChanged on each property when the BrokenRule is added and removed or else WPF will not come back and ask for the latest errors.

RockfordLhotka replied on Monday, May 17, 2010

I guess I wasn't clear - what I was saying in my previous post is that there's a specific scenario SuppressRuleChange is designed to address. I don't think you are in that scenario, so it doesn't surprise me that it isn't meeting your needs.

It might be better to step back and help me understand your needs to see if there's an existing solution.

rweyrauch replied on Monday, May 17, 2010

I understood your point and I appreciate that is what it is used for currently. Unfortunately, it is called SuppressRuleChange and not HoldUpErrorsWhileLoading, or just plain Loading. Given it's name, and its name being a useful property as named, the object should be in a state of suppressing all errors when it is set to TRUE (or better said, error free) and return to an appropriate error state when FALSE. That would make it do things like I need and still be totally useful for its current purpose.

My app shows how I intend on using it. In my app, I have objects I want to keep in memory, as the active DataContext, but that are waiting for external dependencies in order to be considered useful. While the object is waiting for these external dependencies, I do not want it to be in error. The sample app shows why, the IsEnabled of the controls (via the IsEnabled of their container in reality) is bound to the objects Online property (not really called that) and disabled controls should not be in error.

I guess you could make a point that there should be a ShortCircuit Rule for stopping further Rules checking per property. That is, a Rule that says if TRUE, the property is not in error and no more error checking is required. This would allow for internal states that say some portion of the object is not currently in use, although that is not my need or point at this time.

RockfordLhotka replied on Monday, May 17, 2010

I think the property name and xml comments are reasonably clear:

    /// <summary>
    /// Gets or sets a value indicating whether calling
    /// CheckRules should result in rule
    /// methods being invoked.
    /// </summary>
    /// <value>True to suppress all rule method invocation.</value>
    public bool SuppressRuleChecking

You are right - the correct answer, especially when you look forward to CSLA 4, is to implement a gate condition rule that stops most of your rules when your object's state is such that rules shouldn't be applied. That's how the rule system is supposed to work - rules should be expressed, literally, as rules.

rweyrauch replied on Tuesday, May 18, 2010

Ok, that almost seems to suggest that I should remove my rules, or disable them maybe, when they are not in effect, but there does not seem to be any way to do that. A quick test with my three custom Rules showed that: if Rules could be "turned off" (in my test I added very crude control property and they return TRUE if they are off), then the current system would work just fine. The rules all still get called, which is a little inefficent, but because they all returned true, everything is hunky-doory.

I think I can stand by my first observation: If I am suppressing all rule checking, then my object can not possibly be in error. And furthermore, to make sure everyone knows that, any currently reported errors should be cleared out.

One of the other things I have not looked into from a CSLA point of view is delaying the checking of errors until they attempt to save the object for the first time. This feature might be usefull in that instance as well, although it is complicated by software errors (like required filed missing). Leaving me wonder if adding a ValidationRules.MaximumPrioirty integer property would not be the best. As Validation is running the Rules, it would "act as if Rule returned TRUE" if the Rule.Priority is > than the current MaximumPrioirty. That might be self-explainitory but I can provide more info if not.

rweyrauch replied on Tuesday, May 18, 2010

A quick test showed that indeed that a new property of "public int MaxPrioirty = int.MaxValue;" added to ValidationRules gave me exactly what I wanted. I can now easily control the depth of Rule checking based on the state of the B.O. I also added these code fragments (I was not sure the best way to handle the aysnc ones):

              ruleResult = rule.Priority > MaxPriority ? true : rule.Invoke(_target);

and

      lock (SyncRoot)
      {
          if (rule.Priority > MaxPriority || e.Result)
          BrokenRulesList.Remove(rule);

 

 

RockfordLhotka replied on Tuesday, May 18, 2010

You need to use priorities and short-circuiting to stop rule processing. Create a pri -1 rule that acts as a gate rule, setting e.StopProcessing=true to stop further rules from running for that property.

rweyrauch replied on Tuesday, May 18, 2010

I thought e.StopProcessing is only used if the Rule returns false, which means that an error exists (Description should have a string in it - no?). Looking at the code though I see it should work either way. But does that mean I need one of these per property? It still will not cuase the PropertyChanged() notifications required. 

    /// <summary>
    /// Gets or sets a value indicating whether this
    /// broken rule should stop the processing of subsequent
    /// rules for this property.
    /// </summary>
    /// <value><see langword="true" /> if no further
    /// rules should be process for this property.</value>
    /// <remarks>
    /// Setting this property only has an effect if
    /// the rule method returns <see langword="false" />.
    /// </remarks>
    public bool StopProcessing

RockfordLhotka replied on Tuesday, May 18, 2010

At some point, and I don't remember the specific version, but it is in 3.8, the check rules behavior was changed to return a list of affected property names by the rule run, and PropertyChanged is raised for each property in that list (if your property changed mode is set to Xaml - the default is Windows).

Since you are doing WPF (right?), I assume you've changed the property changed mode to Xaml?

rweyrauch replied on Tuesday, May 18, 2010

I had meant to, but, had not - sorry for that. That solved my AddDependentProperty() rule now doing what I want it to but...

It seems that behavior is only in the per-property version of CheckRules(), the place I have been looking is in the one for re-checking all rules. That one does not collect property names of previously broken rules which are now not broken, and newly detected broken rules. After I change online states, I was calling the all rule version where, quite likely, all that has changed is the broken rules collection.

I have to say, I am really digging the MaxPriority concept. It provides a very nice control. And, more importantly, I very appreciate all of your assistance.

RockfordLhotka replied on Tuesday, May 18, 2010

After you call CheckRules(), you can also call OnUnknownPropertyChanged() to get the properties to refresh.

rweyrauch replied on Tuesday, May 18, 2010

One last caviate - it almost does everything that is needed. I forgot to remove all my try-it-to-see-what-effect-it-has code. After I reduced the demo to just the new functionality described above, I am left with on eof the problems I described earlier.

If the only thing that has changed is the error string for a bound property - that is, the properties value did not change but the error string did - then the framework MUST signal a PropertyChanged with the name of the property whos error changed. I know what to do for this but not sure I can add it easily to CSLA.

In the past I have ran a HashSet<string> to collect property names. I collected names for any property that changed value, and, the important part, names for any properties that adding OR REMOVING error states. Then after the work is done, I send a PropertyChanged() for each string in the HashSet. The goal is to only signal a given property once, even if it has changed value and error state, making sure to signal it at least once if any of these conditions happen.

Related to that, I was looking at ValidationRules.AddDependantProperty but it did not seem to setup what I was hoping for. I have dependant properties where I want the dependant property B to signal a PropertyChanged when ever property A does so. Right now, I am watching my own OnPropertyChanged()'s and raising the extra event for B when the property name of A comes in. Is there a better way to so join 2 properties?

Copyright (c) Marimer LLC