CSLA 4
I am trying to create a condtional rule and having trouble trying to replicate behavior from previous Csla versions.
So say I have some properties that are only required based on the state / flag in an object and I would like to reuse the existing RequiredRule.
In previous versions of Csla I would do something like this (sorry, old vb code that I could find..):
Private Shared Function DetailDataRequiredRule(Of T As MyObject)(ByVal target As T, ByVal e As Validation.RuleArgs) As Boolean
Dim isRuleValid = True
If (target.RequireDetailProductData) Then
isRuleValid = CommonRules.StringRequired(target, e)
End If
Return isRuleValid
End Function
And then I can add ValidationRules.AddRule with my custom rule for only the properties that need to be validated when the condition is met.
So...if you are still with me. My problem is trying to execute the existing ValidationRule.
Thanks for pointers.
Hello, if i get what you say , i think that you have to Define new Class for every custom rule inherit BusinessRule in
CSLA 4 so in your case this is what i can help in C# :
private class SomeRule: Csla.Rules.BusinessRule
{
protected override void Execute(Csla.Rules.RuleContext context)
{
var target = (TheClassIncludeTheProperites)context.Target;
If (target.RequireDetailProductData)
{
if(string.IsNullOrEmpty(PrimaryProperty);
context.AddErrorResult("Required Field");
}
}
}
and you add it to the rules in this way
BusinessRules.AddRule(new SomeRule{ PrimaryProperty = PropertyToValidate, AffectedProperties = { PropertyToBeAffected } });
I haven't tried this, but I would probably subclass the StringRequired rule, and the in the override of Execute selectively call the base implementation.
Perfect! That did the trick. Here is my code for reference:
private class AdditionalDataRequired : Csla.Rules.CommonRules.Required {
public AdditionalDataRequired(IPropertyInfo primaryProperty) : base(primaryProperty) { }
protected override void Execute(Csla.Rules.RuleContext context) {
var target = (MyObject)context.Target;
if (target.IsAdditionalDataRequired) {
base.Execute(context);
}
}
}
BusinessRules.AddRule(new AdditionalDataRequired (MyProperty1));
BusinessRules.AddRule(new AdditionalDataRequired (MyProperty2));
BusinessRules.AddRule(new Dependency(PropertyThatDeterminesIfAddtionalDataIsRequired, MyProperty1));
BusinessRules.AddRule(new Dependency(PropertyThatDeterminesIfAddtionalDataIsRequired, MyProperty2));
There is 2 different approaches to this:
1. Chained Rule:
Your rule has a StringRequired inner rule and you test for whether the inner rule should run or not.
2. Use Priority to control sequence and create a "flow control rule" that will ShortCircuit the rule if condition is not met to run first.
I like the concept of FlowControlRules whenever applicable because they can be combined with any other rul and it is easy to get into trouble with RuleChaining if you do not fully understand the underlying rules in terms of Async/Sync capabilities, InputProperties and does the underlying rule require the Target object.
Jonny
Code samples:
1. Chained Rule
''' <summary> ''' This class demonstrates how to utilize inner rules inside a business rule. ''' ''' This rule calls inner rule StringRequired on PrimaryProperty if countryCode is US ''' </summary> Public Class StringRequiredIfUS Inherits Csla.Rules.BusinessRule Private _countryProperty As IPropertyInfo Private _innerRule As IBusinessRule ' TODO: Add additional parameters to your rule to the constructor Public Sub New(primaryProperty As IPropertyInfo, countryProperty As IPropertyInfo) MyBase.New(primaryProperty) _countryProperty = countryProperty _innerRule = DirectCast(New Csla.Rules.CommonRules.Required(primaryProperty), IBusinessRule) InputProperties = New List(Of IPropertyInfo)() ' this rule needs the Country property InputProperties.Add(countryProperty) ' add input properties required by inner rules Dim inputProps = _innerRule.InputProperties.Where(Function(inputProp) Not InputProperties.Contains(inputProp)) If inputProps.Count() > 0 Then InputProperties.AddRange(inputProps) End If End Sub Protected Overrides Sub Execute(context As RuleContext) ' TODO: Add actual rule code here. Dim country = DirectCast(context.InputPropertyValues(_countryProperty), String) If country = CountryNVL.UnitedStates Then _innerRule.Execute(context.GetChainedContext(_innerRule)) End If End Sub End Class
2. "Flow control rule"
(this rule checks if user is allowed to edit a field)
Public Class StopIfNotCanWrite
Inherits BusinessRule Public Sub New([property] As IPropertyInfo) MyBase.New([property]) End Sub ''' <summary> ''' Rule indicating whether the user is authorized ''' to read the property value. ''' Will always be silent and never set rule to broken. ''' </summary> ''' <param name="context">Rule context object.</param> ''' <remarks> ''' Combine this Rule with short-circuiting to ''' prevent evaluation of other rules in the case ''' that the user isn't allowed to read the value. ''' </remarks> Protected Overrides Sub Execute(context As RuleContext) Dim isAuthorized = True Dim business = TryCast(context.Target, BusinessBase) If business IsNot Nothing Then isAuthorized = business.CanWriteProperty(context.Rule.PrimaryProperty) End If If Not isAuthorized Then ' ShortCircuit rule
context.AddInformationResult([String].Empty, True) End If End Sub End Class
And in AddBusinessRules method
' Rules with Priority=-1 will always be executed before default Priority 0
BusinessRules.AddRule(new StopIfNotCanWrite(OtherAddress1Property) { Priority = -1 });
BusinessRules.AddRule(new Required(OtherAddress1Property));
Thanks for the detail reply Jonny. See my reply to Andy for how I ended up handling this scenario.
Copyright (c) Marimer LLC