When needing user input on something ex: MessageBox.Show(...)

Topics: Prism v2 - Silverlight 2, Prism v2 - WPF 3.5
Feb 5, 2009 at 10:00 PM
Hello,

What are some ideas or guidance for the best practice when doing things that require user input in the below example.

Say you have a RemoveCommand on the presenter that needed to ask the user "Are you sure you want to do this?" (Ok, Cancel...) With the view's xaml RemoveButton bound directly to the presentation class's RemoveCommand. You can't put a MessageBox.Show("Are You Sure") in the Presentaion.RemovComand because the testability of the remove command will become difficult.

Does this mean all presentation classes that need this functionality should take a dependency on something like...

public interface IMessageBoxService
{
MessageBoxResult Show(string messageBoxText);
MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button);
}

Where during test you can mock the message box functionality? What is the guidance in this scenario?


OR

Is it not the responsibility of the Presentation class to ask the user for this input, it should be asked before the command executes, meaning the xaml binds to a view level method that does the validatoin and then calls the model's command? 

Some issues I see with that option are:
1. the interaction isn't very testable
2. what if you had mounds of business logic before the question was asked? You'd be forced to put that logic in the code behind which violates the benifit of the seperation of concerns to begin with.

Any thoughts?

Feb 7, 2009 at 7:09 PM

Using an injected MessageBox service is a good decoupled approach. Jeremy Miller (www.codebetter.com/blogs/jeremy.miller) has done a post on this "humble dialog" method. Another possibility if you are passing in an IView is to have the method be on the view itself. Both are testable, though the sevice is a bit more decoupled.

There are other options using property databinding and commands, but they start to get more hokey, this is the cleanest in my exprience.

Glenn 

Feb 7, 2009 at 8:03 PM
Thanks Glen.

I will stick with the IMessageBoxService.

It seems more readable and if I choose to create a more sylish wpf message box to use throughout the app I can do so without having to change many other places.
Feb 8, 2009 at 11:55 AM
I'm with Glenn and have done the message box service approach before myself. The only counter argument is that if it might make sense to make that prompting part of the view itself (i.e. one that can be styled/templated and made pretty as part of the embedded user experience), then I would have an interface-based call back to the view that could be stubbed out instead and let the view implementation decide what the right way to present that prompt is. It is being done within the visual context of the view after all, and it is the view's responsibility to decide what appropriate rendered content is for a given piece of functionality.
Feb 8, 2009 at 5:38 PM
I'm with Bryan on this one.  I just had to do this a couple days ago, in fact, and the approach I took was to define an interface specific to the particular prompt that I needed to perform.  In my case, we have no desire for the prompt to be asynchronous, so we did it like so:

interface IPromptForContinueOfOperationXXX
{
    bool PromptForContinue(...);
}

where XXX is the specific operation I'm prompting for, and ... is any context arguments that represent context of the particular callback.  Then, I inject this interface onto my presentation model and provide the implementation in my view.  Under test, I just fake this (or mock it) to return the value I expect.

I do the "specific interface" so that the callee can decide how to visualize the prompt (and can handle remembering 'saved answers' (i.e. the user clicked the check box that says "don't ask me again") and whatever else the UI should know about that I don't care about the presentation model knowing).
Feb 9, 2009 at 4:04 AM
I'm not seeing the IPromptForContinueOfOperationXXX idea all that much different from the IMessageBoxService that I have above. Althought one thing I like about your idea is not being coupled to the MessageBoxResult.

I could see some small value in having more generic prompts where we don't have to go...

if( _promptUserToContinueService.Show(...) == MessageBoxResult.Ok)...

Instead we could just

if( _promptUserToContinueService.Show(...))...

The symantic difference is small, but I like it a little more.
Feb 9, 2009 at 5:28 PM
The difference is in who decides the 'content' of the messagebox.  In the "IPromptForContinueOfOperationXXX", the contents of the dialog are determined by the object providing the service (with the exception of any parameters it needs to receive in order to properly display the dialog, which would be the arguments to the Show method).  In the IMessageBoxService approach, the 'content' of the message box is defined by the view model/presentation model.  I am not sure exactly where I stand on this, but currently I prefer to have the contents of the message box defined by the view, not the model.

Again, I'm not sure where the details really belong for the contents of the dialog, but I currently prefer them in the view, that way when my UI users say "I'd really rather this dialog read 'xxx' here and 'yyy' there" I can just move around the static text blocks (possibly filled by resource strings if needing localization), without having to modify the model.  Others could argue that this should be testable code (i.e. providing the 'contents' of the dialog) and thus should not be determined by the service but rather the caller.  I'm not convinced of the value of testing these sorts of things yet, so I prefer the former.

Kelly
Feb 13, 2009 at 4:13 AM
Hello,

I had to solve this problem recently. Take for example a detail window that is bound to some business object (in my case a CSLA object). If the user attempts to close the window before saving their edits they should be prompted. 'Are you sure you want to close?'. I solved this the most simple way I could. I put a method on my IEditiableView called 'PromptForContinue(IDialogInfo)'. In my PresentationModel object I simply call this methods when the CloseViewCommand is executed. I am able to test this just fine and it is fairly decoupled.

Brette21
May 14, 2009 at 7:18 PM
Edited May 14, 2009 at 8:42 PM

I was looking for a solution to this problem as well. It seems that there are three aspects to this problem. The first is whether or not a specific command has the capability to be cancelled, the second is the determination of when the client/user should be prompted and the third is how to present the prompt to the client/user. Because the presentation model represents both the structural model as well as the behavioral model of the use case, it seems that the first two aspects of the problem should be completely defined by the presentation model and the third should be defined by the view (and of course the view could optionally use a centralized message box service).

With this in mind, my solution was to simply extend the ICommand interface and create an ICancelableCommand that exposes a cancel event that should be executed by the Execute method of the ICommand. By doing this any object that has a reference to the presentation model (i.e. the View) can elect to handle the cancel event and prompt however is appropriate and set the CancelEventArgs.Cancel property if necessary. This also nicely encapsulates the behavior (capability/prompt timing) within the command itself and does not couple the entire presentation model to some special IPromptForContinueOfOperationXXX interface.

public interface ICancelableCommand : ICommand
{
    event CancelEventHandler Executing;
}

public interface ISomePresentationModel
{
    ICancelableCommand CloseCommand { get}
}

public class SomeView
{
    public ISomePresentationModel Model
    {
        get. . . }
        set
        {    . . .
            // This of course could be bound in XAML

            value.CloseCommand.Executing += CloseCommand_Executing;
        }
    }

    void CloseCommand_Executing(object sender, CancelEventArgs e)
    {
        // Prompt and optionally set e.Cancel = true;
        // This of course could be entirely handled in XAML :)

    }
}

Doug