Background
Weekly we will get some users calling out for help why thy can’t do X on form Y.
Because of complex business rules, we often have to revert looking in the code ourselves to know why that particular action is not available at that time. Are there any proven strategies to deal with this?
How does one collect all the information from the GUI, business rules and/or security that leads to disabling a button?
Example
The user can’t remove a measurement from the measurement overview form because
GUI
- there is no measurement selected in the form.
- there are multiple measurements selected in the form.
Business rules
- the selected measurement has been used in a calculation.
- the selected measurement is linked to (what we call) a productfiche.
Security
- the current user is not a member of the group of analysts responsible for that particular measurement.
- the current user is not an analyst.
Edit
Regarding the valid comments about the fact that we’ve already made a calculation to decide if the control should be disabled.
We use a home brew ACL to handle security. These are the steps to decide if a control should be disabled
- A Global ACL is retrieved (currently from a database). If a
Write ACEin the ACL for the measurement property is present, this indicates that the current user has the right to change the measurement. - A measurement business object gets a copy of this global ACL. The business object puts his business rules on top of the retrieved ACL. If the business rules dictate that the measurement should not be writable, it adds a
Deny Write ACEto the ACL.
Note that a business object can only make the security more restrictive. If global security dictates it can’t be done, it can’t be done. - The coupling with the business object’s ACL and the GUI is done through what we call our GuiMap object. This object retrieves a copy of the ACL from the business object and allows the developer to add function pointers returning booleans to it to add
Gui ruleson top off the business objects ACL.
Now to determine if a button should be enabled, the GuiMap evaluates every function passed to it in combination with the security determined by the ACL of the business object and in extremis with the security of the user.
- If the user has no rights, the result is always disabled.
- else if the business rules say it should be disabled, it is disabled.
- else if any one Gui rule says it should be disabled, it is disabled.
So in fact, every layer builds on top of the previous to determine the final outcome. It is not like there will be one calculation to determine if a button or anything else should be enabled.
The beauty if you like is this: as ACL’s hand out copies, the copies attach themselves to the master and get notified when their master ACL get’s updated. This allows us to
- let every control get updated if a users logs off/on in anyone screen.
- let every control get updated on a change in the business object demands it.
This works quite well for us, except that it is hard to know why something is disabled.
If I understand you correctly, you have two separate problems:
1) The checks in each layer just return a boolean indicating enabled / disabled, and don’t return the reason.
You would have to change that such that each check also returns the reason; e.g. you could return a tuple
(enabled, reason), where access is the boolean that you have now, and reason a description of the reason why it is disabled (e.g. as a string).Depending on the environment you are working in, changing the return type of all access checks may not be feasible; if you want to avoid this, you could report the reason “out of band”, e.g. stash it into a global (or rather thread-local) variable, a la
errnoin UNIX orGetLastErrorin Windows. That’s not that elegant, and will certainly be frowned upon by many, of course 🙁Alternatively, you could change your checks to throw exceptions (with a descriptive message) instead of returning a boolean. Again, this will be frowned upon in many environments…
2) Your business layer adds entries to the object’s access control lists. When you later check the access, you know which entry denied the access, but you don’t know anymore, why this entry has been added.
Th solve this, you could add a
reasonfield to the ACEs. When the business layer adds an ACE, it sets the reason to a description of the reason why the access was denied. The access control check then reads the reason from here and passes it up to the GUI layer as described above.