In the general case, no, you can't interact with the UI from a data portal method, because they (logically at least) run on the server.
It is absolutely inappropriate for a business object to ask the user for confirmation. That sort of thing is a UI concern, not a business layer concern.
Your business object can refuse to save itself until some IsConfirmed property is true. That's OK. But it is the UI layer's job to set that property.
This blurs the architecture a bit, but you could even have a Save() override invoke a delegate before calling base.Save(). I think that is a bad idea, but it would be safe.
But having a server-side method attempt to interact with the user (even if you are in 2-tier deployment) is really a terrible idea. The data portal isn't designed with that in mind, and doing such a thing forever blocks you from being 3- or 4-tier - so you can never switch to a more scalable model or switch to Silverlight.
And obviously in a web or SOA model the business object can't interact with the user, because there's no way to do such a thing.
If BLB.Exists(param) Then [request confirmation from the user]
(where Exists then simply checks for the existence of the item in the collection and returns a bool)That will keep the UI concept of confirming the action separated from the business layer concepts of checking for existing items and doing the save.
Not sure if I'm on the same page with the question or if I am suggesting what others are saying not to do, but I use events whenever I have an operation that needs confirmation or can be cancelled by the user. When the operation is called from the UI, I will raise the pre-event then inspect the Cancel property to see if the operation was cancelled by the UI. My BO doesn't know anything about who or what is handling the event, what was displayed to the user, etc. All it knows is that the Cancel property is true or false and it proceeds accordingly.
For example, I'll have a BO that implements Deleting and Deleted events. Deleting is a pre-event that is called whenever the Delete method is called. The DeletingEventArgs inherits from CancelEventArgs which defines the Cancel property. In the Delete method, after raising the event, I check the Cancel property and abort the operation if set true. If false, I proceed with deleting the object then raise the Deleted post-event when complete.
Of course, all of this runs on the client-side of the data portal. If your situation depends on feedback from the server to decide if or what to prompt, then I agree with Tom_W that a command object with a simple interface to make the check is the best approach. But use events to communicate with the UI and don't couple your BO to the UI. Just like the deleting example, you can code your method to look at a boolean property on the EventArgs and proceed accordingly. Make sure the default value of the boolean property is in line with the default behavior your method should follow. This way, if no event handler is registered, your BO still works as the user would expect.
Hope that helps...
SonOfPirate:For example, I'll have a BO that implements Deleting and Deleted events. Deleting is a pre-event that is called whenever the Delete method is called. The DeletingEventArgs inherits from CancelEventArgs which defines the Cancel property. In the Delete method, after raising the event, I check the Cancel property and abort the operation if set true. If false, I proceed with deleting the object then raise the Deleted post-event when complete.
ajj3085:I've done something similar to this as well. On an invoice, if the billing zip code changes, the change should cause a lookup of the sales tax rate for that zipcode. The cavet is that a user must also be able to cancel the changing of the rate if they want to keep whatever the documnt currently thinks the rate is. So I have an event on the invoice called TaxRateChanging. The delegate looks like void TaxRateChanging( object sender, TaxRateChangingEventArgs ), where TRCEA inherits CancelEventArgs. Of course this is a bit of a difference case because it doesn't happen during a dataportal call, it happens in response to property changes on the document.
This seems to have drifted away from the original question somewhat, but I've found that true "user confirmation" can be implemented in a straightforward but somewhat convoluted transformation from an original implementation:
1. You implement a validation rule to detect the situation that requires confirmation. This rule "breaks" the object until confirmation is supplied.
2. The process of supplying confirmation sets a dependent "confirmed" property (e.g. AddDependentProperty causes rule #1 to refire) in the object that indicates confirmation has been provided, and thus "unbreaks" the object.
3. The user interface explicitly checks for broken rule #1 so that it can display the appropriate confirmation. When the user presses OK, the "confirmed" property in #2 is set, which now unbreaks the object.
This method can happily coexist with other described techniques -- like hooking events so the confirmation can be displayed immediately. The point of the rules is that the object *cannot* be saved without the confirmation having been provided. It's up to the UI to decide how this is done.
As an aside, you can be pretty elaborate with the actual semantics of what "confirmation" means. It doesn't have to just be "yes/no". We have a situation in which an entire child BO with additional information may have to be attached and saved along with the original object being edited and includes additional information like a reason code, comment, status code, etc. The confirmation validation rule in this case breaks the object until a valid "confirmation object" has been attached, thus ensuring that the property can't be changed without the required additional information being provided.
rsbaker0:3. The user interface explicitly checks for broken rule #1 so that it can display the appropriate confirmation. When the user presses OK, the "confirmed" property in #2 is set, which now unbreaks the object.
lukky:rsbaker0:3. The user interface explicitly checks for broken rule #1 so that it can display the appropriate confirmation. When the user presses OK, the "confirmed" property in #2 is set, which now unbreaks the object.
How does it check ? Polling ?
I wouldn't use polling no. When you go to save the object, it's broken and can't be saved (e.g. IsValid is false if you use "error" as the severity for your confirmation rule). You have to have logic in the UI to explicitly test for the specific broken confirmation rule, which I do just before trying to Save() the object.
If you have the luxury of being able to hook an event triggered when the property changes in the UI (e.g. WinForms), then you could display the confirmation at the actual time the property is changed (in which case the confirmation rule won't be broken at Save time).
This approach is certainly not without fault, but I'm coming from a situation where I'm having to port a legacy application to CSLA and be as consistent as is reasonable with the way the legacy application worked, and this seems to work.
rsbaker0:You have to have logic in the UI to explicitly test for the specific broken confirmation rule, which I do just before trying to Save() the object.
It sounds like you are introducing business logic into the UI.
Besides this - and don't get me wrong, I liked your suggestion at first glance - your solution still doesn't address how the confirmation is prompted. Following your approach, IsSavable will always be false until we get the confirmation which is not really possible until after we've tried to save the object. If we have bound the Enabled state of our Save button to IsSavable (which is the point of that property), how do we ever initiate the process? If I can't click Save, I can't prompt for confirmation.
SonOfPirate:rsbaker0:You have to have logic in the UI to explicitly test for the specific broken confirmation rule, which I do just before trying to Save() the object.
It sounds like you are introducing business logic into the UI.
Besides this - and don't get me wrong, I liked your suggestion at first glance - your solution still doesn't address how the confirmation is prompted. Following your approach, IsSavable will always be false until we get the confirmation which is not really possible until after we've tried to save the object. If we have bound the Enabled state of our Save button to IsSavable (which is the point of that property), how do we ever initiate the process? If I can't click Save, I can't prompt for confirmation.
This is at least partially true on the second count, but I'm not sure about the first.
The BO simply has a requirement that a particular change be "confirmed". It doesn't care how it is done, only that it is, and it doesn't even have to be via a UI. For example, a batch process making the same updates could "auto-confirm" the change via a method provided for that purpose. Note also that confirmation can be arbitrarily complex -- you might have to provide credentials, a certificate, etc. versus just "Yes, I confirm this".
Regarding the second problem, I typically don't do this because of the possibility the user can't immediately tell what is wrong, especially with a complex object that might have a broken child not even visible. (We have a multi-tabbed interface). So, I often take the approach of showing them a message (and someday a complete list) of what is wrong with when they press Save versus disabling the button.
I'm admittedly contriving this, but you could put a Confirm Change button on your screen whose enabled (visible, whatever) state is bound to a ConfirmationRequired property. When the protected property is changed, the button becomes enabled/visible. Once the user pushes it (and perhaps supplies whatever information is necessary), they can save the object.
A possibly alternative that would satisfy some is to simply set the severity of the confirmation rule to Warning and just let the warning indicator be sufficient "confirmation".
^^^^
Just wondering how this would work if the BO is being edited via an ASP web page...
Copyright (c) Marimer LLC