Centralized Business Rules - Data Manipulation Rules

Centralized Business Rules - Data Manipulation Rules

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


JoeFallon1 posted on Wednesday, November 18, 2009

As Rocky has stated in the past, the term ValidationRules encompasses two concepts and so he feels it was named incorrectly. It should have been called BusinessRules so that Validation and Data Manipulation could both be included in it.

By Data Manipulation I mean changing the value of a Property of a BO will cause its rules to fire - one of the rules could be to Trim and Uppercase the value - this is a Data Manipulation rule or a Business rule.

If I write a rule called TrimAndUpperCaseString and put it directly in my BO then I can manipulate the private field (or managed field) without re-triggering the rules involved in a Property Set.

But now say that I want to write this rule once and put it in my library of rules so that I can call it from various BOs. There are some implications to that and I would like some feedback as to how others view this.

Public Function TrimAndUpperCaseString(ByVal target As Object, ByVal e As RuleArgs) As Boolean
  Dim ruleArg As MyRuleArgs = DirectCast(e, MyRuleArgs)
  Dim propertyName As String = ruleArg.PropertyName
  Dim newValue As String
  Dim value As String = CStr(CallByName(target, propertyName, CallType.Get))

  newValue = value.Trim.ToUpper
  If newValue = value Then
    'do nothing
  Else
    CallByName(target, propertyName, CallType.Set, newValue)
  End If
  Return True
End Function

Here are the implications that I have drawn.

1. Data Manipulation Rules are never broken.

2. They just modify the property value in some way and always return True.

3. Their value is the fact that whenever the Property is changed then the rules for that property get run.

4. This allows us to write a rule once and then re-use it many times rather than write code in the Property Set many times.

5. The one drawback to writing a general rule in My Rules instead of a specific rule in the BO is that the general rule will end up re-setting the property which causes the rule to be re-run. That is why we have to test if the newValue has been set once already - else we could end up in an infinite loop. If the rule is in a BO then we could simply set the field  directly and avoid the re-run issue.

6. In order to avoid re-running other rules too, the policy is that Data Manipulation Rules should always have priority = -1. That way they always get run first before the default priority = 0 rules and our DB hit priority=1 rules.

Is there a way to write Data Manipulation Rules centrally that do not cause the Property Set to re-run the rule?

Also, should Data Manipulation Rules really run at the lowest possible priority (-1 in my case) so that they occur before any Validation rules are run?

 Joe

 

 

Fintanv replied on Wednesday, November 18, 2009

JoeFallon1:

5. The one drawback to writing a general rule in My Rules instead of a specific rule in the BO is that the general rule will end up re-setting the property which causes the rule to be re-run. That is why we have to test if the newValue has been set once already - else we could end up in an infinite loop. If the rule is in a BO then we could simply set the field  directly and avoid the re-run issue.

I use the following general rule.  Because it uses the CallByName convention (CSLA version), it does not appear to trigger a re-firing of the rule check.  Even if it did, since the value of the property would only be modified once, a supsequent call would not change the value and not re-trigger an infinite loop. 

public static bool StringUpperTrim(object target, Csla.Validation.RuleArgs e)

{

string value = (string)Csla.Utilities.CallByName(target, e.PropertyName, CallType.Get);

if (!string.IsNullOrEmpty(value))

{

Csla.Utilities.CallByName(target, e.PropertyName, CallType.Set, value.ToUpperTrim());

}

return true;

}

JoeFallon1:

6. In order to avoid re-running other rules too, the policy is that Data Manipulation Rules should always have priority = -1. That way they always get run first before the default priority = 0 rules and our DB hit priority=1 rules.

I would not want this as I typically have a "StringRequired" rule as well as the Trim and Upper rule.  The string required rule fires first, and the trim/upper rule only runs if the string required rule passes.

JoeFallon1 replied on Wednesday, November 18, 2009

Thanks for the feedback. I should not have said infinite loop. I meant that if the property is changed that the rule will get run a second time. Your point about the second time is correct - since it is now the same value it will not run the rule a 3rd time. But it does run the rule twice. I tried your code with the CSLA CallByName and it acts the same way as VB.

By checking newValue=Value in the rule itself you avoid the 2nd reflection call which doesn;t do anything anyone, as you pointed out.

Good point on the StringRequired Rule. But shouldn't all string properties in a BO be initialized to ""? And even if they are Nothing doesn't casting them to string using Cstr make them ""? So it should be safe to call the ToUpper.Trim on it.

Also, if you run other rules before the DataManipulation rule then they will all get re-run when the data is changed! So my point is that Data Manipulation rules should run first to minimize the re-running of rules.

Joe

 

Peran replied on Thursday, November 19, 2009

Here are 2 ideas I was working on last night:

The below prevents any database access rules with a higher priory from running twice, as it uses the StopProcessing property to stop the first iteration of rules running in the knowledge that the second iteration will have re-run the rules.  The disadvantage is that if you have a few rules like this it could get confusing to dubug with rule loops running inside rule loop runing inside rule loops...

        public static bool ToUpperTrim1(object target, Csla.Validation.RuleArgs e)
        {
            string value = (string)Utilities.CallByName(target, e.PropertyName, CallType.Get);

            if (value != null)
            {
                string newValue = value.ToUpper(CultureInfo.CurrentCulture).Trim();

                if (value != newValue)
                {
                    // this property set will cause the validation rules to run again...
                    Utilities.CallByName(target, e.PropertyName, CallType.Set, newValue);

                    // ...so stop this iteration as we know that another iteration has just run
                    e.StopProcessing = true;
                }
            }

            return true;
        }

The second idea was to use IManageProperties to update the value without causing the rules to rerun at all.  This does however break encapsulation (using an explicit interface, so it's not quite as bad!) and requires IManageProperties to be changed from internal to public.  You would need to run the StringRequired rule after this rule, incase the user just entered some space characters.

    public class StringRuleArgs : Csla.Validation.RuleArgs
    {
        #region constructors

        public StringRuleArgs(PropertyInfo<string> propertyInfo)
            : base(propertyInfo)
        {
            this.PropertyInfo = propertyInfo;
        }

        #endregion

        #region proeprties

        public PropertyInfo<string> PropertyInfo { get; private set; }

        #endregion
    }

        public static bool ToUpperTrim2(object target, Csla.Validation.RuleArgs e)
        {
            var args = (StringRuleArgs)e;
            var businessObject = (IManageProperties)target;

            string value = businessObject.ReadProperty(args.PropertyInfo);

            if (value != null)
            {
                string newValue = value.ToUpper(CultureInfo.CurrentCulture).Trim();
                businessObject.LoadProperty(args.PropertyInfo, newValue);
            }

            return true;
        }

I have not decided wheather to use these or not...what are your thoughts?


Peran

JoeFallon1 replied on Thursday, November 19, 2009

Thanks!
Those are two very interesting approaches. I have not made any decisions yet either.

Rocky - can you please provide some feedback on the issue that I raised and these potential solutions? Plus any other thoughts on the subject. Like for example should there be an enumeration with ValidationRules and DataManipulationRules. Then the Rules engine could discriminate between the two types of rule and run the DataManipulationRules first but without triggering any events. Then the validation rules could run second.

Joe

 

rsbaker0 replied on Friday, November 20, 2009

JoeFallon1:

Thanks!
Those are two very interesting approaches. I have not made any decisions yet either.


Rocky - can you please provide some feedback on the issue that I raised and these potential solutions? Plus any other thoughts on the subject. Like for example should there be an enumeration with ValidationRules and DataManipulationRules. Then the Rules engine could discriminate between the two types of rule and run the DataManipulationRules first but without triggering any events. Then the validation rules could run second.


Joe


 



Yes, and while we're at it there was also this related thread in which we discussed at length that the rule should understand why it was being called (actual property change versus someone just calling CheckRules)...

http://forums.lhotka.net/forums/thread/36593.aspx

RockfordLhotka replied on Friday, November 20, 2009

This is an area I plan to work on for 4.0, as it will involve some breaking changes.

My current thinking includes a few changes:

  1. Rename the ValidationRules protected property to BusinessRules
  2. Eliminate the bool return from rule methods, instead returning the result via RuleArgs with the default being true (so business rules can ignore the whole thing, while validation rules will set e.Result = false for failure)
  3. Provide a RuleContext value in RuleArgs that allows you to get/set property values from the target object in an abstract manner, much the way async rules work today, but bi-directional
  4. With sync and async rules, after the rule completes (so the code is running on the UI thread), update changed property values from RuleContext back into the object
  5. Maybe call a lambda (Action<T>) after the rule completes and the code is running on the UI thread (whether sync or async)
  6. Allow "rule chaining", where a rule can call other rules, and can aggregate the results of those rules into RuleArgs so post-processing occurs against all the results
  7. Possibly use MEF to dynamically discover and load rule methods in a more abstract manner
  8. Add the concept of "rule domains", so a business type can have multiple rule sets, each belonging to a domain - this would support shared hosting scenarios for server apps and app servers
  9. If I can get the sync/async rule syntax to match by using RuleContext, I may use an [AsyncRule] attribute so you can indicate that a rule is required to run async by putting that attribute on the rule method
  10. Add a BusinessRuleAttribute (subclass of ValidationAttribute) that implements ObjectFactory-like protected members, making it easier to create a rule attribute that can get/set values in the target object

At a high level my goals are

  1. Support shared hosting scenarios
  2. Change naming/coding to better reflect business rules, not just validation
  3. Provide consistency between sync and async rules

As you can imagine, these changes will have at least some impact on existing rule methods and AddBusinessRules() methods. I hope that the changes are pretty mechanical (and thus easy to make), but this will improve readability and open up some new scenarios that are hard/impossible today.

You can imagine AddBusinessRules() looking more like this:

protected override void AddBusinessRules()
{
  base.AddBusinessRules();
  BusinessRules.AddRule(MyRule, MyProperty);
  BusinessRules.AddRule(MyRule, MyOtherProperty);
}

And you can imagine a rule looking like this:

private static void MyRule<T>(T target, RuleArgs e) where T : MyType
{
  if (some condition is met)
    return;
  else
  {
    e.Description = "oops";
    e.Result = false;
  }
}

or a mutator rule like this:

private static void MyRule<T>(T target, RuleArgs e) where T : MyType
{
  e.RuleContext.PropertyValues[e.PropertyName] = 
    e.RuleContext.PropertyValues[e.PropertyName].ToString().ToUpper();
}

 

ajj3085 replied on Friday, November 20, 2009

Are there any plans to seperate kinds of rules?  For instance, validation rules tend to look at the value, but don't change it, but other rules do change the values... so sometimes I would only want to run valedation rules (maybe after a DP_F), but I'd want both sets running in response to a property changed event.. maybe all business rules run prior to validation.

What do you think?

JoeFallon1 replied on Friday, November 20, 2009

Rocky,

Thanks for the detailed feedback on the future direction.

For the immediate 3.8 version could you please comment on what can be done right now? I tend to think the StopProcessing idea for Data Manipulation Rules is the simplest one. Along with setting their Priority to -1 so that they always run first. Under that scenario, a Data Manipulation Rule would run, change the value via reflection, stop processing the current loop and then re-start the loop due to the changed property set. But this time the Data Manipulation rule would not have to modify the value since newValue=value and the rest of the Validation rules would then run.

I will try to find time next week to look into this but I am in the middle of some other projects.

Joe

 

paupdb replied on Thursday, November 26, 2009

What of authorisation rules?

One of the very limiting issues with authorisation rules is that we can't define an authorisation rule using a delegate like we can with business/validation rules.

Having the auth rules run purely against a set of roles only caters for very basic authentication models. 
Most more advanced LOB applications (like mine) have many other temporal and contextual parameters to consider when determining whether or not a user has write/read/view access to a property or object.

It would be a major improvement if you could enable the auth rules side to work like business/validation rules.

RockfordLhotka replied on Thursday, November 26, 2009

You can override CanReadProperty() and CanWriteProperty() in a class to provide more contextual rule behaviors.

 

paupdb replied on Friday, November 27, 2009

True, though CanRead and CanWriteProperty methods are hardly as nice to code and read as ValidationRules.  Plus there's not annotation options with the CanRead/Write methods.

Besides the property level, there is no current way to use delegates to control operations at the object level.  As far as I know all the AuthorisationRules methods are based on a string [] of Roles.
So controlling things like creation and deletion of objects is still very much tied to the basic roles model.

If I'm wrong about this, please enlighten me - but otherwise I would really hope that adding delegate options to the AuthorisationRules class methods is on the list for CSLA 4.

RockfordLhotka replied on Friday, November 27, 2009

There are three types of authz in most people's minds:

Per-property, which you can customize by overriding CanReadProperty()/CanWriteProperty() - and even if I provided a Func<T> so you could inject a lambda you'd end up writing the same code in the lambda that you do in the override - so it isn't clear there's a benefit?

Per-type, which are static rules that are role-based. Since these are static rules, they don't have access to instance data and so can't really be contextual. If someone can present a clear scenario where there'd be value in providing a static bool method to evaluate each rule I'd consider the idea.

Per-instance, which isn't currently supported by CSLA at all. Such rules could be contextual, because they'd live in an instance. But since they'd live in an instance they'd be potentially expensive (if role-based), because it would require initializing and maintaining the lists of roles on a per-instance basis. The perf and memory impact would likely be non-trivial, and having been down that road once before (CSLA 2.0) I have no desire to go there again.

I suppose I could define a per-instance interface that has a default implementation in BusinessBase and BusinessListBase of always returning the value of the existing static methods for each operation - and then allow you to override the methods to provide your own implementation. That way I would merely enable costly implementations, but wouldn't actually create one :)

The per-instance rule concept is actually an item in the wish list - it has been there for a while, because I haven't been able to get excited about it...

http://www.lhotka.net/weblog/PrioritizingCSLANETWorkItems.aspx

 

paupdb replied on Sunday, November 29, 2009

I'll try and explain the issue I have with the current static auth rules implementation with the following example:

In my system, users have multiple roles with each role linked to a set of function/task permissions.
When determining whether or not a user has access to create/delete an object or execute a function, we check a specific task permission to see what level of access they have.
On top of this, we also have to account for their access to certain projects/entities in the system.  If the user does not have access to a specific project entity, then they cannot perform functions on items related to that project.
And to top everything off, we also have a multitude of system parameters/settings that affect whether or not certain functions are available at all at an application level.

Thus we often have to make a 3 level check to detemine whether or not a user can create an object:
Has task permission?
  If yes, has project access?
    If yes, has system parameter set to true?
       If yes, then user can execute.

I did try to load a user's task permissions up into the Roles property of the Identity object, but that still left me without any way to check their project access and the system parameter setting.

What I have ended up doing is actually overriding BusinessPrincipalBase.IsInRole. 
I then used the role parameter as a delimited string using a pattern of my devising in order to be able to pass in the various task, project, system parameter and other values I want to check as part of the access check:

/// <summary>
    /// Highly customised override of the default CSLA IsInRole.
    /// This method can validate against user profile, user task permission, system setting, project permission
    /// and arbitary string equality.
    /// </summary>
    /// <remarks>
    /// The role string passed in is expected to contain one of the constant strings defined in class IsInRoleInstruction
    /// followed directly by the int value to check for.
    /// This method will extract the int value and then run the appropriate security check based on the Instruction.
    /// </remarks>
    /// <param name="role">The string containing an instruction about what specifically to check</param>
    /// <returns>True if the instruction is executed with a true result</returns>
    public override bool IsInRole(string role)
    {
      if (role.Contains(IsInRoleInstruction.TaskInstruction))
        return this.MyIdentity.HasTaskPermission(Int32.Parse(role.Replace(IsInRoleInstruction.TaskInstruction, "")));

      else if (role.Contains(IsInRoleInstruction.ProjectInstruction))
        return this.MyIdentity.HasProjectPermission(Int32.Parse(role.Replace(IsInRoleInstruction.ProjectInstruction, "")));
     
      else if (role.Contains(IsInRoleInstruction.SysParamInstruction))
        return AppCache.SystemSettings.IsSettingEnabled(Int32.Parse(role.Replace(IsInRoleInstruction.SysParamInstruction, "")));

      else if (role.Contains(IsInRoleInstruction.IsEqualInstruction)) {
        var split = role.Replace(IsInRoleInstruction.IsEqualInstruction, "").Split('=');
        return String.Equals(split[0], split[1], StringComparison.InvariantCultureIgnoreCase);
      }

      return false;
    }


An example of a call to this highly customised IsInRole would be something like:
    public static void AddObjectAuthorizationRules()
    {
      AuthorizationRules.AllowDelete(typeof(Document), IsInRoleInstruction.HasTask(TaskPermissions.AllowDeleteDocuments));
      AuthorizationRules.AllowDelete(typeof(Document), 
IsInRoleInstruction.HasProject(AppCache.CurrentProject));
     
AuthorizationRules.AllowDelete(typeof(Document),
IsInRoleInstruction.HasSysParam(SystemParameters.AllowGLImportTrans));
    }


As you can see I have tried to wrap all the delimited string nastiness up in classes, but at the end of the day my approach is hard coded and not at all easy to read like a similar delegate-based ValidationRule would be.

My approach has some issues too.  For one I am relying on only instantiating the static constructor of the class containing the AddObjectAuthorizationRules() method after I have intialised various static cache classes (AppCache for example above). 
Given it is static, I can only use the user project access checks on the client side since server side it would be static for the app domain.
Being able to use a delegate would solve these issues.

Hope this helps you understand my position.  Having static object rules that are based purely on a call to IsInRole is severely limiting in a more complex security model like mine.

RockfordLhotka replied on Sunday, November 29, 2009

So you are able to get this “project” information from ambient data? As in it isn’t specific to an object instance, but rather exists because of some broader global context data?

 

paupdb replied on Tuesday, December 01, 2009

Rocky,

Yes - we load the user's projects into the "MyIdentity" class as an additional property, along with some other properties for the logged-in user's specific settings and data. 
There are a few more user-specific entity assignments that happen in our system which influence authorisation rules at an object level, I've just excluded them for clarity here.

In terms of how we do the AddObjectAuthorizationRules  method, we have static cache objects which return session data on the client and on the server. 
So for example, in the line:
      AuthorizationRules.AllowDelete(typeof(Document), 
IsInRoleInstruction.HasProject(AppCache.CurrentProject));

The AppCache.CurrentProject call goes to a static singleton on the Silverlight client side (since we assume the app domain and session are the same on the client) and on the server side is a more complex call which pulls the user's specific instance of the AppCache out of the ASP.Net session.

rsbaker0 replied on Wednesday, December 02, 2009

RockfordLhotka:
So you are able to get this “project” information from ambient
data? As in it isn’t specific to an object instance, but rather exists because
of some broader global context data?


In our case, the authorization is often determined by a combination of instance and ambient data in many cases.

The main idea is that some objects have a "location" associated with them. The users have roles assigned to them, but they can vary by location. If the object has an associated location, then it is used. Otherwise, the ambient location is used.

I could possibly implement a (comparatively expensive) instance level roles list, but IMHO the best thing would be to have authorization work almost exactly like the validation rules mechanism - have the ability to associate rule methods with reading and writing of properties, and with the basic CRUD operations. If any rule fired returns false, then the authorization is denied. Otherwise, it is granted.

RockfordLhotka replied on Wednesday, December 02, 2009

You can do this by overriding CanReadProperty/CanWriteProperty though, so I'm not sure what sort of change you'd like?

I could maintain more metadata per property, allowing you to define a delegate that is called instead of the normal CanReadProperty/CanWriteProperty - but that's just sugar, since the code you'd write would be essentially the same as today. And maintaining those lists of delegates would be overhead that doesn't exist today - so I think there should be a broad and compelling argument (as in many people asking, not just one) to justify the overhead.

rsbaker0 replied on Wednesday, December 02, 2009

RockfordLhotka:

You can do this by overriding CanReadProperty/CanWriteProperty though, so I'm not sure what sort of change you'd like?


I could maintain more metadata per property, allowing you to define a delegate that is called instead of the normal CanReadProperty/CanWriteProperty - but that's just sugar, since the code you'd write would be essentially the same as today. And maintaining those lists of delegates would be overhead that doesn't exist today - so I think there should be a broad and compelling argument (as in many people asking, not just one) to justify the overhead.



Well, paupdb pretty much summed it up above:

"True, though CanRead and CanWriteProperty methods are hardly as nice to code and read as ValidationRules. Plus there's not annotation options with the CanRead/Write methods."

With validation rules, the infrastructure determines which property changed and fires the proper delegates for you.

Sure, you can override CanRead/CanWrite, then you'll end up manually implementing the test for each property in every such override that you write.

Now there are at least two of us asking... :)

ajj3085 replied on Wednesday, December 02, 2009

Its true you can override CanRead / WriteProperty to do this, but its never seemed consistent with the rest of Csla.  I think it has been said in this forum that auth. rules are just a certain kind of business rule, and I tend to agree.

Allowing delegates puts things along the line of all the other business rules, and people may be more inclined to factor out auth rules into seperate classes, if there's a property whose auth rules are consistent across several use cases. 

If auth rules are consistent within a BO such that they apply to all properties in that BO, overriding CanXProperty is a fine solution.

But when my overrides end up containing switch statements, something starts to feel quite a bit off, and I do have a few of these kinds of BOs around. 

I'm also left wondering if this could help a situation I have where I have the concept of Quotes, Orders, Invoices, etc.  Each of these has a common base (Document) because they share a lot of common behavior (more than not) and I also have a child ShipTo BO.  It needs to know what kind of document, so I have QuoteShipTo, OrderShipTo, InvoiceShipTo, and the only difference between the three is the AddAuthorizationRules override.  There's probably nothing wrong with this design, but I end up with 18 classes (5 distinct types of documents, and besides ShipTo theres BillTo and SoldTo).  Having a delegate may help me remove some of these classes, and put the answer to "who has auth" where it feels like it belongs; in the parent document class.

rasupit replied on Wednesday, December 02, 2009

What about adding overload to AllowRead and AllowWrite method to allow user to inject their own handler.

AuthorizationRules.AllowWrite(string propertyName, Action<T> testHandler)

That way user can implement their own permission check for more complex security not just based on role.

Like Rocky say, this is technically can be done now by overriding CanReadProperty/CanWriteProperty however going through AuthorizationRule will kinda formalize the implementation.

ajj3085 replied on Wednesday, December 02, 2009

Well I think that's the suggestion isn't it?

paupdb replied on Wednesday, December 02, 2009

I can live with CanRead/WriteProperty at a property level since at least this is a reasonable workaround - thank goodness for regions is all I can say for this.  I use a lot of regions in my CanWriteProperty methods since a lot of them are easily 500+ lines on their own.

I'm still waiting on a response regarding the object level authorisation rules. 

The problem with object level auth rules is that there is no workaround when one wants to apply rules which involve more than just what is stored in the user principal's roles.

RockfordLhotka replied on Wednesday, December 02, 2009

I thought I was clear in a previous post - there's a wish list item to allow per-type rules to be customized, and I understand that argument.

There is no such thing as per-object (per-instance) rules. I am not yet sure how they'd work. There's no way to implement CanCreateObject() and CanGetObject() on a per-instance basis, because the object doesn't exist yet when those rules are invoked by the data portal. There could be CanEditObject() and CanDeleteObject() per-instance rules, but that seems quite incomplete as a story...

There are absolutely ways to do delegate-based per-property rules. That incurs a certain amount of overhead because I'd have to maintain the delegate references. A quick look at the code to refresh my memory leads me to believe the overhead would be very minor, and so I may well add support for this concept.

paupdb replied on Wednesday, December 02, 2009

Rocky,

My focus is on the per type static object level auth rule facilities.

In your previous post you mentioned that per-instance object level rules enhancements were on the wish list, but said nothing about what the plans are for the per type statics.

You said
"Per-type, which are static rules that are role-based. Since these are static rules, they don't have access to instance data and so can't really be contextual. If someone can present a clear scenario where there'd be value in providing a static bool method to evaluate each rule I'd consider the idea."

I provided an example in detail.

You also said that static rules cannot access contextual data, but thats not true. 
The current per type rules system uses the IsInRole method on the user principal - which is clearly contextual to the user currently logged in.

What I am saying is that along with user "Roles", I have a system where the user has a number of other properties that feed into authentication decisions at a per type level. 

I gave my system's project concept as an example - where the projects that the user has access to were loaded onto the user principal at present.
We use this project access information to determine authorisation rules.
For instance depending on the user's level of access on the current project selected in the application, a user might be able to delete certain business object types.  This "current project" concept is a session wide value that the user modifies when they want to change project context.

If I'm off track or have missed something here, let me know - but I hope what I have summarised above is clear and accurate.

RockfordLhotka replied on Thursday, December 03, 2009

http://www.lhotka.net/cslabugs/edit_bug.aspx?id=13

paupdb replied on Thursday, December 03, 2009

OK so adding delegates into per type auth rule calls seems to be part of what you refer to as shared hosting improvements? 
I saw you mention that earlier in this thread - so can I assume this means that the the issue # 13 is on the list for CSLA 4?

If so then :)

RockfordLhotka replied on Thursday, December 03, 2009

Issue 13 is in the running for CSLA 4. In the end some of these things will come down to a matter of time. But multi-tenant business and authz rules are fairly high on my priority list.

Fintanv replied on Thursday, November 19, 2009

Peran:
Here are 2 ideas I was working on last night:

The second idea was to use IManageProperties to update the value without causing the rules to rerun at all.  This does however break encapsulation (using an explicit interface, so it's not quite as bad!) and requires IManageProperties to be changed from internal to public.  You would need to run the StringRequired rule after this rule, incase the user just entered some space characters.

    public class StringRuleArgs : Csla.Validation.RuleArgs
    {
        #region constructors

        public StringRuleArgs(PropertyInfo<string> propertyInfo)
            : base(propertyInfo)
        {
            this.PropertyInfo = propertyInfo;
        }

        #endregion

        #region proeprties

        public PropertyInfo<string> PropertyInfo { get; private set; }

        #endregion
    }

        public static bool ToUpperTrim2(object target, Csla.Validation.RuleArgs e)
        {
            var args = (StringRuleArgs)e;
            var businessObject = (IManageProperties)target;

            string value = businessObject.ReadProperty(args.PropertyInfo);

            if (value != null)
            {
                string newValue = value.ToUpper(CultureInfo.CurrentCulture).Trim();
                businessObject.LoadProperty(args.PropertyInfo, newValue);
            }

            return true;
        }

This is how I have handled these rules when they are located local to the business object.  

After the LoadProperty you probably also need to add:

businessObject.OnPropertyChanged(args.PropertyInfo.Name)

so that the UI updates correctly.

Peran replied on Thursday, November 19, 2009

Hi Fintanv,

I'm guessing your using a WinForms UI as the call to OnPropertyChanged is not required for a Silverlight UI.

Rocky created the BindingSourceRefresh control as an alternative approach for WinForms.


Cheers

Peran

rasupit replied on Thursday, November 19, 2009

Joe,
This is pretty interesting topic.  I've ran into this situation before where I need to enforce value to be upper cased. Because I don't do this many times, it never cross my mind to implement it so it can be used globally. 

I'd probably not use ValidationRules to implement data manipulation for clarity.  I'd override PropertyHasChanged and inject the data manipulation code right before validation rules get called.  I'd use attribute to indicate property that requires data manipulation. 

The following implemented in custom business base:
        protected override void PropertyHasChanged(string propertyName)
        {
            var prop = TypeDescriptor.GetProperties(typeof(T)).Find(propertyName, false);

            var attr = (from Attribute a in prop.Attributes
                        where a is EnforcerAttribute
                        select (EnforcerAttribute)a).ToArray();
            if (attr.Length > 0)
            {
                using (BypassPropertyChecks)
                {
                    var modValue = prop.GetValue(this);
                    foreach (var attribute in attr)
                    {
                        modValue = attribute.Enforce(modValue);
                    }
                    prop.SetValue(this, modValue);
                }
            }
            base.PropertyHasChanged(propertyName);
        }
And here how I'd implement Trim and ToUpper attributes:
    class ToUpperAttribute : EnforcerAttribute
    {
        public override object Enforce(object value)
        {
            return ((string) value).ToUpper();
        }
    }

    class TrimAttribute : EnforcerAttribute
    {
        public override object Enforce(object value)
        {
            return ((string) value).Trim();
        }
    }

I have also attach the complete project contain my experimental code, take a look and let me know if that would work for you.

Ricky

Copyright (c) Marimer LLC