I'm using CSLA version 3.7.1 and I'm having trouble with validation rule priority.
I'm trying to use the priority to defer database hits until the other basic rules pass, but it seems that no matter what I do that validation is executed every time.
I read about the new RuleSeverity property, so I added that to the StringRequired rules, but that didn't change anything.
Here's the code:
Protected
Overrides Sub AddBusinessRules() ValidationRules.AddRule(AddressOf Validation.CommonRules.StringRequired, New Csla.Validation.RuleArgs(LastNameProperty, Validation.RuleSeverity.Error), 0)
ValidationRules.AddRule(AddressOf Validation.CommonRules.StringMaxLength, New Validation.CommonRules.MaxLengthRuleArgs(LastNameProperty, 50), 0)
ValidationRules.AddRule(AddressOf Validation.CommonRules.StringRequired, New Csla.Validation.RuleArgs(FirstNameProperty, Validation.RuleSeverity.Error), 0)
ValidationRules.AddRule(AddressOf Validation.CommonRules.StringMaxLength, New Validation.CommonRules.MaxLengthRuleArgs(FirstNameProperty, 50), 0)
ValidationRules.AddRule(AddressOf Validation.CommonRules.StringRequired, New Csla.Validation.RuleArgs(UserNameProperty, Validation.RuleSeverity.Error), 0)
ValidationRules.AddRule(AddressOf Validation.CommonRules.StringMaxLength, New Validation.CommonRules.MaxLengthRuleArgs(UserNameProperty, 50), 0)
ValidationRules.AddRule(Of User)(AddressOf ValidateUniqueUserName(Of User), UserNameProperty, 1)
ValidationRules.AddRule(Of User)(AddressOf ValidateGroupID(Of User), GroupIDProperty, 1)
End Sub
Private Shared Function ValidateUniqueUserName(Of T As User)(ByVal target As T, ByVal e As Validation.RuleArgs) As Boolean
If User.Exists(target.GetProperty(Of String)(UserNameProperty), target.GetProperty(Of Integer)(IDProperty)) Then
e.Description = "That UserName has already been used!"
e.Severity = Validation.RuleSeverity.Error
Return False
Else
Return True
End If
End Function
When I try to save a blank object, I get:
Object is not valid and can not be saved:
-Last Name required
-First Name required
-UserName required
-Please select a Group for this user.
When I try to save an object that only has the UserName field filled out (and populated with a UserName that has already been used), I get:
Object is not valid and can not be saved:
-Last Name required
-First Name required
-Please select a Group for this user.
-That UserName has already been used!
indicating that all of the validation is executing, not just the 0 priority items.
Any ideas?
StringRequired won't cut it, because it won't set e.StopProcessing = true. You'll need your own rule method, which can delegate to StringRequired, but if e.Result is false will have to set e.StopProcessing to true. Something like this:
private static bool StringRequiredStop( object target, RuleArgs e ) {
var result = CommonRules.StringRequired( target, e );
e.StopProcessing = !result;
return result;
}
That's not right Andy - it should work.
By default all pri 0 rules run (barring any explicit StopProcessing value).
If any pri 0 rules fail, then NO pri 1+ rules should run.
If all pri 0 rules pass, then ANY failing pri 1+ rule should automatically stop subsequent processing.
Here's a simple test app that illustrates how it works. Just change the return value of the Pri0Rule() method to true or false to control whether the Pri1Rule() is invoked.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Csla;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var obj = DataPortal.Create<TestItem>();
Console.ReadLine();
}
}
[Serializable]
public class TestItem : BusinessBase<TestItem>
{
public static PropertyInfo<string> NameProperty = RegisterProperty<string>(c => c.Name);
public string Name
{
get { return GetProperty(NameProperty); }
set { SetProperty(NameProperty, value); }
}
protected override void AddBusinessRules()
{
base.AddBusinessRules();
ValidationRules.AddRule<TestItem>(Pri0Rule, NameProperty, 0);
ValidationRules.AddRule<TestItem>(Pri1Rule, NameProperty, 1);
}
private static bool Pri0Rule<T>(T target, Csla.Validation.RuleArgs e) where T: TestItem
{
Console.WriteLine("Pri0Rule");
return false;
}
private static bool Pri1Rule<T>(T target, Csla.Validation.RuleArgs e) where T : TestItem
{
Console.WriteLine("Pri1Rule");
return true;
}
}
}
Ahh, I think I see the misunderstanding.
Automatic rule short-circuiting only works within the scope of a single property.
So my earlier post listing how this works applies to rules for a SINGLE property. Each property is evaluated independently from other properties.
If you stop and think about it, this is the only way it can work. A user may set properties in any order, or not at all. When a property is set, that triggers running rules for that property.
When you call ValidationRules.CheckRules() to check all rules, it basically loops through all the properties on the object, calling CheckRules() for each property - still evaluating each property individually (and ignoring dependent properties for efficiency).
Yeah, that makes sense.
I still think there is value in avoiding unnecessary database hits whenever possible, so is Andy's solution the best way to accomplish that, or is there a better method?
Thanks for the clarification!
dmbRedGetta:Yeah, that makes sense.
I still think there is value in avoiding unnecessary database hits whenever possible, so is Andy's solution the best way to accomplish that, or is there a better method?
Thanks for the clarification!
Your current code should work as is. No need to change it.
For testing just fill in everything except UserName.
Since StringRequired for UserName is priority 0 it will fail and then the DB hit will not occur since it is priority 1.
(Of course the GroupID database hit will always occur as there are no priority 0 rules for the same property!)
Joe
Copyright (c) Marimer LLC