Using a service interface to get user input from dialogs I haven't worked with WPF/MVVM for a while, but now I'm back fixing some issues with one of my old apps and remembering the struggles I always had when dealing with asking for user input (eg. showing a dialog) from a view model.

I've put some thought into it and decided to try routing user input requests through a service interface so that it can be mocked/tested, while keeping the view logic out of my view model.

public interface IUserInputService
{
    UserRegistration PromptUserRegistration();
}


I then wrote a generic method that creates a new instance of a view and view model, connects them together, shows the dialog and then returns the dialog view model and result (eg. accepted, cancelled) back to the caller. Finally, the caller can extract information from the returned view model and return the actual desired result.
The view models have events which are called when the view model is "done", eg: Accepted and Cancelled. These are hooked up using the generic method.

public class UserInputService : IUserInputService
{
    // Implementation of the service interface using the generic method below
    public UserRegistration PromptUserRegistration()
    {
        var result = ShowDialog&lt;UserRegistrationDialog, UserRegistrationViewModel&gt;((v, vm) =&gt;
        {
            vm.Accepted += (s, e) =&gt; v.DialogResult = true;
            vm.Cancelled += (s, e) =&gt; v.DialogResult = false;
        });

        if (result.State != DialogState.Accepted)
        {
            return null;
        }

        return new UserRegistration
        {
            FirstName = result.ViewModel.FirstName,
            LastName = result.ViewModel.LastName
        };
    }

    // The generic method
    private DialogResult&lt;TViewModel&gt; ShowDialog&lt;TView, TViewModel&gt;(
        Action&lt;TView, TViewModel&gt; viewModelConnected
    )
        where TView : Window, new()
        where TViewModel : ViewModelBase, new()
    {
        TViewModel viewModel = new TViewModel();
        TView view = new TView();
        view.DataContext = viewModel;

        viewModelConnected(view, viewModel);

        view.ShowDialog();

        DialogState state;
        switch (view.DialogResult)
        {
            case true:
                state = DialogState.Accepted;
                break;
            case false:
                state = DialogState.Declined;
                break;
            default:
                state = DialogState.Cancelled;
                break;
        }

        return new DialogResult&lt;TViewModel&gt;(state, viewModel);
    }
}


Now I can pass around the service interface using DI/IoC and view models can ask for user input without caring how (eg. via dialog or mocking).

The other types (DialogResult and DialogState) are these:

public enum DialogState
{
    Accepted,
    Declined,
    Cancelled
}

public class DialogResult&lt;TViewModel&gt;
{
    public DialogResult(DialogState state, TViewModel viewModel)
    {
        State = state;
        ViewModel = viewModel;
    }

    public DialogState State { get; }
    public TViewModel ViewModel { get; }
}


So my questions are... 


Is this a good approach?
What would you do differently and why?
Any other suggestions regarding code quality?


I want to stick to the principles of MVVM as much as possible (though within reason) while keeping things testable... Let me know what you think.
I have worked in a project where such an IUserInputService was used. After few years, the implementation was a big class containing the dialog calling logic for all modules.

Today, I would prefer a generic service which is able to connect a view with the view model without implementing the logic for each concrete dialog.



Code Review


Your code looks clean and tidy
The communication between view model and the view via events may be OK in your case, because the view model is just an internal structure not known outside the method. However, in generell I would prefer a way where the view is not references by the view model (wich is indirect the case because the delegates contain a reference to the view). Usually, I use a view model property "bool Close" and bind it to the view so that the view can react on changes and close itself.
Your approach does not allow creation of view models via DI container. If the VM has dependencies, they must be passed to the method PromptUserRegistration.
Configuring the VM with default values requires to parameterize the PromptUserRegistration as well.


All things considered, I would prefer an approach where the view model is created / configured by the calling logic and shown via an generic service because it is more flexible and less distributed code.



Note that there are also frameworks available that adress that problem. E.g. https://github.com/FantasticFiasco/mvvm-dialogs