Prevent Property Changes (business rule)

Prevent Property Changes (business rule)

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


AaronH posted on Friday, July 30, 2010

In the old days of CSLA, if you wanted to prevent a change to a property, you would simply check a few conditions on the setter.  If those conditions failed, you would simply thrown an exception, preventing the property value from being changed.

With the introduction of the static IPropertyInfo members, this method doesn't seem to work, as loading an object invokes the property setters.  I don't want existing data to go through these business rules when the data is being loaded into the objects (on Fetch)

I tried adding a new Rule, but the problem is that the rule is executed AFTER the property has already been set.

I really need the rule to be run first, and based on success, the property is set.

Am I going about this the right way.  Any thoughts?

RockfordLhotka replied on Friday, July 30, 2010

The only way to prevent changes to a property (and this has been true for a few years/versions now) is to use an authorization rule.

Throwing an exception was never a good solution, as it confused data binding and could often leave the user seeing something on the screen that didn't match what was in the object (or just crash the app).

AaronH replied on Friday, July 30, 2010

Not sure how authorization rules tie into this.  This is really a business rule.  If the rule fails, I need it to prevent setting the underlying property value.

See, the issue is that if I bind a grid to a list of objects and the user makes a change, the object becomes invalid and the property now contains the invalid value.

Now, all I can do is present them with a "cancel changes" which rolls back the object state to its previous values, but what if they made a few changes to the object that were good also?  Then they have lost all of those changes.

I suppose I could also prevent entry for that certain property too based on pass/fail of the logic, but now i'm putting business logic into the UI as well...

joemorin73 replied on Friday, July 30, 2010

If you edit data directly in a SQL editor and one of the values is invalid, all data is rolled back when you press escape.  This is a common user experience.

Are there many fields in your data?

tmg4340 replied on Friday, July 30, 2010

True - but that is not the only undo paragidm with which Aaron's users may be familiar.

Consider Excel.  If you type a value in a cell, don't like it, and press Ctrl-Z (or use whatever means of invoking the undo functionality that floats your boat), only that cell's value is undone - every other change you have made is kept.

This is a common issue - the "batch"-level undo versus the "individual"-level undo.  Unfortunately, CSLA isn't really built to handle the individual-level undo.  And without changing the CSLA codebase, I don't think you can shoehorn individual-level undo in there, particularly on a property-by-property basis.  It might be possible by switching to private-variable backing fields, but you'd still probably have to ignore/work around a lot of the CSLA infrastructure, and you still may not get there without a lot of work.

- Scott

RockfordLhotka replied on Friday, July 30, 2010

AaronH

See, the issue is that if I bind a grid to a list of objects and the user makes a change, the object becomes invalid and the property now contains the invalid value.

The challenge here, is that the datagrid controls expect things to work a certain way - and not the way you are describing. So you are embarking on a road where you'll have to fight the datagrid controls, data binding and CSLA (or any other object framework that supports data bindng).

Data binding assumes it can always set the value. This is a blanket assumption. If you prevent the value from changing in the setter, you'll almost certainly confuse data binding - the user will see one thing in the UI and the object will have a different value.

Additionally, datagrid controls and data binding interact with the IEditableObject interface on a per-row (per-object) basis, and expect undo operations to occur at the row level. That's the way the whole infrastructure is designed. Outside of select datagrid controls, there's no concept of per-cell undo - and if a datagrid does such a thing it is purely at the control level - because data binding itself has no such concept, nor are there interfaces by which a data source can interact with the UI for such a concept.

CSLA supports data binding. What data binding defines, CSLA does. What data binding doesn't do (generally speaking) CSLA doesn't do. How could it? Without formalized interfaces and data binding interactions, CSLA can't arbitrarily invent some concept - nothing else in the world would support it.

Given all that - the question is how to solve your problem.

The answer is to "allow the change to occur" and to then undo the change.

In other words, data binding assumes the property will accept any new value whether valid or not. And it assumes the object will go into an invalid state if the value is bad.

You are saying you dislike the model defined by data binding. OK, then you need to work around data binding.

You can do that by accepting the bad value, and then resetting the value to the previous value and allowing CSLA to interact with data binding as though the value changed. That way CSLA will tell data binding that the value changed, so data binding will refresh the value, so the user will see the value that's in the object - which is the previous value.

If you don't follow that sequence, the user will see what they typed in, but the object will have the previous value - which is a horribly confusing user experience.

So how do you do this? Well, it will take some work.

First, you need to use custom FieldData types that track the previous value - Jason Bock has a blog post on how to do this, and it is something CSLA does support (with some work on your part).

Second, you need a rule to run (probably at priority -1 or something) that reverts the new value to the previous value and triggers explicit short-circuiting so no subsequent rules run (why bother when you've undone the change to the new value).

What'll happen then, is that the user will enter the new value. It will be "accepted" into the object. Your priority -1 rule will run and revert the property to its previous value. But CSLA will still think that the property has been changed, so it will raise PropertyChanged. That'll cause data binding to refresh the value, updating the UI to replace the invalid value the user typed in, with the value from the object - which is the previous value.

The user experience will still be sort of iffy (imo), because the user will enter a value, hit Tab and their entry will silently be replaced by the previous value. They won't know why, and perhaps won't even notice that their entry was ignored/undone. For my part, I'd find such an experience to be really frustrating - but perhaps your users are used to this sort of thing.

AaronH replied on Saturday, July 31, 2010

Thanks for the thoughtful replies, everyone.  The actual problem isn't so much reverting the value, that was just an example.  But it is a solid business rule that absolutely must prevent the state of the object from changing based on some other state values.

Here goes the scenario:  I have a class called Record.  For sake of clarity, Record has an InputType (enum) property that represents an AUTOMATIC entry or a MANUAL entry.  If the Record instance was created AUTOMATICALLY, then certain fields CANNOT be changed.  This is where I would throw the exception. 

It's business logic that physically prevents state from being changed.  So I guess it's a bit more than just telling the object whether it's valid or invalid.  It's more of a requirement of business logic preventing the object instance from even reaching an invalid state due to a property of the Record object changing while being AUTOMATICALLY entered.

What I have done for this type of scenario in the past is to throw an exception in the property setter.  This logic guarantees that the object cannot be changed in certain scenarios.  Then, when a developer consuming the object bound it to some UI,  it would be up to them to disable input based on the state of the object.  This would prevent the exception from ever being raised in the first place, but still having the business logic built into the business class. 

I guess this is the approach I was thinking of following, but running into issues of course, due to the IPropertyInfo paradigm.  Perhaps this isn't a great practice to follow anymore? 

Or perhaps, I should be explicitly calling LoadProperty to avoid the property setters being called, which invoke the business logic?  See, right now, I am calling Property = DataSource.Value within a BypassPropertyChecks block, which causes each property setter to be called when loading the state (data) of the record object.

Any further thoughts or comments would be greatly appreciated!

ajj3085 replied on Saturday, July 31, 2010

I believe the previous suggetion of authorization rule still applies.  In Csla 4, you can write an auth rule which checks the state of the object and allows or denies write access to the property.  Just make sure it runs first.  Prior to 4, you should be able to do the exact same thing by overriding CanWriteProperty.  You'll just need to ensure that the cell control checks if the property can be written to and enable / disabling when the other values change.

RockfordLhotka replied on Saturday, July 31, 2010

I do agree with Andy - to a point.

If you can determine, before the user enters any new value, whether the user should be able to enter a new value, then that's an authorization rule. That can be done easily in CSLA 4, and can be done by overriding CanWriteProperty() in earlier versions of CSLA.

However, if the user is allowed to enter a new value, and you want to validate that new value after they've entered it, then you really should follow the model defined by data binding for how this should work - and that's the model supported by CSLA of course. If you really want to fight data binding and CSLA, see my previous post for some ideas on how you might be able to overcome the standard .NET data binding behaviors.

Jav replied on Saturday, July 31, 2010

AaronH

 If the Record instance was created AUTOMATICALLY, then certain fields CANNOT be changed. 

I am a newbie in this realm also, but can those fields not be made ReadOnly under those circumstances. 

Jav

RockfordLhotka replied on Saturday, July 31, 2010

I think the thing to remember here, and this is the key:

Authorization is NOT security. Authorization is a form of business logic.

What I hear being described in this thread is this:

"I have an object, and when the object is in a certain state, users are not authorized to change a specific property."

That's an authorization rule. Is it security? No, but it most certainly is authorization.

This is why CSLA 4 has much more powerful authorization capabilities than previous versions. While my answer in 3.x would be to override CanWriteProperty(), that was awkward. But in CSLA 4 you can very easily create a rule to handle this scenario and attach that rule to the write operation of the property.

The result, btw, will be an exception if the user does attempt to change the property. But that really shouldn't happen if the UI developer is competent, because the UI developer will have picked up on the CanWriteProperty() result (via IAuthorizeReadWrite) and will have prevented the user from even trying to update the value. CSLA includes controls for Windows Forms, Silverlight, WPF and ASP.NET MVC that make this happen almost automatically.

AaronH replied on Sunday, August 01, 2010

Do you see a clear advantage of taking the CanWriteProperty approach over the approach of placing the business logic directly in the property setter, and invoking LoadProperty to avoid invoking that business logic on load?

 

RockfordLhotka replied on Sunday, August 01, 2010

well sure, if you use the authz subsystem then the ui can find out the user can't enter values BEFORE the user tries. this enables the ui developer to give the user a substantially superior experience. if you embed the rule in the setter there is no way for the ui developer to reuse the logic to improve the user experience.

JonnyBee replied on Sunday, August 01, 2010

Hi,

I've tried to update the BusinessRuleSample but I am not happy about the way BusinessBase caches the result of the authorization rules.

Say if the State of the object is actually dependent on a value the user has entered/selected. Ex address form in an application with Country list and US State list. The State list is only allowedd to write/select if Country is "US". This will not work properly today in an editform in WinForms/WPF/SL as the rule result is cached, ie: returns the result of the first tun of the authorization rule in this instance - thus allowing for awkward situations.

Should the result of authorization rules be cached at all?

Is it obvious for delopers that AuthorizationRules and ValidationRules are treated differently in the new rule system?

It's very easy to override but I think we'll see some questions about it in the forum.

To make this work properly the developer must override CanWriteProperty so that rules are not cached.


    public override bool CanWriteProperty(Csla.Core.IPropertyInfo property)
    {
      // must override so that authorization rules are not cached
      return BusinessRules.HasPermission(AuthorizationActions.WriteProperty, property);
    }

 

RockfordLhotka replied on Sunday, August 01, 2010

That is true - and that's an issue I'm aware of.

I've been thinking that the best overall answer is probably for IAuthorizationRule to have a bool property that indicates whether the results of the rule can be cached (with the cache invalidating based on a change to the current principal). I'd hoped to work that into the 4.0.0 release, but ran out of time.

Some rules, like IsInRole, etc, can be cached based on the principal.

Other rules, like what we're discussing here, can't.

So I think the answer is for the rule to indicate whether it is OK with having its results cached.

JonnyBee replied on Monday, August 02, 2010

Yes, that would be a much better solution for Authorization.

How important is caching for the authorization rules?
Does the authorization providers themselves provide caching mechanisms internally?   

 

JonnyBee replied on Monday, August 02, 2010

Hi,

For more advanced Authorization (BusinessRules) it  would also be nice to have ReadProperty available in these rules.

Maybe even have InputProperties to create better generic rules.

 

RockfordLhotka replied on Monday, August 02, 2010

The caching is important, at least in Windows Forms where data binding can re-read properties fequently.

There is no Microsoft infrastructure for any of this btw. This is a CSLA thing, and has been for years. I wish Microsoft would formalize authorization as a concept within data binding - but so far no luck...

I agree, allowing for InputProperties is probably a good idea for a future enhancement.

JonStonecash replied on Tuesday, August 03, 2010

I would vote for the rule being able to exclude itself from caching.

Copyright (c) Marimer LLC