DI Interception and UIs

  softwareengineering

I contribute to an open-source project that heavily uses the ctor-injection design pattern. Recently, there was a discussion about using interception for certain things, including injecting UI commands to a view model, and correctly binding the command to the correct value.

As an example, we have a command:

public class FooCommand : CommandBase    // CommandBase implements ICommand
{
    public bool CanExecute(object obj);
    public void Execute(object obj);
}

We have many of these, and we inject them to the view model with Ninject. It may be relevant that in this specific case, the commands are marked with an attribute so we know which subset to inject:

public class FooViewModel : ViewModelBase
{
    public FooViewModel(IEnumerable<CommandBase> commands) {}
}

Now, in the class defined above, we go like this:

public class FooViewModel : ViewModelBase
{
    public FooCommand FooCommand { get; }

    public FooViewModel(IEnumerable<CommandBase> commands)
    {
        FooCommand = commands.SingleOrDefault(command => command is FooCommand);
    }
}

The public instance of FooCommand is bound to the UI elements for certain actions.

Now, when we tried to work with this using intercepted commands, everything broke down. When we intercepted our implementation and injected Ninject’s implementation, we only received a bunch of CommandBase implementations, so we don’t know what command to bind to which value. Essentially, we are relying on a specific implementation, or any derived implementation, to bind to a specific command; Ninject, however, is returning values at one abstraction level higher than we need.

There are two methods I can see to fix this–A) somehow make Ninject intercept the specific class instead of its interface, and B) add an abstract field to CommandBase to use to figure out what is what. I’m not sure how to do A, or even if it can be done–at least, I’ve not had success with it. B, on the other hand, seems to be 1) adding redundant information (the type already provides this value–when we don’t use interception, that is), and 2) adding another possible place for this to break. What are your recommendations for this?

This is a pretty mangled way to do DI. You are basically doing service locator without the service locator… And the reason it doesn’t work is exactly that: you are using a tool to try and do something it was not meant to do.

The solution is simple: you should only inject the actual classes you NEED by requesting them as they are.

What you are doing here is a valid pattern for some cases (inject an unknown number of classes that extend the same interface and that all need to perform some work), but not yours.

0

LEAVE A COMMENT