Closed halloween8 closed 9 years ago
My recommendation is to separate business and validation (ex: async lookup and validation).
The async rule should only be responsible for doing the async lookup and update field values and should only be allowed to run when the actual property has been edited by a user. Use PropertyRule base class and :
CanRunAsAffectedProperty = false;
CanRunOnServer = false;
CanRunInCheckRules = false;
Each field that is updated should have sync rules for validation that should be rerun automatically by the rule engine after the async rule has completed.
See my blog post for more detailed info on the rule engine: https://jonnybekkum.wordpress.com/2011/08/29/csla-4-2-rules-update/ https://jonnybekkum.wordpress.com/2012/11/07/csla-4-5-ruleengine-update/
Now the behavior you see is intended and as such: a) InputProperties is read from the object before the async rule starts execution b) When rule has completed you must call context.Complete and only then will properties be updated and eventual broken rules added to the objects collection of BrokenRules. c) So given this behavior you should prefer to have sync rules to do the basic input format/range/required style validation.
Hoooo That worked, but I'm guessing that could be dangerous about infinite loop of business rule checking.
Nope. Read my blog posts. By default the rule engine will only rerun but not cascade down when running rules for AffectedPropertied (the second level).
You may activate the advanced mode (CascadeOnDirtyProperties) at your own risk and that may result in an infinite loop.
I see, I've removed from the Async rule the InnerRule and added the CanRunAsAffectedProperty = True in the validation rules.
Does not seem to be executed when the context.Complete() is execute.
any thoughts?
Well, the question is - when should the async rule run?
My recommendation is that async rules should only run when the user edits fields and so the rule should be added to each input field the user is allowed to edit that may cause the rule to execute.
IE: Async rules should only run on the primary property (1st level) and then the rule engine will rerun sync rules for the next level in context.Complete().
I also prefer to have Async rules registered with a Priority = 10 (or larger). This way the async rule will only run if there are no sync validation errors on the actual field.
When on the logical server side - in data access - you should load fields as appropriate directly from database and NOT rely on async rules.
So if the sync rules does not run the most likely cause is that the async rule is called from the "second" level as AffectedProperty.
I understand, but here is what I'm trying to do, I have a screen, where any of the 4 first fields entered can load the rest of the object. The desired behavior is if the user enters the first, second, third of 4 field and that the row is already in the db I load the rest of the object and the user can continue with the rest of the fields. So I what I did is a business rule that load the data and the object based on those fields.
Then I need the object to validate itself with simple sync rule (Required and the like).
So If I understand you correctly, what you are saying is
AsyncRule(property1, property2,property3,property4) AsyncRule(property2, property1,property3,property4) AsyncRule(property3, property1,property2,property4) AsyncRule(property4, property1,property2,property3)
so these will trigger the validation rule for Property1 when the first rule is trigger?
For this case it is #2 and you should also set Priority to be > 0 (I prefer to use 10). This is to make sure async rule is not even called if there are broken rules with lower priority. By default - ALL rules with Priority =0 will always execute. It doesn't matter i previous rules were broken!
so these will trigger the validation rule for Property1 when the first rule is trigger?
No. The AsyncRule for that property will be called when that property is set to a new value. And when you make sure that the async rule is only called when a user edits the field (as primaryProperty) the rule engine will rerun rules for the affectedProperties of this rule. And the async rule should NOT be triggered again to o an async lookup when it is the second run (affectedProperty).
I would declare the rule like this:
public class AsyncRule : PropertyRule
{
private readonly PropertyInfo<string> _purchaseInvoiceIdProperty;
private readonly PropertyInfo<string> _invoiceIdProperty;
private readonly PropertyInfo<Guid?> _supplierIdProperty;
private readonly PropertyInfo<DateTime> _invoiceDateProperty;
private readonly PropertyInfo<byte> _isAInOutProperty;
public AsyncRule(IPropertyInfo primaryProperty,
PropertyInfo<string> purchaseInvoiceIdProperty,
PropertyInfo<string> invoiceIdProperty,
PropertyInfo<Guid?> supplierIdProperty,
PropertyInfo<DateTime> invoiceDateProperty,
PropertyInfo<byte> isAInOutProperty)
: base(primaryProperty)
{
//keep parameters in private variables
_purchaseInvoiceIdProperty = purchaseInvoiceIdProperty;
_invoiceIdProperty = invoiceIdProperty;
_supplierIdProperty = supplierIdProperty;
_invoiceDateProperty = invoiceDateProperty;
_isAInOutProperty = isAInOutProperty;
InputProperties.AddRange(new IPropertyInfo[] { purchaseInvoiceIdProperty,
invoiceIdProperty, supplierIdProperty, invoiceDateProperty, isAInOutProperty });
AffectedProperties.AddRange(new IPropertyInfo[] { purchaseInvoiceIdProperty,
invoiceIdProperty, supplierIdProperty, invoiceDateProperty, isAInOutProperty });
IsAsync = true;
CanRunAsAffectedProperty = false; // do not run as affected property
CanRunInCheckRules = false; // do not run in CheckRules
CanRunOnServer = false; // do not run on serverside
}
protected override void Execute(RuleContext context)
{
// get input property values using generic helper methods in context
var purchaseInvoiceID = context.GetInputValue(_purchaseInvoiceIdProperty);
var invoiceId = context.GetInputValue(_invoiceIdProperty);
var supplierId = context.GetInputValue(_supplierIdProperty);
var invoiceDateProperty = context.GetInputValue(_invoiceDateProperty);
var isAInOutProperty = context.GetInputValue(_isAInOutProperty);
// do async lookup and set properties
}
}
And also make sure that primaryProperty is not used within the Execute method. This will allow you to attach the rule to "multiple" properties as the primaryProperty is only used to control when the rule is executed.
Also note how I added strong typed property info's to the constructor. This allows me to keep the generic property info in member variables and in turn simplifies reading values from InputProperties. It also makes it easier to maintain as you will get a compilation error if you refactor a datatype or tries to use a wrong property type.
I see, now what if I have a property 5 which is not needed to load the object but it is set via the AsyncRule and I need to validate that it is valid. The validation rule will never be triggered as it is not a primary property of the Async Rule.
How can I trigger that validation rule on a property that does NOT trigger the async rule but it loaded by it.
Again - read my blog posts!
Remember - the async rules does not do validation - only lookup and (in my opinion) should only be called after a user has changed the value of PrimaryProperty.
Very helpful thanks
I have an async business rule that gets data from a table and sets other properties of the object, after these are set I want to validate that the required properties are set.
Here is what I did something like this
public class AsyncBusinessRule : BusinessRule { private Csla.Rules.IBusinessRule InnerRule1 { get; set; }
The problem seems to be that this code var chainedContext = context.GetChainedContext(this.InnerRule1); this.InnerRule1.Execute(chainedContext);
The inner rule contains the original value and not the affected one, if I put context.Complete(); before I call the GetChaingedContext.
What Am I doing wrong?