BusinessRules only when condition is met

BusinessRules only when condition is met

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


rfcdejong posted on Tuesday, November 02, 2010

As i didn't saw a easy way to add a business rule like:
 - "Required unless is other property is null" 
 - "Required when other property value equals"

I guess that the most common solution would be writing a new Business Rule inheriting from the Required Rule, having another property as a secondary property which will be read and compared with some other value.Or create a business rule when something is a little bit differend. EnddateIsNullOrNotInThePastRule, EnddateIsInThePastRule

For those little differences i created a wrapper business rule that can be around the actual business rule which behavious like a comparer. If .... then execute the actual rule

Is this something that can be taken into Csla itself, maybe in a better (more complete) implementation ?

/// <summary>
///
Business rule condition wrapper to execute an inner rule when condition is met.
///
</summary>
public class ConditionRule<T, V> :
BusinessRule
 
where T :
IBusinessRule
{
  ///
<summary>
  ///
Creates an instance of the rule.
 
///
</summary>
 
/// <param name="rule">Rule to execute when condition is met.
</param>
 
/// <param name="conditionProperty">Condition value property.
</param>
 
/// <param name="conditionValue">Condition comparer function.
</param>
 
public ConditionRule(PropertyInfo<V> conditionProperty, Func<V, bool
> conditionFunc, T rule)
    :
base
(conditionProperty)
  {
    ConditionProperty = conditionProperty;

    PrimaryProperty = rule.PrimaryProperty;
    this.RuleUri = new RuleUri(rule, PrimaryProperty);

    InputProperties = new List<Csla.Core.IPropertyInfo> { conditionProperty };
    ConditionFunc = conditionFunc;
    Rule = rule;
    InputProperties.AddRange(Rule.InputProperties);
  }

///
<summary>
 
///
The rule to execute when the condition compared true.
 
///
</summary>
 
private IBusinessRule Rule { get; set
; }

  ///
<summary>
 
///
Condition function to execute the comparisation.
 
///
</summary>
 
private Func<V, bool> ConditionFunc { get; set
; }

  ///
<summary>
  ///
Rule implementation.
 
///
</summary>
 
/// <param name="context">Rule context.
</param>
 
protected override void Execute(RuleContext
context)
  {
   
var
value = (V)context.InputPropertyValues[ConditionProperty];
   
if
(ConditionFunc(value))
   
{
      Rule.Execute(context);
    }
  }
}

using it like this:

protected
override void AddBusinessRules()
{
 
// RejectReason is required when IsRejected is true
  BusinessRules.AddRule(new ConditionRule<Required, bool
>(
                          IsRejectedProperty, (p) => p ==
true
,
                          new Required(RejectReasonProperty)));

 
// When enddate is not null it should not be in the past
 
BusinessRules.AddRule(new ConditionRule<IsDateNotInThePastRule, DateTime?>(
                          EnddateProperty, (p) => p !=
null,
                         
new IsDateNotInThePastRule(EinddatumProperty)));
}

t.kehl replied on Tuesday, November 02, 2010

Hi rfcdejong

I have tried this. But there is a problem:

In the Execute of the ConditionRule you call Execute of the InnerRule with the Context of the ConditionRule. Now, for example the Required-Rule reads the InputValueProperty out of the Context - but this Property is not in this context. Do you see, what I mean?

Best Regards, Thomas

JonnyBee replied on Tuesday, November 02, 2010

In all essence - we are here looking at a rule with RuleChaining.

There is a couple of lines missing (bugs)  in that code:

1. Code must add InputProperties from the inner rule in constructor.

  public ConditionRule(PropertyInfo<V> conditionProperty, Func<V, bool> conditionFunc, T rule)
    :
base
(conditionProperty)
  {
    InputProperties = new List<Csla.Core.IPropertyInfo> { conditionProperty };
    ConditionFunc = conditionFunc;
    Rule = rule;
    InputProperties.AddRange(Rule.InputProperties);

  }

2. Execute must use GetChainedContext:

  protected override void Execute(RuleContext context)
  {
   
var
value = (V)context.InputPropertyValues[PrimaryProperty];
   
if
(ConditionFunc(value))
   
{
      Rule.Execute(context.GetChainedContext(Rule));
    }
  }

 

rfcdejong replied on Wednesday, November 03, 2010

Thomas, yes, i see what u mean.. It's one of the reasons i post this here and i see Jhonny saw the bugs.
I'm going to adjust my code and impliment it further.

I changed the line of adding inputproperty's to:

  InputProperties.AddRange(Rule.InputProperties.Where(x => !InputProperties.Contains(x)));

This way a duplicate InputProperty won't cause an exception in method BusinessRules.RunRules at line 520 adding the property item twice in the values dictionary.


Jhonny, do u think something like this should be in Csla or perhaps CslaContrib ?

JonnyBee replied on Wednesday, November 03, 2010

Hi,

I'd love to see this kind of contributions going into CslaContrib.  We need more activity and contributions going into CslaContrib and Rules is definitely one new area we should be able to share code.

Rocky is in charge for what goes into CSLA or not so that's his call

 

 

Copyright (c) Marimer LLC