views:

780

answers:

3

I'm experimenting with MVVM for the first time and really like the separation of responsibilities. Of course any design pattern only solves many problems - not all. So I'm trying to figure out where to store application state and where to store application wide commands.

Lets say my application connects to a specific URL. I have a ConnectionWindow and a ConnectionViewModel that support gathering this information from the user and invoking commands to connect to the address. The next time the application starts, I want to reconnect to this same address without prompting the user.

My solution so far is to create an ApplicationViewModel that provides a command to connect to a specific address and to save that address to some persistent storage (where it's actually saved is irrelevant for this question). Below is an abbreviated class model.

The application view model:

public class ApplicationViewModel : INotifyPropertyChanged
{
    public Uri Address{ get; set; }
    public void ConnectTo( Uri address )
    { 
        // Connect to the address
        // Save the addres in persistent storage for later re-use
        Address = address;
    }

    ...
}

The connection view model:

public class ConnectionViewModel : INotifyPropertyChanged
{
    private ApplicationViewModel _appModel;
    public ConnectionViewModel( ApplicationViewModel model )
    { 
        _appModel = model; 
    }

    public ICommand ConnectCmd
    {
        get
        {
         if( _connectCmd == null )
         {
          _connectCmd = new LambdaCommand(
           p => _appModel.ConnectTo( Address ),
           p => Address != null
           );
         }
         return _connectCmd;
        }
    }    

    public Uri Address{ get; set; }

    ...
}

So the question is this: Is an ApplicationViewModel the right way to handle this? How else might you store application state?

EDIT: I'd like to know also how this affects testability. One of the primary reasons for using MVVM is the ability to test the models without a host application. Specifically I'm interested in insight on how centralized app settings affect testability and the ability to mock out the dependent models.

+1  A: 

Yes, you are on the right track. When you have two controls in your system that need to communicate data, you want to do it in a way that is as decoupled as possible. There are several ways to do this.

In Prism 2, they have an area that is kind of like a "data bus". One control might produce data with a key that is added to the bus, and any control that wants that data can register a callback when that data changes.

Personally, I have implemented something I call "ApplicationState". It has the same purpose. It implements INotifyPropertyChanged, and anyone in the system can write to the specific properties or subscribe for change events. It is less generic than the Prism solution, but it works. This is pretty much what you created.

But now, you have the problem of how to pass around the application state. The old school way to do this is to make it a Singleton. I am not a big fan of this. Instead, I have an interface defined as:

public interface IApplicationStateConsumer
{
    public void ConsumeApplicationState(ApplicationState appState);
}

Any visual component in the tree may implement this interface, and simply pass the Application state to the ViewModel.

Then, in the root window, when the Loaded event is fired, I traverse the visual tree and look for controls that want the app state (IApplicationStateConsumer). I hand them the appState, and my system is initialized. It is a poor-man's dependency injection.

On the other hand, Prism solves all of these problems. I kind of wish I could go back and re-architect using Prism... but it is a bit too late for me to be cost-effective.

Brian Genisio
+2  A: 

If you weren't using M-V-VM, the solution is simple: you put this data and functionality in your Application derived type. Application.Current then gives you access to it. The problem here, as you're aware, is that Application.Current causes problems when unit testing the ViewModel. That's what needs to be fixed. The first step is to decouple ourselves from a concrete Application instance. Do this by defining an interface and implementing it on your concrete Application type.

public interface IApplication
{
  Uri Address{ get; set; }
  void ConnectTo(Uri address);
}

public class App : Application, IApplication
{
  // code removed for brevity
}

Now the next step is to eliminate the call to Application.Current within the ViewModel by using Inversion of Control or Service Locator.

public class ConnectionViewModel : INotifyPropertyChanged
{
  public ConnectionViewModel(IApplication application)
  {
    //...
  }

  //...
}

All of the "global" functionality is now provided through a mockable service interface, IApplication. You're still left with how to construct the ViewModel with the correct service instance, but it sounds like you're already handling that? If you're looking for a solution there, Onyx (disclaimer, I'm the author) can provide a solution there. Your Application would subscribe to the View.Created event and add itself as a service and the framework would deal with the rest.

wekempf
I've actually been pouring over the Onyx code for the last few days to glean some insight into WPF. It's definately layed out much how I think and I've learned quite a bit.
Paul Alexander
Thanks. Even if you don't use Onyx itself, I hope the ideas are useful. Onyx is certainly not required here, though the service interface solution I think really is what you're looking for.
wekempf
+2  A: 
Martin Harris