I'm new to .NET MVVM structure of application and have just basic knowledge of it's principles, like decoupling, bindings, commands and etc. I'm using MVVM Light framework to simplify common MVVM issues like messaging and service location.
One thing I don't understand: do I need to call SimpleIoC every time I using Model classes from ViewModel?
Example:
I have simple View, a ViewModel corresponding to it and a Model with one class Settings
.
MainWindow.xaml
<Window ...>
<Window.Resources>
<viewModels:MainWindowModel x:Key="ViewModel" />
</Window.Resources>
<DockPanel DataContext="{StaticResource ViewModel}">
<Button Command="{Binding DoSomeCommand}" />
</DockPanel>
</Window>
MainWindowModel.cs
public class MainWindowModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand DoSomeCommand { get; private set; }
protected void RaisePropertyChangedEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindowModel()
{
DoSomeCommand = new RelayCommand(DoSome);
}
public void DoSome()
{
var data = Settings.Instance; //singleton
//... do some with data ...
Debug.Log($"{data.Prop1}, {data.Prop2}, {data.Prop3}");
}
}
Settings.cs
public static class Settings
{
//... singleton implementations ...
public int Prop1 { get; set; } // implementation of getters and setters
public int Prop2 { get; set; }
public int Prop3 { get; set; }
}
This code has one huge disadvantage: DoSome()
method is not unit-testable. Ok, let's fix that:
public class MainWindowModel: INotifyPropertyChanged
{
//...
private Settings _data;
public MainWindowModel()
{
_data = Settings.Instance;
DoSomeCommand = new RelayCommand(() => DoSome(_data));
}
public void DoSome(Settings data)
{
//... do some with data ...
Debug.Log($"{data.Prop1}, {data.Prop2}, {data.Prop3}");
}
}
Now I can mock or stub Settings
class and test DoSome()
.
But I know that 'Settings' class could be in different implementations, like 'SettingsXML' (data for XML), 'SettingsRegistry' (data for Window Registry), 'SettingsINI' (data from INI-file, wierd, but true). To avoid potential problems I rewrote it in interfaces:
public interface ISettings
{
public int Prop1;
public int Prop2;
public int Prop3;
}
public static class Settings: ISettings
{
//... singleton implementations ...
public int Prop1 { get; set; } // implementation of getters and setters
public int Prop2 { get; set; }
public int Prop3 { get; set; }
}
public class MainWindowModel: INotifyPropertyChanged
{
//...
private ISettings _data;
public void DoSome(ISettings data)
{
... do some with data ...
Debug.Log($"_data.Prop1}, {data.Prop2}, {data.Prop3}");
}
}
Everything looks fine to me. DoSome()
is testable and Settings
implementation could be different. One thing bothers me: MainWindowModel
knows actual class of settings (_data = Settings.Instance
).
Is it ok in MVVM structure?
Is it really necessary to use some IoC, write some 'SettingsWrapper' class with dependency injection of ISettings class and then use _data = SimpleIoc.Default.GetInstance<SettingsWrapper>
?
What I should do if Settings
class is NOT singleton?
Sorry, if I got basic idea of DI and IoC completely wrong. I will appreciate, if you will correct me.
One thing bothers me:
MainWindowModel
knows actual class of settings (_data = Settings.Instance
).
It shouldn't. It should only know about the interface that it is getting injecting with.
Instead of passing an ISettings
object to the DoSome
method, you could inject the MainWindowModel
class itself with an ISettings
when you create it:
public MainWindowModel(ISettings settings) { ... }
You could then let the ViewModelLocator
taking care of creating the view model class:
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<ISettings>(() => Settings.Instance);
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
}
<DockPanel DataContext="{Binding Main, Source={StaticResource Locator}}">
<Button Command="{Binding DoSomeCommand}" />
</DockPanel>