CSLA 4.0 RTM 1 BusinessRules issue with non-primary property

CSLA 4.0 RTM 1 BusinessRules issue with non-primary property

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


GregDobbs posted on Friday, July 23, 2010

Been having a bit of a problem with BusinessRules - so let's see if it's just me or if there's an issue.

Scenario:

BO has two properties - Password and ConfirmPassword.

Each Property has a Required and MaxLength Common business rule.

Custom business rules need to enforce that Password = ConfirmPassword.

Each property - Password and ConfirmPassword - has a business rule that makes sure it matches the other property.

Thus, whenever either the Password or ConfirmPassword properties change, business rules need to be run against both properties resulting in the PropertyStatus controls for each showing a broken rule if they don't match.

What seems to be happening is that whenever one of the properties changes, the business rules associated with that property run correctly. However, in the PropertyChanged handler for that property a BusinessRules.CheckRules method is run for the other property - and that rules check delivers erroneous results. Note that none of these rules are asynchronous.

When the BusinessRules.CheckRules method is called from the changed property, the business rule on the other property still retains the previous value of the calling property.

Imagine you have a usercontrol with two textboxes: Password and ConfirmPassword each with a PropertyStatus control.

Here's the play-by-play:

1) Enter password deer  in the Password textbox and tab off control

Password PropertyStatus correctly indicates that Password does not match ConfirmPassword (since ConfirmPassword is blank).

ConfirmPassword PropertyStatus correctly indicates that it is required (common rule), but does NOT indicate that it does not match the Password after the Password has been entered. (The PropertyChanged event of Password should have triggered a business rule check in ConfirmPassword - it should be showing TWO broken rules - required and does not match).

2) Enter deer1 in the ConfirmPassword control and tab off the control

ConfirmPassword PropertyStatus now correctly indicates that it does not match the Password.

3) Enter deer1 in the Password textbox and tab off the control. Now, Password and ConfirmPassword are equal, so the PropertyStatus controls should be blank (no error) for both.

Instead, the ConfirmPassword PropertyStatus control still shows that Password does not equal ConfirmPassword. Debugging shows that the value in the custom business rule for ConfirmPassword still has the old Password value of deer - even though that has been changed to deer1.

Here's the code for the custom business rules and PropertyChanged :

Business Rules Declaration:       

BusinessRules.AddRule(New PasswordMatchRule(PasswordProperty))
BusinessRules.AddRule(New PasswordConfirmationMatchRule(ConfirmPasswordProperty))

PropertyChanged:

 Private Sub PropertiesChangeHandler(ByVal sender As Object, ByVal e As    System.ComponentModel.PropertyChangedEventArgs) Handles Me.PropertyChanged
        Select Case e.PropertyName
            Case "Password"
                BusinessRules.CheckRules(ConfirmPasswordProperty)
            Case "ConfirmPassword"
                BusinessRules.CheckRules(PasswordProperty)
        End Select
    End Sub

Business Rules:

    'Password Matches Password Confirmation
    Private Class PasswordMatchRule
        Inherits Csla.Rules.BusinessRule
        Public Sub New(ByVal primaryProperty As Csla.Core.IPropertyInfo)
            MyBase.New(primaryProperty)
            Me.IsAsync = False
            InputProperties = New List(Of Csla.Core.IPropertyInfo)
            InputProperties.Add(primaryProperty)
            InputProperties.Add(ConfirmPasswordProperty)
        End Sub
        Protected Overrides Sub Execute(ByVal context As Csla.Rules.RuleContext)
            MyBase.Execute(context) 
            If context.InputPropertyValues(PasswordProperty) <> context.InputPropertyValues(ConfirmPasswordProperty) Then
                context.AddErrorResult("Password does not match password confirmation.")
            End If
        End Sub
    End Class


    'Password Confirmation Matches Password
    Private Class PasswordConfirmationMatchRule
        Inherits Csla.Rules.BusinessRule
        Public Sub New(ByVal primaryProperty As Csla.Core.IPropertyInfo)
            MyBase.New(primaryProperty)
            Me.IsAsync = False
            InputProperties = New List(Of Csla.Core.IPropertyInfo)
            InputProperties.Add(primaryProperty)
            InputProperties.Add(PasswordProperty)
        End Sub
        Protected Overrides Sub Execute(ByVal context As Csla.Rules.RuleContext)
            MyBase.Execute(context) 
            If context.InputPropertyValues(PasswordProperty) <> context.InputPropertyValues(ConfirmPasswordProperty) Then
                context.AddErrorResult("Password does not match password confirmation.")
            End If
        End Sub
    End Class

 

In attempting to use the custom rules I have tried using both InputPropertyValues and context.target.property methods in getting the proper values. Both methods yield the same erroneous result.

Anyone else had this problem?

 

 

 

 

 

 

 

RockfordLhotka replied on Friday, July 23, 2010

You should use the AffectedProperties concept to solve this.

Create one rule that compares two values, and associate that rule with both properties - but in the rule make sure the AffectedProperties list includes the other property. That way both properties become valid or invalid together:

  [Serializable]
  public class Test : BusinessBase<Test>
  {
    public static readonly PropertyInfo<string> UserName1Property = RegisterProperty<string>(c => c.UserName1);
    public string UserName1
    {
      get { return GetProperty(UserName1Property); }
      set { SetProperty(UserName1Property, value); }
    }

    public static readonly PropertyInfo<string> UserName2Property = RegisterProperty<string>(c => c.UserName2);
    public string UserName2
    {
      get { return GetProperty(UserName2Property); }
      set { SetProperty(UserName2Property, value); }
    }

    protected override void AddBusinessRules()
    {
      base.AddBusinessRules();
      BusinessRules.AddRule(new ValuesMatch(UserName1Property, UserName2Property));
      BusinessRules.AddRule(new ValuesMatch(UserName2Property, UserName1Property));
    }
  }

  public class ValuesMatch : Csla.Rules.BusinessRule
  {
    private Csla.Core.IPropertyInfo CompareProperty { get; set; }
    public ValuesMatch(Csla.Core.IPropertyInfo primaryProperty, Csla.Core.IPropertyInfo compareProperty)
      : base(primaryProperty)
    {
      CompareProperty = compareProperty;
      InputProperties = new List<Csla.Core.IPropertyInfo> { PrimaryProperty, CompareProperty };
      AffectedProperties.Add(CompareProperty);
    }

    protected override void Execute(Csla.Rules.RuleContext context)
    {
      var v1 = (string)context.InputPropertyValues[PrimaryProperty];
      var v2 = (string)context.InputPropertyValues[CompareProperty];
      if (v1 != v2)
        context.AddErrorResult(string.Format("Value {0} must match {1}", PrimaryProperty.FriendlyName, CompareProperty.FriendlyName));
    }
  }

GregDobbs replied on Saturday, July 24, 2010

Works like a charm - thank you Rocky! If I was wealthy I'd be donating a percentage of my project income to you for all your help on this forum!!! :-) 

Jeff Flowerday replied on Wednesday, September 08, 2010

This solution creates 2 broken rule error entries one for each property.  What if you wanted only one broken rule entry for the 2 properties.

ie) Start Date must be Less than End Date

JonnyBee replied on Wednesday, September 08, 2010

Hi,

Then you should use the new Dependency rule to create a dependency between the two fields:


    protected override void AddBusinessRules()
    {
      base.AddBusinessRules();
      BusinessRules.AddRule(new ValuesMatch(UserName1Property, UserName2Property));
      BusinessRules.AddRule(new Dependency(UserName2Property, UserName1Property));
    }

The last dependency rule will tell the rule engine that whenever UserName2 is changed then rules for UserName1 field must also be checked.

And you wouldn't need to set the AffectedProperties in your custom rule.

Jeff Flowerday replied on Wednesday, September 08, 2010

Thanks!!!  That's what I was missing.

brafales replied on Monday, November 15, 2010

Hi,

I've used this code and works fine, however, when a property is invalid, I'd like to have on the view highlighted all of the AffectedProperties instead of only the PrimaryProperty.

Is there a way to do this? Using the code I only get highlighted in red the primary property and not all the related ones.

Copyright (c) Marimer LLC