Dynamically loaded validation rules?

Dynamically loaded validation rules?

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


stacy.odell posted on Monday, May 12, 2008

I have a need to define validation rules in a database and have them loaded and applied at run time to my objects.

For example, in the database I would specify the property name, the rule, and (optionally) the value -- for example, property "MyString", rule "StringMaxLength", value 10.

Along those lines, I would also like to define dependencies between rules in the database as well (example:  if the value of this one boolean property is True, then make sure the length of this other string property is a maximum of 30)

Has anyone done anything like this (CSLA 3.0.4)?  any examples you'd like to share?

RockfordLhotka replied on Tuesday, May 13, 2008

The rule implementations need to be written in code. But assuming you have that, then associating the rules with properties can be done from metadata.

What you need to know in the database are:

  1. Business class name
  2. Property name
  3. Name of assembly containing rule class
  4. Name of rule class
  5. Name of rule method
  6. Name/value list of parameters for rule

Items 1 and 2 are needed to identify which rules go with which properties of course.

Items 3-5 are necessary so you can use reflection to get at the specific rule method metadata and then create a delegate reference to that method.

Item 6 provides the parameters for the rule method. In CSLA .NET 3.0 I introduced the idea of DecoratedRuleArgs, which you can read about in the Using CSLA .NET 3.0 ebook. The purpose is specifically to make your scenario easier. The rules in CommonRules now all use DecoratedRuleArgs, so you can pass in parameters as name/value pairs instead of having to use the strongly-typed RuleArgs subclass for each rule.

stacy.odell replied on Wednesday, July 02, 2008

Excellent!  Just what I was looking for.  This will help me to dynamically define validation rules in a database and apply them at run time.

Now for the second part of my question:  how can I dynamically define dependencies between properties in a database and apply them at run time?

gregmm replied on Thursday, July 03, 2008

Lhotka is it possible to let user wrte his own businessrule using some kind of scripting?

ajj3085 replied on Monday, July 07, 2008

What kind of dependencies are you talking about?  Foreign key constraints, or something else?

stacy.odell replied on Monday, July 07, 2008

ajj3085:
What kind of dependencies are you talking about?  Foreign key constraints, or something else?

 

Dependent properties, a la "if the value of property A changes, automatically run the validation code associated with property B"

ajj3085 replied on Monday, July 07, 2008

See ValidationRules.AddDependentProperty

stacy.odell replied on Monday, July 07, 2008

ajj3085:
See ValidationRules.AddDependentProperty

 

Doh!  (slaps forehead) I knew that.  I guess until now I just never put two and two together and realized all I need are the property names and the bi-directionalness in the database.

 

Thanks for pointing out the obvious.  :)

stacy.odell replied on Tuesday, December 09, 2008

RockfordLhotka:

The rule implementations need to be written in code. But assuming you have that, then associating the rules with properties can be done from metadata.

What you need to know in the database are:

  1. Business class name
  2. Property name
  3. Name of assembly containing rule class
  4. Name of rule class
  5. Name of rule method
  6. Name/value list of parameters for rule

Items 1 and 2 are needed to identify which rules go with which properties of course.

Items 3-5 are necessary so you can use reflection to get at the specific rule method metadata and then create a delegate reference to that method.

Item 6 provides the parameters for the rule method. In CSLA .NET 3.0 I introduced the idea of DecoratedRuleArgs, which you can read about in the Using CSLA .NET 3.0 ebook. The purpose is specifically to make your scenario easier. The rules in CommonRules now all use DecoratedRuleArgs, so you can pass in parameters as name/value pairs instead of having to use the strongly-typed RuleArgs subclass for each rule.

 

Thanks for this Rocky.  I have an associate who is doing this work and is struggling with getting the reflection stuff to work (or even compile).  Can you or anyone else provide me an example of how to dynamically load an instance rule, if we know the assembly, method name, property name, and parameters?

rsbaker0 replied on Tuesday, December 09, 2008

Incidentally, note that if you are doing per-Type rules this way (e.g. dynamic from the database), that you will run into issues if you change your database connection at runtime to one that has different rules. Basically, there really isn't a good way to clean out the rules cache once it has been initialized/instantiated.

I chose to have my application exit and relaunch itself if the user changes the database connection to a different database to solve this issue.

Fintanv replied on Tuesday, December 09, 2008

Something along the line of:

            string method = string.Format("Add{0}Rule", rule.Rule.ToString());

            if (!string.IsNullOrEmpty(method))
            {
                MethodInfo mi = typeof(Library.Validation.CommonRules).GetMethod(method, BindingFlags.NonPublic | BindingFlags.Static);
                if(mi != null) mi.MakeGenericMethod(rule.Datatype).Invoke(null, new object[] { item, property, rule });
            }

where:
- item is a business object that has ValidationRules
- property is an object of type IPropertyInfo
- rule is your rule handler

Then for rules such as MinLength you would have something like:

        static void AddMinLengthRule(IValidation item, Csla.Core.IPropertyInfo property, Validation.ItemDetailRuleInfo rule)
        {
            int value = Csla.Utilities.CoerceValue<int>(rule.Parameters[0].GetType(), null, rule.Parameters[0]);
            item.ValidationRules.AddInstanceRule(Validation.CommonRules.MinLength, new Validation.CommonRules.MinLengthRuleArgs(property, value, rule.Severity));
        }

rob.polak replied on Tuesday, December 09, 2008

So I am the one working on this.. this is roughly what I have..  The problem I am having is wiring up the MethodInfo to the AddInstanceRule().

String NameSpace = "CustomValidation"; //namespace of the class we are calling
Assembly assembly = Assembly.GetExecutingAssembly(); //get the assemby for the library class
Type t = assembly.GetType(NameSpace, true); //get the type from the method
if (t == null) throw new Exception("Invalid NameSpace in Custom Validation");

List<Type> methodParams = new List<Type>(); //start building the parameter structure
methodParams.Add(typeof(object)); //object (0)
methodParams.Add(typeof(DecoratedRuleArgs)); //RuleArgs (1)
MethodInfo mthd = t.GetMethod("ValidateBoldString", methodParams.ToArray()); //get the method info for this method
if (mthd == null) throw new Exception("Cannot find Custom Validation Method");

Delegate d = Delegate.CreateDelegate(t, mthd);
//get the data we are going to pass into the Validation method
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("Bold", true);
dict.Add("String", "TestString123");

//build the decorated rule args
DecoratedRuleArgs args = new DecoratedRuleArgs("value", dict);

//add the rule
ValidationRules.AddInstanceRule((RuleHandler)d, args);




rob.polak replied on Wednesday, December 10, 2008

So I found my blunder.. the problem was my Delegate did not match the RuleHandler specification

I was using
delegate bool CustomValidationDelegate(Object o,DecoratedRuleArgs d);

Which should have been
delegate bool CustomValidationDelegate(Object o,RuleArgs d);

//doh

Fintanv replied on Wednesday, December 10, 2008

Aside from the code I included in my last post, in my AddInstanceRule method I do the following:

            Validation.ItemRuleList ruleList = Validation.ItemRuleList.GetList(itemCode);
            foreach (Validation.ItemRuleInfo rule in ruleList.GetRules())
            {
                Validation.CommonRules.AddDynamicRule(this, ValueProperty, rule);
            }

The ItemRuleList is a ReadOnly BO collection that pulls the rule metadata from the DB.  The AddDynamicRule is the method that contains the first section of code in my prior email.

The part that I struggled with was due to the use of generic rule handlers.  mi.MakeGenericMethod solved the problem.

Copyright (c) Marimer LLC