Business Rule Shortcomings in the framework

Business Rule Shortcomings in the framework

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


david.wendelken posted on Wednesday, May 02, 2007

One of the main reasons I settled on CSLA was for its integrated support for business rules.  Overall, it does a very fine job.

That said, here are some issues that I have with the way business rules are handled:

I'll cover these points in detail:

ReadOnlyBase does not support business rules.

In prior versions, all rules had an implied severity = Error.  A broken rule prevented the object from persisting its data.  Therefore, a read-only version of that object, by definition, had no broken rules.

In the current version, rules can also have a severity of Info or Warning, and objects can persist their data at those severity levels.  Thus, a read-only version can have broken rules!  Just because the user of that object cannot change the data does not mean they don't want to know about its problems!

The publicly available information about rules in the object is incomplete.

If I ask the framework for a list of rules, I get a collection of items formatted like this:

rule://TheRuleName/ThePropertyName?theFirstParameterName=Value1&theSecondName=Value2

The severity and priority of the rule is not included in the above format.  

I would accept the argument that priority is a technical implementation choice and should not be made public.  (Although it would be useful for those testing the application to know...)

But severity is clearly a business issue.   Since the rule information uses URL format, maybe we could put the severity level in the port slot, with 0,1,2 as the values. (0=error, 1 = warning, 2 = info)

rule://TheRuleName:80/ThePropertyName?theFirstParameterName=Value1&theSecondName=Value2

The publicly available information about CommonRules is insufficient to display a meaningful list of rules to the user.

There is real business value in enabling the user to get a list of business rules that are being enforced.  With the proper UI implementation of this knowledge, training times to use a new application can be reduced.  So can product support costs.

I think we would all agree that presenting to users the list of business rules the object enforces in the following format is less than user friendly. :)

rule://TheRuleName:80/ThePropertyName?theFirstParameterName=Value1&theSecondName=Value2

We need to be able to turn this back into a useful error message in the user's language.  For the rules that come in CommonRules, there is no way to programmatically deduce which resource key goes with which rule.  A human can figure it out at a glance, but a program cannot.  The names of the rules and the names of the error message key do not exactly agree.

I suggest a convention for naming the rule error message like this:

ruleTheRuleName

This has the additional advantage of putting all the rule error messages together in the resource file instead of having them scattered around along with other messages.

Second, even if the correct base error message string is retrieved, it will look something like this:

{0} must be greater than {1} and less than {2}.

Or, it might have been written:

{0} must be less than {1} and greater than {2}.

Now, any human might, after a bit of thought, be able to deduce the correct replacements.  But a general purpose program can have no way of knowing which of these {} tags represents the property name, the first parameter or the second parameter.

I suggest that the error messages be written in the following manner:

{rulePropertyName} must be greater than {theFirstParameterName} and less than {theSecondParameterName}.

{rulePropertyName} must be less than {theSecondParameterName} and greater than {theSecondParameterName}.

This would mean, of course, that String.Format wouldn't work inside the rule code, and that String.Replace would have to be used instead.

Examples of Use

The CslaContrib project has a C# sub-project that has re-implemented CommonRules (as StdRules) using this method.  (I have only converted the English resource files at this time.) 

RuleBusinessBase is a subclass of BusinessBase, and it exposes the collection of Rules (and Broken Rules) as properties.

PublicRuleInfo and PublicRuleInfoList contain the list of business rules in human-readable format.

I had to jump thru some hoops to do the coding because my code is in an extension DLL, not the CSLA dll itself.  Obviously, the code might be simpler if it had direct access to the data it needed.  It might also be simpler if I were a more experienced C# programmer, but we all learn by doing. :)

I have not added rule capability to RuleReadOnlyBase like I did with RuleBusinessBase.  I decided to create a new subclass, called RuleReadOnlyRuledBase (horrible name! yuck!) that would implement rule support.  That way, if an read-only object had no warning/info rules, it wouldn't have the overhead associated with them.

 

RockfordLhotka replied on Wednesday, May 02, 2007

I missed this post earlier, so here are my comments:

david.wendelken:

ReadOnlyBase does not support business rules.

In prior versions, all rules had an implied severity = Error.  A broken rule prevented the object from persisting its data.  Therefore, a read-only version of that object, by definition, had no broken rules.

In the current version, rules can also have a severity of Info or Warning, and objects can persist their data at those severity levels.  Thus, a read-only version can have broken rules!  Just because the user of that object cannot change the data does not mean they don't want to know about its problems!

I suggest using a BB-derived object with read-only properties for this purpose. The goal of ROB is to provide for very light-weight and fast data retrieval. There's a fair amount of unavoidable cost to incorporating validation rule support into the base class, and I don't want ROB to slow down.

BB already has all the functionality you want, and if you build a BB object with read-only properties you get the ROB behavior of a read-only object, but with all the validation behaviors you are after.

david.wendelken:

The publicly available information about rules in the object is incomplete.

If I ask the framework for a list of rules, I get a collection of items formatted like this:

rule://TheRuleName/ThePropertyName?theFirstParameterName=Value1&theSecondName=Value2

The severity and priority of the rule is not included in the above format.  

I would accept the argument that priority is a technical implementation choice and should not be made public.  (Although it would be useful for those testing the application to know...)

But severity is clearly a business issue.   Since the rule information uses URL format, maybe we could put the severity level in the port slot, with 0,1,2 as the values. (0=error, 1 = warning, 2 = info)

rule://TheRuleName:80/ThePropertyName?theFirstParameterName=Value1&theSecondName=Value2

The issue here is that severity is set by the rule method. So even if severity were included here, all you'd get is the most recent severity returned by the rule. That could be quite unpredictable for rules that change the severity depending on how the rule is broken.

Priority, on the other hand, can't change over time, and so it could be included here in a meaningful way. As you note however, its value is limited to testing, because it is hard to imagine how you'd use it at runtime.

david.wendelken:

The publicly available information about CommonRules is insufficient to display a meaningful list of rules to the user.

[...]

We need to be able to turn this back into a useful error message in the user's language.  For the rules that come in CommonRules, there is no way to programmatically deduce which resource key goes with which rule.  A human can figure it out at a glance, but a program cannot.  The names of the rules and the names of the error message key do not exactly agree.

[...]

Unfortunately this problem (which I'm not sure I fully understand to be honest) may get worse in 3.0, due to the introduction of DecoratedRuleArgs.

There's another problem with rule methods that you haven't mentioned, which is that the use of strongly typed RuleArgs subclasses makes code generation (or at least designer support) really hard. So I'm solving that issue by using the decorator design pattern in the form of DecoratedRuleArgs, which is a subclass of RuleArgs.

DecoratedRuleArgs is a dictionary of name/value pairs, with each name corresponding to a parameter required by the rule method, and each value containing the value for that property.

The CommonRules rule methods now all use DecoratedRuleArgs to do their work, which means that a code generator and/or designer tool can now use a simple grid (or similar control like the VS property control) to get the required arguments from the user and pass them through to the rule method.

For backward compatibility, the strongly typed RuleArgs subclasses continue to work - but they now subclass DecoratedRuleArgs and load the dictionary with values rather than using their own fields. I thought that was a clever solution Smile [:)]

A side-effect of this however, is that the order of the parameter values is no longer determinate. In other words, you can't count on there being a "first parameter" and "second parameter" because someone could load the dictionary in reverse or random order.

On the upside, 3.0 now includes a RuleDescription class that understands how to parse the rule:// URI in a friendly way. It is similar to System.Uri, but is tailored to the specific implementation of the rule:// URI contents. It doesn't solve your issue (I don't think), but it certainly makes working with rule:// URIs much easier. (thanks to Magenic's Brant Estes for helping put this together)

david.wendelken replied on Wednesday, May 02, 2007

RockfordLhotka:

I missed this post earlier, so here are my comments:

david.wendelken:

ReadOnlyBase does not support business rules.

In prior versions, all rules had an implied severity = Error.  A broken rule prevented the object from persisting its data.  Therefore, a read-only version of that object, by definition, had no broken rules.

In the current version, rules can also have a severity of Info or Warning, and objects can persist their data at those severity levels.  Thus, a read-only version can have broken rules!  Just because the user of that object cannot change the data does not mean they don't want to know about its problems!

I suggest using a BB-derived object with read-only properties for this purpose. The goal of ROB is to provide for very light-weight and fast data retrieval. There's a fair amount of unavoidable cost to incorporating validation rule support into the base class, and I don't want ROB to slow down.

BB already has all the functionality you want, and if you build a BB object with read-only properties you get the ROB behavior of a read-only object, but with all the validation behaviors you are after.

Brilliant!  Duh!  (Duh! is addressed to me.)

RockfordLhotka:

david.wendelken:

The publicly available information about rules in the object is incomplete.

If I ask the framework for a list of rules, I get a collection of items formatted like this:

rule://TheRuleName/ThePropertyName?theFirstParameterName=Value1&theSecondName=Value2

The severity and priority of the rule is not included in the above format.  

I would accept the argument that priority is a technical implementation choice and should not be made public.  (Although it would be useful for those testing the application to know...)

But severity is clearly a business issue.   Since the rule information uses URL format, maybe we could put the severity level in the port slot, with 0,1,2 as the values. (0=error, 1 = warning, 2 = info)

rule://TheRuleName:80/ThePropertyName?theFirstParameterName=Value1&theSecondName=Value2

The issue here is that severity is set by the rule method. So even if severity were included here, all you'd get is the most recent severity returned by the rule. That could be quite unpredictable for rules that change the severity depending on how the rule is broken.

Priority, on the other hand, can't change over time, and so it could be included here in a meaningful way. As you note however, its value is limited to testing, because it is hard to imagine how you'd use it at runtime.

That's an interesting concept. When I read your rule changes for 2.1, I assumed we should set the severity when the rule is added to the validation list.  You are having the rule set the severity at rule execution runtime.  To me, that would have been two rules:

If the framework is going to keep the concept that the innards of the rule decide whether it's an error or not, rather than the assignment of the rule to the object, you are right, it can't be included in the public info.  Conceptually, I don't agree with you, though. :)  To me, the business policies and rules come first, and the objects exist to instantiate them.  

RockfordLhotka:

david.wendelken:

The publicly available information about CommonRules is insufficient to display a meaningful list of rules to the user.

[...]

We need to be able to turn this back into a useful error message in the user's language.  For the rules that come in CommonRules, there is no way to programmatically deduce which resource key goes with which rule.  A human can figure it out at a glance, but a program cannot.  The names of the rules and the names of the error message key do not exactly agree.

[...]

Unfortunately this problem (which I'm not sure I fully understand to be honest) may get worse in 3.0, due to the introduction of DecoratedRuleArgs.

There's another problem with rule methods that you haven't mentioned, which is that the use of strongly typed RuleArgs subclasses makes code generation (or at least designer support) really hard. So I'm solving that issue by using the decorator design pattern in the form of DecoratedRuleArgs, which is a subclass of RuleArgs.

DecoratedRuleArgs is a dictionary of name/value pairs, with each name corresponding to a parameter required by the rule method, and each value containing the value for that property.

The CommonRules rule methods now all use DecoratedRuleArgs to do their work, which means that a code generator and/or designer tool can now use a simple grid (or similar control like the VS property control) to get the required arguments from the user and pass them through to the rule method.

For backward compatibility, the strongly typed RuleArgs subclasses continue to work - but they now subclass DecoratedRuleArgs and load the dictionary with values rather than using their own fields. I thought that was a clever solution Smile [:)]

A side-effect of this however, is that the order of the parameter values is no longer determinate. In other words, you can't count on there being a "first parameter" and "second parameter" because someone could load the dictionary in reverse or random order.

On the upside, 3.0 now includes a RuleDescription class that understands how to parse the rule:// URI in a friendly way. It is similar to System.Uri, but is tailored to the specific implementation of the rule:// URI contents. It doesn't solve your issue (I don't think), but it certainly makes working with rule:// URIs much easier. (thanks to Magenic's Brant Estes for helping put this together)

Actually, the DecoratedRuleArgs sounds much better for what I want to do.  It should make what I want to do much easier!

The order of parameters in a rule URI isn't the issue, it's matching up those parameters to the {0}, {1} tags in the error message text in the resource string that is the problem.  That's why I want us to use {parameterName} and {rulePropertyName} tags instead of numeric {} tags.  That, along with resource keys that exactly match the rule name means we can parse the rule to get back a description for the rule.

I'll take a look at the 3.0 beta version this weekend.   If I've understood what you're telling me correctly, I may be able to quickly show you how to modify RuleDescription, common rules, and the resource file entries so that it can return a user-friendly message describing the rule.  If you like it, I'll do the leg work on updating the resource files in all the different languages, plus the common rules.  Should be able to knock out the C# version changes in a long evening. 

 

RockfordLhotka replied on Wednesday, May 02, 2007

Before replacing string.format with a series of string.replace calls, we need to evaluate the likely perf cost. It may be trivial, but it may not, and I don’t want to find out after the fact that n string.replace calls is a lot slower than one string.format call.

 

Rocky

david.wendelken replied on Wednesday, May 02, 2007

I'll set up a performance test to go along with it.

 

Remember, the String.Format/String.Replace calls only happen if the rule is broken.

Users can only type so fast! :)

The only way I see it might be a performance problem would be if we loaded a collection of thousands of objects, each of which had lots and lots of warning/info only broken rules.

My expectation is that it would usually turn out to be a trivial difference.

david.wendelken replied on Wednesday, May 02, 2007

RockfordLhotka:

Before replacing string.format with a series of string.replace calls, we need to evaluate the likely perf cost. It may be trivial, but it may not, and I don’t want to find out after the fact that n string.replace calls is a lot slower than one string.format call.

It just dawned on me that using the named tags {paramNameOne} is better for globalization purposes than the numbered {1} tags. 

With numbered tags, the translated message must keep the same relative order of tags in the message text.  That may cause some very unnatural translations, as sentence structure varies between different languages.

A typical English sentence is written in Subject - Verb - Object (SVO) order.

I am going to the market.

I believe some languages prefer to use Verb - Object - Subject (VOS) or Verb - Subject - Object order.

Going to market am I.

Wikipedia has a brief overview of these concepts.

http://en.wikipedia.org/wiki/Verb_Subject_Object

http://en.wikipedia.org/wiki/Subject_Object_Verb

etc.

 

 

RockfordLhotka replied on Wednesday, May 02, 2007

I think you misunderstand the way the numbered tags work in string.format.

 

The order of the tags doesn’t matter. The tag number (0, 1, 2, etc) is relative to the order of the parameter on the string.format call itself, and that is constant because it is hard-coded in the rule method.

 

So

 

String.format(“Hi {0}, {1}”, “Rocky”, “how are you”)

 

And

 

String.format(“Hello, {1} {0}?”, “Rocky”, “how are you”)

 

Both work, even though I’ve reversed the order of the 0 and 1 parameters in the format string.

 

Rocky

 

david.wendelken replied on Wednesday, May 02, 2007

RockfordLhotka:

I think you misunderstand the way the numbered tags work in string.format.

The order of the tags doesn’t matter. The tag number (0, 1, 2, etc) is relative to the order of the parameter on the string.format call itself, and that is constant because it is hard-coded in the rule method.

So

String.format(“Hi {0}, {1}”, “Rocky”, “how are you”)

And

String.format(“Hello, {1} {0}?”, “Rocky”, “how are you”)

Both work, even though I’ve reversed the order of the 0 and 1 parameters in the format string.

Rocky

Right you are.  My thinking cap had a short circuit in it.  That's my story and I'm sticking to it. :)

I do think

"Hi {personName}, {greetingPhrase}?"

and

"Hello, {greetingPhrase} {personName}?" 

are more self-documenting when it's known that the {} contain the relevant parameter names.

I'm writing up a performance test to check String.Format vs Replace().  (Can you tell I'm excited about this?)  I'm replacing two values, as 1, 2 or 3 values seem to be the most likely.

I'll start out by grabbing both resource strings, so the first method does not have to pay any startup cost to grab a resource.

Resource Strings:

MessageKeyFormat     {0} must be less than {1}. 
MessageKeyReplace    {rulePropertyName} must be less than {maxValue}. 

I'm using the StopWatch class to capture elapsed time.

StopWatch Frequency = 3579545

StopWatch HighResolution = true

private static void PerformFormatTest()
{
    formatStartTimestamp =
Stopwatch.GetTimestamp();

    for (int i = 0; i < 10000; i++)
   {
        int j = 50000;

        String s = String.Format(Properties.Resources.MessageKeyFormat, "Salary", j);
    }

    formatEndTimestamp = Stopwatch.GetTimestamp();

    System.Console.WriteLine("Format test length: "
                                                    + ((
long)(formatEndTimestamp - formatStartTimestamp)).ToString());

}

//     

 

private static void PerformReplaceTest()
{
    replaceStartTimestamp =
Stopwatch.GetTimestamp();
    for (int k = 0; k < 10000; k++)
    {
        int j = 50000;

        string s = Properties.Resources.MessageKeyReplace.ToString().Replace("{rulePropertyName}",     
                           "Salary"
).Replace("{maxValue}", j.ToString());

    }

    replaceEndTimestamp = Stopwatch.GetTimestamp();

    System.Console.WriteLine("Replace test length: "
                            + ((
long)(replaceEndTimestamp - replaceStartTimestamp)).ToString());

}

Test Results

Here are the results, averaged over 10 runs of 10,000 iterations, each of which represents a broken rule:

Format Test:    56167

Replace Test:  70973

Typical time to perform the loop, instantiate an int variable, and instantiate a string with just the properties.resources text in it was approximately 15000, so the remainder represents the string processing to do the subsitute and replace.

Average time over 10 runs of 100 iterations, each of which represents a broken rule:

Format Test: 674

Replace Test: 771

There may be faster ways to write the replace code, I'm still learning C# nuances.

 

 

 

david.wendelken replied on Tuesday, May 08, 2007

I took a look at RuleDescription this weekend.  It looks like a perfect candidate for the extension I have in mind.  (But I've had a tooth-ache so I haven't coded it yet.  Hard to concentrate!)

Question:

I don't want to put my custom rule messages into the CSLA Properties.Resources file because they would get overwritten by a new release.

I don't want to put them into my own application's resource file because RuleDescription won't be able to find it - it does not recieve "rule DLL parentage information".

I'm thinking of adding a CustomProperties folder to CSLA and putting a resources file in it.

Rule Description, when putting together a human-ready error message, would first look in the standard resources file, and if not successful, look in the custom resources file.

Anyone got a better idea?

 

 

RockfordLhotka replied on Tuesday, May 08, 2007

This is not the sort of thing I'd put into the core framework. The reason is that the rule descriptions (rule://) are static information about the rule methods. They can't contain information about the human-readable description, or the severity, because those things can be changed by the rule itself.

You can easily envision a rule that returns various possible human-readable descriptions depending on how the rule is broken - and each of them might be at a different severity.

You can even envision a rule method that invokes other rule methods, aggregating their results together into a consolidated result. That one is tricky, because the "sub-rules" would never even show up in the rule:// list. Odds are this hypothetical "router rule" would call specific sub-rules depending on the state of the object at the time, so there's no static way to know which ones would be called when.

The rule infrastructure is really quite a lot more dynamic than it might first appear, and I don't want to do anything that would limit or restrict that flexibility.

david.wendelken replied on Wednesday, May 09, 2007

I'm coming at this discussion from the point of view that an object should be self-documenting, that it should be able to present, upon demand, a list of the rules it enforces in human-readable format.  And that this capability has the ability, if properly harnessed, to provide significant business value.

As a framework architect and developer, you do not want to break existing code or limit your developers' technical options.

I believe that these two goals are not mutually exclusive.

RockfordLhotka:

You can easily envision a rule that returns various possible human-readable descriptions depending on how the rule is broken - and each of them might be at a different severity.

Yes I can, but I can equally envisage a general purpose explanation of the rule.  The general rule is what should be displayed when the user is presented a list of rules.  For example:

(General rule statement)

(Specific error messages in response to user input)

In theory, the rule might return all three of the above messages if the user was really not paying attention! 

Regardless, the general rule did not change regardless of the specifics of how the user broke the rule.

The rule the user must follow, and the specific coaching they receive upon breaking the rule are not quite the same thing. 

The current method of storing messages in resource files works great to handle this. In the example above, there would be four resource tags and their associated text messages.  (I'll use really long names for the tags to make the connections clearer):

RockfordLhotka:
The reason is that the rule descriptions (rule://) are static information about the rule methods. They can't contain information about the human-readable description, or the severity, because those things can be changed by the rule itself.

I disagree that the rule should decide whether violating it is an error or a warning.  The assignment of the rule to the object should decide. 

For example, in one situation, StringRequired might be an error, in another, it might be a warning.

This might raise a warning on an Employee object, but might raise an error on a GovernmentSecurityClearance object.

Your approach forces us to write two rules, because the rule decides instead of the object using it.

To counter that, from what I perceive to be your approach, you might argue that some user input might qualify as an error but other input might qualify as a warning in the same rule, and that my approach would require splitting up that rule function into two - one that would be assigned an error status by the object and one that would be assigned a warning status by the object.  Yep.  Exactly right.  It would!  These are, in fact, two separate rules in my view:

The above approach has the advantage of the object being able to instruct the user as to the rules it follows. 

The current method does not.

Based on your explanation, as I understand it, severity cannot be added to the rule uri because of how existing systems have been coded.  That makes sense to me.  (Doesn't make me happy, but I understand and agree with your call! :)

Incidentally, did you realize that the current method of assigning a rule is also flexible enough to specify the severity when the rule is assigned to the object?  The syntax is a bit clunky, but it can be done. :)

RockfordLhotka:

You can even envision a rule method that invokes other rule methods, aggregating their results together into a consolidated result. That one is tricky, because the "sub-rules" would never even show up in the rule:// list. Odds are this hypothetical "router rule" would call specific sub-rules depending on the state of the object at the time, so there's no static way to know which ones would be called when.

I can also envision providing a general rule statement covering said consolidated results.

For example, a GoldDigger object, when it's spouse property is set to a non-blank value, might trigger the following General Rule:

Our consolidation rule might call several complex functions, such as:

If we are unable to come up with a general purpose rule description, I would first argue that we might be asking a rule to do too much, much as we can ask an object to assume too many responsibilities.  Maybe it should really be two or more rules.  Aside from that, the only harm done would be the rule text message for that rule would be blank or default back to the rule URI.  Given that all rule text messages are blank now, that is hardly a problem. In fact, a configuration switch could be made to force a URI to always be listed, for those who do not want this feature or code in a manner inappropriate for it. :)

People who do not want to use this portion of the framework would simply ignore it.  People who wanted to use it would do so, and make sure their general rule messages made sense.

RockfordLhotka:

The rule infrastructure is really quite a lot more dynamic than it might first appear, and I don't want to do anything that would limit or restrict that flexibility.

I completely agree that the current approach is very flexible, and I certainly would not want you to add limitations to it! :)

I hope I've shown that the method I'm proposing is also flexible, and that it does not have to be implemented in a way that limits how the existing system works.  If I haven't done so to your satisfaction, please explain where I fell short.  It will probably simply have been failure of communication on my part.

Regardless, thanks for considering this!

RockfordLhotka replied on Thursday, May 10, 2007

david.wendelken:

I'm coming at this discussion from the point of view that an object should be self-documenting, that it should be able to present, upon demand, a list of the rules it enforces in human-readable format.  And that this capability has the ability, if properly harnessed, to provide significant business value.

As a framework architect and developer, you do not want to break existing code or limit your developers' technical options.

I believe that these two goals are not mutually exclusive.

I agree entirely that this is a good goal. Without restricting the types of rule methods that can be created, and with the limitation of globalization support, I'm not sure how to make this work in any easy way.

The RuleMethod class contains metadata for each property<->rule. This object is created based on the data you provide when you call ValidationRules.AddRule(), and so this is the sole entry point for setting up this sort of metadata.

It would be possible to add a Description value to RuleMethod, and of course a whole host of overloads to AddRule.

Then the rule:// URI could include __priority and __description as property values along with all the other properties specified by RuleArgs (perhaps __description only appears if it has been set). This should enable the scenario you are after, and would allow people like me who take a different approach to the rule method design to also provide a description if desired.

Since the description would be set by the business object when AddRule() is called, globalization becomes easy, because the value would come from the business assembly's resources.

The only issue I have is including __priority in the URI, because you know there'll be continual threads spawned as people wonder why that value is not consistent over time (when their rule methods change the value). That's a support headache I don't want or need. But perhaps RuleArgs can keep the original priority value in a field, and use that value to build the URI, rather than whatever the current value might be at the time.

david.wendelken replied on Friday, May 11, 2007

Adding Rule Description to the rule arguments would work for me.  Would you like me to ripple that thru the code for you?  I can get it to you by the end of next week.

Priority is a nice-to-have for the testers, but not essential.  If listed, it should be called "CurrentPriority" 

Severity would be far more useful to the users, but, as you've pointed out before, that's not a path that will work given the existing code base. If it were listed, I would call it CurrentSeverity.

 

 

david.wendelken replied on Friday, May 11, 2007

Ok, while getting my tooth drilled this morning, I did some more thinking about adding a RuleDescription property in RuleArgs.

There are good and bad points to it, as compared to the solution I proposed earlier, which would have the rule look up the description in the resources file and do the substitutions using {tags} that match the rule arguments.

For adding RuleDescription argument:

Against adding RuleDescription argument:

For using Rule Messages with resource keys that use tags that match argument names:

For using Rule Messages with resource keys that use tags that match argument names:

If I were building this for just my team, I would go with the approach I put forward.

If I were in your shoes, having to support lots of people all over the place, I would be sorely tempted to go with the RuleDescription argument you've proposed.

 

 

RockfordLhotka replied on Friday, May 11, 2007

I agree that it is not ideal to put the burden on the consumer of the rule.

 

Unfortunately I can’t figure out any realistic way to provide a rule description from the rule itself, given the following constraints:

 

1.       Globalization

2.       Rule descriptions (e.Description) could be hard-coded – they don’t have to come from resource files

3.       Rules can return different e.Description values based on how the rule is broken

 

Without the globalization requirement, I’d just use an attribute on the rule method and we’d be done. But there’s no way (that I know of) to localize the value passed into an attribute, because it must be a constant. And you can’t even pass in the name of the resource, because it is the attribute code that is resolving that name, so all rule description names would then need to be in Csla.dll (or associate resource files) and that’s not acceptable. The resource text should come from the assembly that implements the rule method.

 

The second issue is a big one too. I’m guessing a lot of people hard-code their rule description text into the rule method. I only use resources in CommonRules because CSLA is localizable, but when I’m building apps for US companies who aren’t worried about globalization of their code, I put the text directly into the rule method. There’s no way to extract that text to build the rule:// URI in this (most common) case.

 

And the third one is a show stopper on using e.Description, and I do this fairly often – especially when using severities. Different severities of a rule result in slightly different descriptions.

 

 

So another alternative, is to have an external cross reference table that translates a rule method name into a description. This would be outside CSLA itself, and would belong to your app. Since RuleDescription gives you the rule method name, it would be an easy thing for you to translate that to some rule description text based on your own resource file, database table, XML xref data or whatever.

 

I rather prefer this solution J

 

Rocky

david.wendelken replied on Friday, May 11, 2007

I found a way around the resource file problem.  If you have the appropriate object reference, you can get to the correct resources file.

You can see an example in the PublicRuleInfo class in CslaContrib.

Basically, PublicRuleInfoList (containing PublicRuleInfo) is instantiated inside RuleBusinessBase (which is a subclass of BusinessBase).  We pass in a reference to the object it belongs to.

PublicRuleInfo checks the Resources file that it belongs to for the message string that goes with the rule.  (The message string is keyed by the rule name in the URI.)

If it finds a match, uses that message string as the base for the rule description.

If it doesn't find a match, it uses the object reference to check that objects properties.

(I did it in this order on the assumption that there would be more hits on CommonRules than custom ones.)

Take a look at it and see if it wouldn't work as an approach.   My URI parsing code is a bit ratty compared to your new RuleDescription class, but that's because you know more than I do about this stuff!  (I'm planning to update the code as soon as I can using your example.)

Basically, if I put a GetResourceString in each class, it seems to work great.

 

david.wendelken replied on Friday, May 11, 2007

Take a look at PublicRuleInfo, PublicRuleInfoList and RuleBusinessBase and the associated Resources file in the CslaContrib project (in the CslaSrd sub-project).

I think I've found a way past the resources file problem, and in a way that does not put the burden of a general rule definition on the user of the rule.  It pulls in the rule text from the the standard rules resource file or the user's assembly resource file, as appropriate.

 

Copyright (c) Marimer LLC