To this point, we’ve been able to run each of our screens by hand-coding the necessary object initialization. We could continue this as we move forward, but the complexity would grow with each additional screen we complete. Instead, let’s take the time to configure a dependency injection framework that will take care of the complexity for us.

Using a dependency injection framework is one of those things that can sometimes seem like more effort than it’s worth. Part of the problem here is that the benefits accrue as the size of the project grows. In most demonstration projects, the overhead of setup and configuration dwarfs the benefits, simply because the projects are small.

I’ve split this update over three separate posts. In this post, we’ll configure dependency injection for the core of our application. In later posts, we’ll configure our view-model layer, and then our WPF layer.

For WordTutor, I’ve chosen to use the SimpleInjector project. We start by adding an appropriate NuGet reference to our WordTutor.Desktop project.

In common with most (if not all) dependency injection frameworks, we need to configure the container with all the types that will make up our running application. We do this by adding the method CreateContainer() to Program.cs.

public static Container CreateContainer()
{
    var container = new Container();

    // Register Redux Store
    container.RegisterSingleton<
        IReduxStore<WordTutorApplication>,
        ReduxStore<WordTutorApplication>>();

    container.Verify();

    return container;
}

This is the simplest possible starting point.

The Verify() call is extremely useful, allowing SimpleInjector to report on issues right upfront. This is far better than experiencing failures when we enter a specific screen.

I’ve made it a public static method to allow the container configuration to be used from a few unit tests.

At the start of Main(), we need to create our container and then ask it for an instance of our Redux Store:

var container = CreateContainer();
var store = container.GetInstance<IReduxStore<WordTutorApplication>>();

Running this, we get the following error:

System.InvalidOperationException
The configuration is invalid.
Creating the instance for type IReduxStore failed. No registration for type WordTutorApplication could be found and > an implicit registration could not be made. The constructor of type WordTutorApplication contains the parameter with name 'initialScreen' and type Screen that is not registered. Please ensure Screen is registered, or change the constructor of WordTutorApplication.

(I’ve made some minor edits for the sake of clarity.)

Wow, getting an error like this can be pretty intimidating. Let’s break it down.

  • The error begins by informing us that it failed to create an instance to satisfy our request for an IReduxStore<WordTutorApplication>.
    This makes sense, as that’s the request we added to `Main(), above.
  • Next, it tells us that the reason it failed is that we’re lacking a registration for WordTutorApplication. Where did this come from? SimpleInjector looked at the constructor for ReduxStore<T>.
  • To try and cover the missing registration, it tried to create an implicit (automatic) registration for WordTutorApplication, but that failed.
  • The implicit registration failed because the constructor for WordTutorApplication requires an instance of Screen - but that’s an abstract class and can’t be implicitly registered.

The challenge here is to identify the required fix.

In this case, the problem is that ReduxStore<> requires the initial state to be passed to the constructor. We don’t want the dependency injection container creating instances of WordTutorApplication, because that represents the state of our application; it’s not part of our infrastructure.

One potential solution would be to introduce an additional method, allowing for the initial state of the store to be updated after construction:

public class ReduxStore<T> : IReduxStore<T>
{
    public ReduxStore(IReduxReducer<T> reducer) { ... }
    public void Initialize(T initialState) { ... }
}

However, there are some notable problems with this approach:

  • After construction, the store isn’t in a valid state - and it can’t be used until Initialize() has been properly called. This is a form of temporal coupling.
  • Despite the name implying single-use, having a method on the store invites consumers to call it again. Having a back-door for modifying the application state feels like a mistake.

Instead, we’ll introduce a new interface, representing a factory that the store can call to obtain the initial state needed.

public interface IReduxStateFactory<T>
{
    T Create();
}

The constructor for ReduxStore<T> needs to be modified to use the factory:

public ReduxStore(
    IReduxReducer<T> reducer, 
    IReduxStateFactory<T> initialStateFactory)
{
    _reducer = reducer 
        ?? throw new ArgumentNullException(nameof(reducer));
    State = initialStateFactory.Create();
}

Existing code from Program.cs, used to create our initial state, can now be extracted out into an implementation of the factory:

public class WordTutorApplicationStateFactory 
    : IReduxStateFactory<WordTutorApplication>
{
    public WordTutorApplication Create() { ... }
}

A side effect of this is that we’ve reduced the number of responsibilities of our entry point - it’s no longer also responsible for creating our initial state.

Lastly, we need to register the factory in our existing CreateContainer() method:

container.RegisterSingleton<
    IReduxStateFactory<WordTutorApplication>,
    WordTutorApplicationStateFactory>();

With all this out of the way, we can run the application again to see if it works.

Of course, it doesn’t.

System.InvalidOperationException
The configuration is invalid.
Creating the instance for type IReduxStore failed. The constructor of type ReduxStore contains the parameter with name 'reducer' and type IReduxReducer that is not registered. Please ensure IReduxReducer is registered, or change the constructor of ReduxStore.

Another error, this one complaining that we haven’t registered the reducers used to modify the state of the application.

We need to add two new registrations. Unlike other dependency injection containers, SimpleInjector requires separate registration for collections of implementations.

Firstly, we need to specify that anyone requesting a single reducer (e.g. ReduxStore<>) should be given our composite reducer:

// Register Reducers
container.RegisterSingleton<
    IReduxReducer<WordTutorApplication>,
    CompositeReduxReducer<WordTutorApplication>>();

Second, we need to specify that anyone requesting a collection of reducers (e.g. CompositeReduxReducer<>) should be given a specific set of reducers. We want all of these to be registered as singletons, so we need to loop over the types ourselves:

var coreAssembly = typeof(WordTutorApplication).Assembly;

foreach (var type in container.GetTypesToRegister<IReduxReducer<WordTutorApplication>>(coreAssembly))
{
    container.Collection.Append(
        typeof(IReduxReducer<WordTutorApplication>),
        type,
        Lifestyle.Singleton);
}

Notice how we’re registering all available reducers, not just a known set. This ensures that any new reducers we add for new screens will automatically be registered for us. This is a pattern we’ll use multiple times.

With this registration in place, our code runs; the dependency injection container can successfully create our redux store, which makes it available to any other type that needs it.

In the next post, we’ll look at how we can use our existing container to create ViewModels when we need them.

Prior post in this series:
Integration Testing
Next post in this series:
Dependency Injection: ViewModels

Comments

blog comments powered by Disqus