Business Rule

Business Rule

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


Lazaro posted on Friday, July 01, 2011

We are working with CSLA 4 and we have an issue with the business rules. We want to modify/reset a property when another specific property is set (value changes), so I created a Business Rule to do this (let’s call it B1). The problem is that when BusinessRules.CheckRules() runs it also runs B1, and I do not want that. I want the rule B1 to be run only when the value changes. I would have thought that Business Rules, which modify other properties, would be run only when the properties associated to them are set.

Is there a built in way to achieve this?

Here are some solutions I know I can implement:

-          one is to remove the rule and go back to how we used to do it: by calling a reset method directly from the property setter.

-          another possible solution is to create a flag to indicate that the property is in the process of been changed and it will help us know inside the rule that the call came from the property setter but we will have to create variables for every property that has a business rule associated.

 

Does anybody know how to do it or has a better solution for this situation?

Thanks,

Lazaro

 

Lazaro replied on Tuesday, July 05, 2011

Does anyone have any ideas for this problem?

Thanks,

Lazaro

rxelizondo replied on Wednesday, July 06, 2011


Hi Lazaro,


I never tried this but I wonder if a "RuleSet" will work for you. Something like this:

protected override void AddBusinessRules()
{
   BusinessRules.RuleSet = "default";
   // Here you add all your rules including B1 rule.

   BusinessRules.RuleSet = "SpecialRuleSet";
   // Here you add all your rules except B1.

   // Set what RuleSet you want to be active.
   BusinessRules.RuleSet = "default";
}

So now, when you do a "RuleCheck()", you do this:

BusinessRules.RuleSet = "SpecialRuleSet";
BusinessRules.CheckRules();
BusinessRules.RuleSet = "default";

----------------

Instead of retyping all the rules twice, I guess you could create a function that adds all the rules and pass a parameter to the function indicating wether to add or not add the B1 rule:

protected override void AddBusinessRules()
{
   BusinessRules.RuleSet = "default";
   AddAllRules(true) // includes B1.

   BusinessRules.RuleSet = "SpecialRuleSet";
   AddAllRules(false) // do not includes B1.

   // Set what RuleSet you want to be active.
   BusinessRules.RuleSet = "default";
}

Again, not sure if it would work so please let me know because I am kind of curios.

Thanks.

Lazaro replied on Friday, July 08, 2011

Thank you for your answer. Here is how I did it using the RuleSet.

We have a Business Base class that is the base to all our business classes (in-between the CSLA Base class), so we put the following logic there for all our business classes to use. The idea is that when a rule gets added, we know whether it modifies or not and if it does, we do not execute it when calling all the rules. Note that the rules that we add as modifying rules should not contain validation logic, but only modification logic.

public abstract class MyBusinessBase<T> : BusinessBase<T> 
  where T : MyBusinessBase<T>
{

 

  #region CONSTANT

 

  protected const string CONST_RULESET_WITHOUT_MODIFYING_BUSINESS_RULES =   

  "WithoutModifyingBusinessRules";

  protected const string CONST_RULESET_DEFAULT = "default";

 

  #endregion

 

  protected void AddBusinessRule(IBusinessRule rule)
  {
     AddBusinessRule(rule, false);
  }

  protected void AddBusinessRule(IBusinessRule rule, bool itModifies)
  {
     if (!itModifies)
     {
       BusinessRules.RuleSet = CONST_RULESET_WITHOUT_MODIFYING_BUSINESS_RULES;
       BusinessRules.AddRule(rule);
     }
 
     BusinessRules.RuleSet = CONST_RULESET_DEFAULT;
     BusinessRules.AddRule(rule);        
  }
  protected void CheckAllRules()
  {
     BusinessRules.RuleSet = CONST_RULESET_WITHOUT_MODIFYING_BUSINESS_RULES;
     BusinessRules.CheckRules();
     BusinessRules.RuleSet = CONST_RULESET_DEFAULT;
  }

 
  protected override void Child_Create()
  {
     BusinessRules.RuleSet = CONST_RULESET_WITHOUT_MODIFYING_BUSINESS_RULES;
     base.Child_Create();
     BusinessRules.RuleSet = CONST_RULESET_DEFAULT;
  }

  protected override void DataPortal_Create()
  {
     BusinessRules.RuleSet = CONST_RULESET_WITHOUT_MODIFYING_BUSINESS_RULES;
     base.DataPortal_Create();
     BusinessRules.RuleSet = CONST_RULESET_DEFAULT;
  }

}

JonnyBee replied on Saturday, July 09, 2011

Hi,

A very simple soultion is in your rules execute method to test for:

if (ApplicationContext.LogicalExecutionContext == LogicalExecutionContexts.Client) {
    ... rule logic runs onle on client side when property is changed or CheckRules is called.
}

You should also be aware that for Csla 4.2 we have added options to restrict when a rule will run (RunMode) when you register a rule. I have had the same problems with rules that updates other properties and (avoiding) async rule chaining.

  [Flags]
  public enum RunModes
  {
    /// <summary>
    /// Default value, rule can run in any context
    /// </summary>
    Default=0,
    /// <summary>
    /// Deny rule from running in CheckRules
    /// </summary>
    DenyCheckRules = 1,
    /// <summary>
    /// Deny rule from running as AffectedProperties from another rule.
    /// </summary>
    DenyAsAffectedProperty = 2,
    /// <summary>
    /// Deny rule from running on serverside portal
    /// </summary>
    DenyOnServerSidePortal = 4
  }

Code sample


    protected override void AddBusinessRules()     {      
      base.AddBusinessRules();
      BusinessRules.AddRule(new OnlyWhenPropertyHasChanged(Value1Property) { RunMode = RunModes.DenyCheckRules | RunModes.DenyAsAffectedProperty });
      BusinessRules.AddRule((new AsyncRule(Value2Property){RunMode = RunModes.DenyOnServerSidePortal}));
    }

Lazaro replied on Monday, July 11, 2011

Thanks Jonny.

I will download the version 4.2 right now because it is really what I was waiting for.

Lazaro replied on Monday, July 11, 2011

Hi Jonny,

I just downloaded the version 4.2 but it did not include the logic that you just mentioned about restricting when the business rules should be running.

Could you let me know when the code will be available to de downloaded?

Thanks again,

Lazaro

JonnyBee replied on Tuesday, July 12, 2011

Use Subversion and download the latest version from trunk (anynonymous has readonly access):

http://www.lhotka.net/cslanet/Repository.aspx

(We use TortoiseSVN for access - you will find a link to this in the repository page.)

Another interesting point is that we/you may also create new rule base classes like (I haven't time to tinker around with these ideas yet) 

PropertyChangedRule         (property changed and cascaded change)
PropertyChangedOnlyRule (property changed only - not cascaded property changed)
ObjectRule                             (object level rule)

Lazaro replied on Thursday, July 14, 2011

Thanks Jony,

I was able to download CSLA from the trunk and we are testing the project right now.

When the version 4.2 gets officially released, will it contain this code?

Thanks,

Lazaro

 

JonnyBee replied on Friday, July 15, 2011

Csla 4.2 is still alpha so there could still come breaking changes but we have not (yet) discussed any removal of this feature/rework of the rule engine.

We'd still like to get feedback from developers on how this feature has been implemented and it's usability.

This feature has been on the wish list for some time and we want to support the Error/Warn/Info messages to other properties than primary property and also support external rule engines (that may return a list of properties and messages).

 

 

StefanCop replied on Tuesday, October 18, 2011

Hi,

The RunModes sound interesting to me. Have you considered a mode for  "run only before Save()" ?
Right now we set a flag on the BOs, which all our rules have to check.

Or has anyone a solution for this: for certain reason we set UpdateSourceTrigger="PropertyChanged" in Xaml. That's no problem with simple validation rules, but expensive rules should only run just before the Save() ? 

Would async rules or priority help?


JonnyBee replied on Wednesday, October 19, 2011

Hi Stefan,

No, we haven't discussed or even considered a mode for RunOnlyBeforeSave.

The main reason is that inside CSLA the BusinessRules.CheckRules() is only ever called by the framework in Core.BusinessBase:

So - there is no "context" for the BusinessRules to know if it is called from a Save or any other part of your code.

The "primary" usage of rules should be to check rules when object is created - then check all rules as properties is changed by the user.

Property rules is triggered from the <bo>.PropertyHasChanged method and object level rules by <bo>.CheckObjectRules. These are the ONLY methods you should call on an object that is active in databinding as only these methods will raise OnPropertyChanged for affected properties to keep the UI in sync.

<bo>.BusinessRules.CheckRules should only be called on objects that are not active in databinding (ex dDataAccess) because this code will not raise  OnPropertyChanged event to update the UI.

StefanCop
Or has anyone a solution for this: for certain reason we set UpdateSourceTrigger="PropertyChanged" in Xaml. That's no problem with simple validation rules, but expensive rules should only run just before the Save() ? 

Now for your WPF/SL validation I prefer to use async validation rules for expensive rules and only allow these to run when the property is changed by the user (ie: CanRunAsAffectedProperty, CanRunInCheckRules and CanRunOnServer all set to false).

Another option is to use "object" level rules. These can be restrictet to run ONLY when your code calll <BO>.CheckObjectRules() (ie: CanRunInCheckRules = false). That would however run all the object rules but only when your code calls the method.

And it's also my recommendation to always run async rules with a higher priority than sync rules (to be executed after the sync rules). 

StefanCop replied on Wednesday, October 19, 2011

Hi Jonny

Thanks a lot for your explanation, that makes the decision clear. And I also appriciate your recommendation and options!

I'm just wondering if that's not a common problem:
If you set UpdateSourceTrigger="PropertyChanged", a  PropertyChanged is raised with every keystroke,
and then if a rule needs to go to the server to validate that property, this takes far to long.

I must admit that I've not used async rules yet. If my understanding of async rules is correct, they wouldn't block the UI (only the property becomes busy?), but entering 10 characters could send 10 requests to the server.

 

 

JonnyBee replied on Wednesday, October 19, 2011

Oh, I see.

Ideally you would apply a sync rule for format check/length and only allow the expensive rule to run when input is valid or use UpdateSourceTrigger="LostFocus". (Ie: the async rule has a priority higher that BusinessRules.ProcessThroughPriority and will not run when a previous sync rule is broken).

You could however also create a "dummy" field and attach the expensive rule to that field. Then when some condition is met your code call PropertyChanged for that "dummy" property to trigger that rule. (You only need a PropertyInfo - don't have to implement the property with getter/setter).

StefanCop replied on Wednesday, October 19, 2011

Oh yes, your first remark sounds good! Now, I remember Rocky mentioning this in an ebook, too.  I'll try this soon.

[ With the "LostFocus" option we get stuck on a "WPF problem": Clicking a Save button without leaving the field.
Also the dummy field (same as the object rule) has the disadventage of not having any property where the validation message can be assigned to. ]

Thanks a lot for your input!

JonnyBee replied on Wednesday, October 19, 2011

Just one last comment.

Starting from CSLA 4.2 property rules can set validation messages to other properties than PrimaryProperty.
So the "dummy" property is a valid option for your case.

Copyright (c) Marimer LLC