Are InputProperties always considered as AffectedProperties?

Are InputProperties always considered as AffectedProperties?

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


Andreas posted on Monday, July 23, 2012

Hi!          

Are properties added to the InputProperties collection in a custom BusinessRule implementation always considered as AffectedProperties?

If so, how can I make sure that rules with higher priority are called after business rules with lower priority?

I have a BussinesRule with high priority (1) attached to property "Property_1", because I want this rule to be executed after all other rules which have the default priority (0). But some of the other BusinessRules with priority "0" need the value of "Property_1" to calculate its rule result. So I have to add "Property_1Property" to their BusinesssRule constructor and also to its InputProperties collection to get access to its value when the rule is executed.

Now, the Rule attached to Property_1 with priority 1, is executed on the same priority level as the BusinessRules a priority "0", because it is automatically identified as an AffectedProperty and AffectedProperties are always executed after the primary property. Unfortunately this breaks my business logic because, as I said, I have to make sure that the rule attached to Propertiy_1 should only execute after all standard business rules with priority 0. Any advises are very much appreciated.

Regards,

Andreas

 

JonnyBee replied on Monday, July 23, 2012

InputProperties is always considered a dependency (starting from Csla 4.2).

I do not quite understand the problem tho´, as the the rule prosessing always follows the priority.

  1. Rule is always execute in ordered sequence Priority (asc) for the "PrimaryProperty" that was changed.
  2. Then accumulated AffectedProperties from  execution PLUS other properties where PrimaryProperty is an InpuProperty is executed.
  3. The Rules is exuted on a "per-property" basis (possible alphabetically sorted -  can´t remeber from my memory) sorted by Priority (asc).

So the rules do not mix the priority levels. 

Andreas replied on Monday, July 23, 2012

Hi Jonny,

thanks. As you pointed out, rules are executed on a "per-propery" basis sorted by priority. That's what I was missing and it explains the way the rules are executing in my code. But, I am still looking for a way to execute a rule at the very end, after all other BusinessRules have been completed.

Andreas

JonnyBee replied on Monday, July 23, 2012

Mmmm, that could be little tricky.

I assume you are talking SYNC rules only -  as ASYNC rules will finish at an "undertemined" time. 

1. You could override PropertyHasChanged and call an ObjectRule (or fictive property rule) after the SYNC rules have completed. (ie after base.PropertyHasChanged(property)).

When all rules have complete (SYNC and ASYNC) you will get the ValidationCompleted event. However - I do not think that you should start new rules within this event. 

Another possibility is to review options on Dependency and Priority.  Like consider to add a "fictivious" property named "ZZZZ" and add this as dependency.  

Andreas replied on Tuesday, July 24, 2012

So there is no way to control the order of business rule execution on "per object type" basis (vs. "per property" basis)? I think, currently the order in which the rules are added determines the order in which they are executed. Wouldn't it make sence to be able to control the order of the business rule execution on "per object type" basis? 

JonnyBee replied on Tuesday, July 24, 2012

Hi,

CSLA supports 2 types of rules: PropertyRules and ObjectRules. 

PerPropertyRules is executed from PropertyHasChanged (autmatically when property is set to a new value)  or when you call BusinessRules.CheckRules()

ObjectRules is only executed when your code call <bo>.CheckObjectRules()  (databound) or BusinessRules.CheckRules or BusinessRules,CheckObjectRules(). 

RULES IS ALWAYS EXEUTED IN ASCENDING ORDER ON PRIORITY - BOTH PER-PROPERTY AND PER-OBJECT. 

Within the same priority I consider the sort sequence random and of no significant order!!!!!

Andreas replied on Tuesday, July 24, 2012

Sorry Jonny for the confusion, but I am looking for a way to make sure that BusinessRules attached to property "B" are executed before the rules attached to property "A".

JonnyBee replied on Tuesday, July 24, 2012

In which condition? PropertyB is edited by the user? CheckRules()  to check all rules?  

Andreas replied on Tuesday, July 24, 2012

When CheckRules() is called.

JonnyBee replied on Tuesday, July 24, 2012

Why dows it matter? 

Ex:

PropertyA  => AffectedProperty = PropertyB
or
PropertyB has InputProperty = PropertyA

Rules for PropertyB will ba run at least 2 times in CheckRules():

  1. After execution of propertyRules for propertyA
  2. As PropertyRules on PropertyB (whether before or after PropertyA@s rules is executed).

Andreas replied on Tuesday, July 24, 2012

If you have a property that are assembled from other properties. For example a phone number’s string representation is assembled from country code, city code and the phone number extension. My UI uses three different input boxes for the country code, city code and the local phone number. Each one has DataAnnotationRules attached. In my database I have only one column “PhoneNumber” that has to follow a specific format: “++<ContryCode> (<CityCode>) <Number>”

I attach a “PhoneFormatRule” to the PhoneNumber. It creates a string like “++<ContryCode> (<CityCode>) <Number>” and pass it as a parameter to AddOutValue(). In this case I have to make sure that PhoneFormatRule is called after the rules for CountryCode, CityCode and Number. I use context.IsCascadeContext to check if the rule is called because of an AffectedProperty condition. I execute the Rules formatting logic only if context. IsCascadeContext==false and I added this rule as the last one in AddBusinessRules(). It works for me, but it’s probably a hack that will break in the future.

JonnyBee replied on Tuesday, July 24, 2012

Does this mean that the user can edit ANY of the 4 fields - including the calculated one?

Do you attach business rules to fields tht the user is not allowed to edit?

No, I wouldn´t do this as you have done - but is unable to post my solution now as I am soon travelling home from vacation.

rfcdejong replied on Wednesday, July 25, 2012

Whenever you are back from vacation i would like to see into the same solution since a colleague of my has a simulair problem which tried to explain in here http://forums.lhotka.net/forums/t/11506.aspx

JonnyBee replied on Wednesday, July 25, 2012

I would claim that your data access code should have the responsibility for setting the correct values during Fetch/Create.

Then I would have a rule to PhoneNumber property to ALWAYS format the field (assuming that the user is not allowed to edit all 4 fields). If user is allowed to edit all 4 fields the "calculation rules" must be attached to each property to know exactly which field was changed.

  public class FormatPhoneNumber : Csla.Rules.CommonRules.CommonBusinessRule
  {
    private readonly PropertyInfo<int> _countryProperty;
    private readonly PropertyInfo<int> _cityProperty;
    private readonly PropertyInfo<int> _numberProperty;
 
    public FormatPhoneNumber(PropertyInfo<string> phoneNumber, PropertyInfo<int> countryProperty, PropertyInfo<int> cityProperty, PropertyInfo<int> numberProperty)
      : base(phoneNumber)
    {
      _countryProperty = countryProperty;
      _cityProperty = cityProperty;
      _numberProperty = numberProperty;
 
      InputProperties = new List<IPropertyInfo>() { countryProperty, cityProperty, numberProperty };
 
      CanRunInCheckRules = false;
      CanRunOnServer = false;
    }
 
    protected override void Execute(RuleContext context)
    {
      var country = context.InputPropertyValues[_countryProperty];
      var city = context.InputPropertyValues[_cityProperty];
      var number = context.InputPropertyValues[_numberProperty];
 
      context.AddOutValue(string.Format("++{0} ({1}{2}", country, city, number));
    }
  }

Added to the root object like this:

  [Serializable]
  public class Root : BusinessBase<Root>
  {
 
    public static readonly PropertyInfo<int> CountryProperty = RegisterProperty<int>(c => c.Country);
    [Range(1, 99)]
    public int Country
    {
      get { return GetProperty(CountryProperty); }
      set { SetProperty(CountryProperty, value); }
    }
 
 
    public static readonly PropertyInfo<int> CityProperty = RegisterProperty<int>(c => c.City);
    [Range(1, 99)]
    public int City
    {
      get { return GetProperty(CityProperty); }
      set { SetProperty(CityProperty, value); }
    }
 
 
    public static readonly PropertyInfo<int> NumberProperty = RegisterProperty<int>(c => c.Number);
    [Range(1, 999999)]
    public int Number
    {
      get { return GetProperty(NumberProperty); }
      set { SetProperty(NumberProperty, value); }
    }
 
    public static readonly PropertyInfo<string> PhoneNumberProperty = 
RegisterProperty<string>(c => c.PhoneNumber, null"<invalid>");     public string PhoneNumber     {       get { return GetProperty(PhoneNumberProperty); }       set { SetProperty(PhoneNumberProperty, value); }     }     protected override void AddBusinessRules()     {       // add data validation rules       base.AddBusinessRules();       BusinessRules.AddRule(new FormatPhoneNumber(PhoneNumberProperty, CountryProperty,
CityProperty, NumberProperty){ Priority = -1});     }     public static Root NewEditableRoot()     {       return DataPortal.Create<Root>();     }     protected override void DataPortal_Create()     {       LoadProperty(PhoneNumberProperty, string.Empty);       base.DataPortal_Create();     }   }

The end result is that whenever the user edits one of the inputproperties in WinForms/WPF/SL/WinRT the PhoneNumber property is set and then other rules for PhoneNumber is ossible validation rules for PhoneNumberProperty is rerun.

In my case I want the PhoneNumber to ALWAYS be calculated. If i wanted to hide an Invalid value in PhoneNumber from being shown to the user I could also add:

    private static List<string> inputForPhoneNumber = 
new List<string> { CountryProperty.Name, CityProperty.Name, NumberProperty.Name };     public override bool CanReadProperty(Csla.Core.IPropertyInfo property)     {       if (property == PhoneNumberProperty)         return !((BusinessRules.GetBrokenRules().Any(              p => p.Severity == RuleSeverity.Error && inputForPhoneNumber.Contains(p.Property))));       return base.CanReadProperty(property);     }

And the PhoneNumberProperty would then hide the actual value and return the default value ("<invalid>") for the field when one of the input properties has a validation error.

So:

  1. Transformation should always run to keep ALL backing fields updated at ANY time
  2. Use CanReadProperty/CanWriteProperty to add logic to hide actual value or prevent user from editing a field.
  3. These rules should only run in user side logic for rich clients (CanRunInServer = false, CanRunInCheckRules = false)
  4. For WEB application they must run in CheckRules but not on serverside (CanRunInServer = false, CanRunInCheckRules = true)

Andreas replied on Thursday, July 26, 2012

Thanks Jonny. Especially the very clever combination of BrokenRules with CanReadPropery/CanWriteProperty opened my eyes for other useful scenarios inside my current project.

There is only one point where I slightly disagree, from what you wrote. I don't think that it is always possible to setup all properties inside the data access code. Some calculated properties depend on data and behavior that is not fully available at fetch/create time. That again means that the calculation of these properties needs to be delayed until the business object as a certain status. And because I love the rules engine of Csla I would like implement all these calculations inside business rules only and not inside the data access code (Fetch/Create) and not inside the property getter and setter. But then I think, I need more control over the way business rules are executed.
As I already said I would like to have control about the order of business rule execution on property basis (for ex. property B first and property A last). Or do you think that this kind of business logic should be not implemented in Business Rules, but in a separate member function of the business object?

 

JonnyBee replied on Thursday, July 26, 2012

Hi,

No, I do not agree. Rules for a property should not have that type of "workflow" defined.

Each property/object has an autonomous set of rules.

If you should ever need to check rules for another property first I would rather override PropertyHasChanged and add that logic there.

That way you could still use:

using (BypassPropertyChecks())
{
      PropertyA = ..;
      PropertyB = ...;
}

as in this DataAccessCode there will be no PropertyHasChanged called.

Properties is NOT code and should ONLY be get/set with the exception of lazy loading that must make a DataPortal call to get the object.

I would encourage you to look at the Samples\Net\cs\RuleTutorial sample in Csla Samples download and the Rules sample http://cslagenfork.codeplex.com/releases/view/83993 as there is a load of interesting rules to use and learn from.

Like ShortCircuit rules for:

or Gateway rules that wraps another rule:

and loads more....

You could as an example create a rule like StopIfNotFieldAExists of StopIfNotFieldAHasValue so that no rules for FieldB will not be allowed to run until FieldA has received a value.

Copyright (c) Marimer LLC