How to implement Business Method that requires confirmation?

How to implement Business Method that requires confirmation?

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


RedShiftZ posted on Thursday, August 27, 2009

I'm unsure how to implement this, or even what terminology to search on to find results, so any help would be appreciated, even the appropriate search terms.

Here is my situation. I have a ERLB (Entries) holding BBs (Entry). The ERLB has a Business Method that needs to interact with the UI. (I'm converting this from an older project that did table scans...)
See my dilemma in the comments inline...

    public void PrepEntry(inputparms)
    {
      bool recordFound = false;
      foreach(Entry ent in this)
      { //Loop the list to see if the record exists
        if (bb.parms != inputparms)
        { // None Found, Lets Create One
        bool goAhead = true;
        if (inputParm.Date < DateTime.Today) //We're being asked to create an "old" record
        {
          // Problem starts here...
          // Need to ask user to confirm.
          // But we are in the BO so no forms access because it might run on the server...
          // How do we get user interaction from the BO?
          // What is the correct methodology for this?
          // Am I going about this anywhere near the right way?
          if (MessageBox.ShowDialog != DialogResult.OK)
            goAhead = false;
        }
          if (goAhead)
          {
            Entry e = Entry.NewEntry();
            ...
            e = e.Save();
            this.Add(e);
          }
        }
      }
    }



Thank you very much.

Jeff Young

JonnyBee replied on Thursday, August 27, 2009

Hi Jeff,

2 alternatives comes to mind:
1. Use ValidationRules that trigger the check for duplicates on each item.
    You may use SeverityLevel of Information or Warning (will not block Save).
     * Implement a method on your list that runs through all new items and check for duplicates .
     * Look for broken rules on each new item on that given property.
     * Ask user for confitmation
     * Call Save on list of your user wants to save.

     * Create a CommandObject that checks for duplicates on entry
        (can be called from rule).


2. Create a CommandObject that checks on a list of items. Call this function before Save and if function returns true ask user for confirmation before save.

My preference would likely be #1 - as it also allows you to make a "unbound" column in your grid to show status on rows that are "old".

The import point - as you have noticed -  is that you must ask user for confirmation before you start the save operation. After Save is triggered execution of Save may happen on another computer.

/jonnybee

RedShiftZ replied on Thursday, August 27, 2009

Basically, this is a "if does not exist, then add if users says ok" method.

Can Business Methods(instance not static methods) happen on the server?

I have this working on the local dataportal by using a delegate to a UI function that displays a dialog box asking for confirmation. It works, but is it safe?

RockfordLhotka replied on Thursday, August 27, 2009

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.

RedShiftZ replied on Friday, August 28, 2009

I think perhaps I am not asking the right question...  I get that SAVE() cannot ask for confirmation. What I was wondering, was if an Instance Business Method on a BLB could interact with the user and if this was appropriate. For example....

public delegate bool UserReq(string requestString);

public void AddNewObjWithConfirm(string parm, UserReq userreq)
{
  bool Exists = false;
  foreach (BB b in this)
  {
    if (b.Parm == parm)
    {
      Exists = true;
      break;
     }
  }

  if (!Exists)
  {
     // Verify User Intent Here
    if (userreq("Does Not Exist, Create New?"))
    {
      //Add new object to list here.. Then call Save()
    }
   
  }
}

So this is bad then?


Tom_W replied on Friday, August 28, 2009

I think you'd be better having an Exists method on your BLB and then in your UI Save button code doing something along the lines of
 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.

SonOfPirate replied on Sunday, August 30, 2009

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...

 

ajj3085 replied on Monday, August 31, 2009

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.

RedShiftZ replied on Tuesday, September 01, 2009

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.


Can someone please post an complete example of an event and it's wireup for a BO? I have looked through the ProjectTracker and do not see this methodology in use... Did I just miss it?

Thanks,

Jeff

rsbaker0 replied on Wednesday, September 02, 2009

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.

lukky replied on Friday, September 04, 2009

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 ?

rsbaker0 replied on Friday, September 04, 2009

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.

 

SonOfPirate replied on Friday, September 04, 2009

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.

rsbaker0 replied on Friday, September 04, 2009

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".

 

 

ajj3085 replied on Friday, September 04, 2009

I don't have time to post a complete example, as its part of my production code, but the concept is the same as any other event. It was a matter of creating the subclass of CancelEventArgs (to provide more information so that the UI can display the old and new tax rate), and then invoking the event at the correct point in the BO code.

After the BO raises the event, it checks the Cancel property of the CancelEventArgs class, and if true it doesn't change the tax rate, and if false it will (and then raise corripsonding property changed events for the TaxRate property).

rsbaker0 replied on Friday, September 04, 2009

^^^^

Just wondering how this would work if the BO is being edited via an ASP web page...

ajj3085 replied on Friday, September 04, 2009

Hmm... good point, as I'm doing it in winforms... so the eventing wouldn't work.

asp.net screws everything up, and stuff like that is why i hate working to build web "applications."

You'd have to take the logic to lookup a tax rate by zipcode, expose it, then check if the rate would change based on the new code... if it does, you'd probably have do your DataMapper.Map call, but don't do the save so you can display a confirmation of tax rate changing. Then set the Confirmed property as in other posts..

Just a quick though.

Copyright (c) Marimer LLC