Gideon
Gideon

Reputation: 2014

MVVM Light inject DataService with parameter

My ExampleViewModel has a parameter (a model) that it needs to function properly. I also have a list variant of the ExampleViewModel that just contains a ObservableCollection<ExampleViewModel> and has some wrapper methods.

The issue shows it self when I try to use dependency injection with MVVM Light. Let me explain the scenario with some code.

default ViewModelLocator:

public class ViewModelLocator {

    public ViewModelLocator() {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

        if (ViewModelBase.IsInDesignModeStatic) {
            SimpleIoc.Default.Register<IDataService, DesignDataService>();
        } else {
            SimpleIoc.Default.Register<IDataService, DataService>();
        }

        SimpleIoc.Default.Register<MainViewModel>();
    }

    public MainViewModel Main {
        get {
            return ServiceLocator.Current.GetInstance<MainViewModel>();
        }
    }

    public static void Cleanup() {}
}

Simple MainViewModel:

public class MainViewModel : ViewModelBase {

    private IDataService _service;
    private MainModel _model;

    // Goal. I think?
    public MainViewModel(IDataService service, MainModel model) {
        _service = service;
        _model = model;
    }

    // Used now
    public MainViewModel(MainModel model) {
        _service = ServiceLocator.Current.GetInstance<IDataService>();
        _model = model;
    }
}

And the ListMainViewModel fills it's list like this:

Mains = new ObservableCollection<MainViewModel>(
    _service.GetAll().Select(model => new MainViewModel(model))
);

Because of the list creation in the ListMainViewModel I can't inject the data source in the constructor and have to use it as shown under // Used now. Which is fine, but when I try to unit test problems occur, as you can probably imagine.

I use a unit test like this at the moment:

public void ListMainViewModel_DoSomething_Success() {

    // Arrange
    var mock = new Mock<IDataService>();
    mock.Setup(m => m.Create(new MainModel { Naam = "Create" }));
    mock.Setup(m => m.Update(new MainModel { Naam = "Update" }));

    var listMainViewModel = new ListMainViewModel(mock.Object);

    var selected = new MainModel() { Prop = "Test" };

    listMainViewModel.SelectedMainViewModel = new MainViewModel(selected);

    // Act
    listMainViewModel.DoSomething();

    // Assert
    mock.Verify(x => listMainViewModel.DoSomething(), Times.Exactly(1));
}

Note: Not sure if the unit test is correct. But the problem occurs while creating the SelectedMainViewModel because it doesn't fetch a DataService.

For now I have two choices (I think), I can make the MainModel in MainViewModel optional, which is not something that should be possible and contructor inject the IDataService. Or I can discover a way to make the _service find-able by the MainViewModel.

Is there any elegant way to solve this? Because I have the feeling I have to make some hack using if (InDesignMode) or something similar.

Upvotes: 0

Views: 915

Answers (1)

Scope Creep
Scope Creep

Reputation: 881

You mention that:

I can't inject the data source in the constructor and have to use it as shown under // Used now.

What are you seeing when you try this? What exception is thrown? Why doesn't the below work?

Mains = new ObservableCollection<MainViewModel>(
    _service.GetAll().Select(model => new MainViewModel(model, _service))
);

I think it may help if you get away from using a service locator, which looks to be an anti-pattern for your goal. When using a DI container, it's best to create the entire object graph at the application start or web request (for web apps). But here you are using the new keyword to create your MainViewModel.

See this post.

Upvotes: 1

Related Questions