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?
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));
}
}
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!!! :-)
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
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.
Thanks!!! That's what I was missing.
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