Search code examples
c#dependency-injectionninjectninject-extensions

Isolating a dependency for an instance (and that instances dependencies) when instance created through a Factory


EDIT: I've cleaned this question up significantly after solving my problem, including changing the title.

I have a MessageChannel interface which defines (unsurprisingly) a channel that my classes can use to push messages to the end user.

Normally this MessageChannel is a Singleton and is bound to a ViewModel that implements the MessageChannel interface. Essentially, there is a single location at the top of my application where messages to the user will be shown. So far its worked pretty well.

This MessageChannel is used in a lot of places, one of which is in some operation classes that I have setup.

I now have a need for a LOCAL MessageChannel, such messages being posted in some reduced scope get posted to that local MessageChannel and not the global one.

What this means is that I need to be able to create instances of a ViewModel (through a Factory), such that that particular instance has its own MessageChannel instance AND that MessageChannel instance is shared for all dependencies injected into that ViewModel (and their dependencies and so on).

Some classes to illustrate. I have simplified things somewhat, my messages are more than just strings:

using Ninject;
using Ninject.Extensions.Factory;

public interface MessageChannel
{
    void PostMessage(string message);
}

public class MessageChannelViewModel : MessageChannel
{
    public string Message { get; set; }

    public void PostMessage(string message)
    {
        Message = message;
    }
}

public interface Operation
{
    void Do();
}

public interface OperationFactory
{
    Operation Create();
}

public class DefaultOperation : Operation
{
    public DefaultOperation(MessageChannel userMessages)
    {
        _UserMessages = userMessages;
    }

    private readonly MessageChannel _UserMessages;

    public void Do()
    {
        // Do something.
        _UserMessages.PostMessage("Success!");
    }
}

public interface IsolatedViewModel
{
    MessageChannelViewModel LocalMessages { get; }
}

public interface IsolatedViewModelFactory
{
    IsolatedViewModel Create();
}

public class DefaultIsolatedViewModel : IsolatedViewModel
{
    public IsolatedViewModel(MessageChannelViewModel localMessages, OperationFactory opFactory)
    {
        _OpFactory = opFactory;
        LocalMessages = localMessages;
    }

    private readonly OperationFactory _OpFactory;

    public MessageChannelViewModel LocalMessages { get; private set; }
}



public class Module : NinjectModule
{
    public override void Load()
    {
        Bind<MessageChannel, MessageChannelViewModel>().To<MessageChannelViewModel>().InSingletonScope();

        Bind<Operation>().To<DefaultOperation>();
        Bind<OperationFactory>().ToFactory();

        Bind<IsolatedViewModel>().To<DefaultIsolatedViewModel>();
        Bind<IsolatedViewModelFactory>().ToFactory();

        // Something to make it so the IsolatedViewModel DOESNT get the Singleton
        // instance of the MessageChannelViewModel, and instead gets once of its own
        // AND so the Operations created by the OperationFactory injected into the
        // IsolatedViewModel get the SAME MessageChannel, so messages being posted
        // from any place in the IsolatedViewModel's dependencies are shown only\
        // locally.
    }
}

I tried the NamedScope extension but I couldn't get it do what I wanted it to do.


Solution

  • I think you can try to use The Ninject Context Preservation Extension which adds support for recording (and making available to Contextual Binding rules) the context pertaining to factories that call the Kernel to Resolve Requests.

    This enables you to add contextual conditions to your Bindings.