Try a collaborative solution before inheriting your CSLA Business Object!

Try a collaborative solution before inheriting your CSLA Business Object!

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


thomasc@magenic.com posted on Wednesday, September 27, 2006

When faced with the need to extend a CSLA Business Object, keep in mind that inheritance is not your only option.  Inheritance should be considered only after looking at using a solution using composition or collaboration.  Additionally, inheritance should only be considered when your intention is to extend behavior, not data (properties).

Consider the following scenario:  You write a CSLA Business Object, and your intention is to reuse that Business Object from multiple consuming applications.  Your intention is that the ValidationRules will differ in each of those sites. 

A first thought might be to actually write a "BaseCustomer" class and derive from that class for each site implemention, override the AddBusinessRules method.  Interesting thought, but have you tried trying to extend a CSLA Business Object's behavior?  Tough...and it will leave you wanting to try something else.

Here is a solution that Rocky and I worked out this week.  Rocky encouraged me to attempt a collaborative solution, and this is an implementation of his idea that I thought was pretty cool:  Use a delegate to apply the business rules.  Here's a sample:

Declare a delegate in your CSLA Business Object file (outside of the class definition), representing the function signature of the class containing your rules:

internal delegate void ApplyRulesDelegate(ValidationRules rules);

Remember that declaring a delegate is like writing a class.  The above code is actually a declaration of a class, that inherits from System.Delegate.

Declare the delegate as a static object reference within your class as such:

private static ApplyRulesDelegate customerRules;

Within your AddBusinessRules function, simply invoke the delegate as such, passing your ValidationRules collection to the delegate as a reference:

protected override void AddBusinessRules()

{

      customerRules.Invoke(this.ValidationRules);

}

Write a static method in your CSLA Business Object to set the type containing your CLSA Business Rules as such:

public static void UseRulesFrom(Type rulesType){

      MethodInfo mInfo = rulesType.GetMethod("SetRules")

      customerRules= (ApplyRulesDelegate)Delegate.CreateDelegate(typeof(ApplyRulesDelegate), mInfo, true);

}

Write a class to encase your varying behavior:

public class ExtendedCustomerBusinessRules

{

public static void SetRules(ValidationRules rules)

{

rules.AddRule(new RuleHandler(CommonRules.StringRequired), "FirstName");

rules.AddRule(new RuleHandler(CommonRules.StringMaxLength),

new CommonRules.MaxLengthRuleArgs("FirstName", 30));

}

}

Rocky's suggestion was to set this property once at the load of the AppDomain.  Assuming you are writing a web-app, you can pull this off by wiring up the CurrentDomain.AssemblyLoad Method in the global.asax like this:

AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(MyAssemblyLoadEventHandler);

and here would be how to set the rules library:

static void MyAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs args)

{

BO.CustomerUseRulesFrom(typeof(BO.ExtendedCustomerBusinessRules));

}

The nice thing about this solution is that its dynamic.  As you write Business Rule extensions for your object, you simply plug them in.  No nasty case logic in your business objects!

justncase80 replied on Wednesday, September 27, 2006

Rather than passing in the Type and using reflection to set the delegate couldn't you just use the delegate itself as the parameter instead, something more like (using the ValidationRules collection rather than a string) :

namespace ConsoleApplication1
{
	//This would have a validation rules collection parameter instead
	public delegate void ApplyRulesDelegate(string test);

	class Program
	{
		public static void Main(string[] args)
		{
                        //this would be called in the app domain load.
			UseRulesFrom(MyCustomRules.ApplyRules);

			Program p = new Program();
			p.ApplyRules();

			Console.ReadLine();
		}

		public void ApplyRules()
		{
			//You would pass in your ValidationRulesCollection here
			ProgramRules("Hello World!");
		}

		private static ApplyRulesDelegate ProgramRules;

		public static void UseRulesFrom(ApplyRulesDelegate rules)
		{
			ProgramRules = rules;
		}
	}

	public class MyCustomRules
	{
		public static void ApplyRules(string test)
		{
			//You would add your business rules to the validationrules collection ehre.
			Console.WriteLine(test);
		}
	}
}

You could even create an interface and use that to develop your business rule handlers:

public class MyCustomRules : IRulesPlugin
{
	public void ApplyRules(string test)
	{
		//You would add your business rules to the validationrules collection ehre.
		Console.WriteLine(test);
	}
}

public interface IRulesPlugin
{
	void ApplyRules(string Test);
}

//in Main call this:
UseRulesFrom(new MyCustomRules());

thomasc@magenic.com replied on Wednesday, September 27, 2006

I considered passing the delegate, but if you do that, you have to expose the Delegate type publicly, so the business object consumer can resolve the type.

In doing that, you are forcing the business object consumer to have a specific knowledge of the technical inplementation.  In my example, that is true also, but to a lessor degree.  In my example, the BO Consumer must know which "RulesEngine" he wants to apply.

Your second idea works also, the one about the interface, but again, you'd need to expose the interface type publicly, so it can resolve, thus this idea also lends itself to a degree of understanding about how the solution is implemented.

I do see the value in not applying the reflection, so maybe my points are negated by that. :)

justncase80 replied on Wednesday, September 27, 2006

Well in either case you're talking about creating a class that has a single method that creates the desired business rules, it seems to me that you'd actually have to know less about the technical implementation with this system because you have the interface there to guide you. It seems like "just knowing" that your method has to be called "SetRules" is less intuitive then implementing an interface. And actually if you explicitly implement the interface like this:

public class MyCustomRules : IRulesPlugin
{
	void IRulesPlugin.ApplyRules(string test)
	{
		//You would add your business rules to the validationrules collection ehre.
		Console.WriteLine(test);
	}
}

Then your ApplyRules method will actually be private but when you cast the MyCustomRules object into an IRulesPlugin you will be able to access it still, so you don't have to expose anything undesireable publicly.

To me that is one of the most powerful uses of interfaces... very cool!

Copyright (c) Marimer LLC