Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to store application settings/state in a MVVM application

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.

like image 910
Paul Alexander Avatar asked Apr 23 '09 23:04

Paul Alexander


2 Answers

I generally get a bad feeling about code that has one view model directly communicating with another. I like the idea that the VVM part of the pattern should be basically pluggable and nothing inside that area of the code should depend of the existence of anything else within that section. The reasoning behind this is that without centralising the logic it can become difficult to define responsibility.

On the other hand, based on your actual code, it may just be that the ApplicationViewModel is badly named, it doesn't make a model accessible to a view, so this may simply be a poor choice of name.

Either way, the solution comes down to a break down of responsibility. The way I see it you have three things to achieve:

  1. Allow the user to request to connect to an address
  2. Use that address to connect to a server
  3. Persist that address.

I'd suggest that you need three classes instead of your two.

public class ServiceProvider {     public void Connect(Uri address)     {         //connect to the server     } }   public class SettingsProvider {    public void SaveAddress(Uri address)    {        //Persist address    }     public Uri LoadAddress()    {        //Get address from storage    } }  public class ConnectionViewModel  {     private ServiceProvider serviceProvider;      public ConnectionViewModel(ServiceProvider provider)     {         this.serviceProvider = serviceProvider;     }      public void ExecuteConnectCommand()     {         serviceProvider.Connect(Address);     }         } 

The next thing to decide is how the address gets to the SettingsProvider. You could pass it in from the ConnectionViewModel as you do currently, but I'm not keen on that because it increases the coupling of the view model and it isn't the responsibility of the ViewModel to know that it needs persisting. Another option is to make the call from the ServiceProvider, but it doesn't really feel to me like it should be the ServiceProvider's responsibility either. In fact it doesn't feel like anyone's responsibility other than the SettingsProvider. Which leads me to believe that the setting provider should listen out for changes to the connected address and persist them without intervention. In other words an event:

public class ServiceProvider {     public event EventHandler<ConnectedEventArgs> Connected;     public void Connect(Uri address)     {         //connect to the server         if (Connected != null)         {             Connected(this, new ConnectedEventArgs(address));         }     } }   public class SettingsProvider {     public SettingsProvider(ServiceProvider serviceProvider)    {        serviceProvider.Connected += serviceProvider_Connected;    }     protected virtual void serviceProvider_Connected(object sender, ConnectedEventArgs e)    {        SaveAddress(e.Address);    }     public void SaveAddress(Uri address)    {        //Persist address    }     public Uri LoadAddress()    {        //Get address from storage    } } 

This introduces tight coupling between the ServiceProvider and the SettingsProvider, which you want to avoid if possible and I'd use an EventAggregator here, which I've discussed in an answer to this question

To address the issues of testability, you now have a very defined expectancy for what each method will do. The ConnectionViewModel will call connect, The ServiceProvider will connect and the SettingsProvider will persist. To test the ConnectionViewModel you probably want to convert the coupling to the ServiceProvider from a class to an interface:

public class ServiceProvider : IServiceProvider {     ... }  public class ConnectionViewModel  {     private IServiceProvider serviceProvider;      public ConnectionViewModel(IServiceProvider provider)     {         this.serviceProvider = serviceProvider;     }      ...        } 

Then you can use a mocking framework to introduce a mocked IServiceProvider that you can check to ensure that the connect method was called with the expected parameters.

Testing the other two classes is more challenging since they will rely on having a real server and real persistent storage device. You can add more layers of indirection to delay this (for example a PersistenceProvider that the SettingsProvider uses) but eventually you leave the world of unit testing and enter integration testing. Generally when I code with the patterns above the models and view models can get good unit test coverage, but the providers require more complicated testing methodologies.

Of course, once you are using a EventAggregator to break coupling and IOC to facilitate testing it is probably worth looking into one of the dependency injection frameworks such as Microsoft's Prism, but even if you are too late along in development to re-architect a lot of the rules and patterns can be applied to existing code in a simpler way.

like image 194
Martin Harris Avatar answered Sep 24 '22 07:09

Martin Harris


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.

like image 37
wekempf Avatar answered Sep 24 '22 07:09

wekempf