0

It's entirely possible my approach is incorrect but I'd like to outline the actual requirements first before my attempt at a resolution. My approach is based on the details provided here

Task: in a wizard oriented stucture, get a BaseWizardStepNavigator object based on the current step. E.g., if I'm on step WizardStep.Step1, return instance of Step1Navigator. The instance of Step1Navigator should have any injected assemblies supplied in its constructor such that if I have;

public class Step1Navigator : BaseWizardStepNavigator
{
    private readonly ISomeReader _reader;
    public Step1Navigator(ISomeReader reader)
        : base(WizardSteps.Step1)
    {
        _reader = reader;
    }
}

...that argument reader is populated with the appropriate implementation.

My idea is that I'd have a manager object that ninject can instantiate by passing in all implementations of the base class (with appropriate IoC injections) such that;

public class NavigatorManager
{
    private readonly List<BaseWizardStepNavigator> _navigators;
    public class NavigatorManager(IEnumerable<BaseWizardStepNavigator> navigators)
    {
         _navigators = new List<BaseWizardStepNavigator>(navigators);
    }
    public BaseWizardStepNavigator Get(WizardStep step)
    {
         return _navigators.FirstOrDefault(n => n.Step == step);
    }
}

There will eventually be 10's of wizard steps with appropriate navigators to determine what the next step is but they'll need to hit the DB occasionally to do that.

My current attempt and performing the binding in a NinjectModule where I'm using Ninject and Ninject.Conventions is;

Module (load method);

Kernel.Bind(s => s.FromAssemblyContaining(typeof(BaseWizardStepNavigator))
                    .SelectAllClasses()
                    .WhichAreNotGeneric()
                    .InheritedFrom<BaseWizardStepNavigator>()
                    .BindWith<NavigatorBinding>());
var test = Kernel.GetAll<BaseWizardStepNavigator>();

Then other classes for the bindings and provider;

public class NavigatorBinding : IBindingGenerator
{
    public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
    {
        if (type.IsInterface || type.IsAbstract)
        {
            yield break;
        }

        yield return bindingRoot.Bind(typeof(BaseWizardStepNavigator)).ToProvider<NavigatorProvider>();
    }
}

public class NavigatorProvider : IProvider<BaseWizardStepNavigator>
{
    public object Create(IContext context)
    {
        return null;
    }

    public Type Type { get { throw new NotImplementedException(); } }
}

Now, while the call to kernel.GetAll<BaseWizardStepNavigator>() does call the Provider implementation methods, I'm a bit lost as to how to actually get it to spit back the objects. The documentation is obscure and I'm not entirely certain I'm even on the correct path. Help?

Cœur
  • 37,241
  • 25
  • 195
  • 267
DiskJunky
  • 4,750
  • 3
  • 37
  • 66
  • Even if your Manager gets a list of navigators in its constructor, how is it going to determine which one to return based on the WizardStep? It looks to me like you need a map of some sort between step and handlers. – JuanR Nov 29 '16 at 21:21
  • @Juan there is though I didn't include it in the example above. The `BaseWizardStepNavigator` constructor takes an argument of `WizardStep` which each child must specify. In the `Get()` method, I'll be able to do `if (manager.Step == step) return manager;`. The issue is the `IProvider` implementation. I've no idea how to get it to select and return the appropriate objects – DiskJunky Nov 29 '16 at 21:30
  • This would be a lot simpler if you used interfaces instead of steps to identify the class. Since you are using a DI container, simply create an interface for each step and associate the class in the container. – JuanR Nov 29 '16 at 21:56
  • @Juan, actually no, that isn't possible in this case. I need *all* instances to process the navigation properly (each step is tested in turn until one fails to determine the current step). Binding to an Interface gets me one instance - if I know ahead of time and only want one. I want all implementations in a list so that I can pull them out and interrogate them. It's a moot point - I found a way of doing it. I'll post the answer as soon as SO allows me to – DiskJunky Nov 30 '16 at 11:30

1 Answers1

0

I managed to get an implementation working fairly simply in the end. There was no need for IBindingGenerator or IProvider implementations.

Code for Step1Navigator and NavigatorManager remains the same.

NinjectModule binding code changes to;

// set the navigator bindings
Kernel.Bind(s => s.FromAssemblyContaining(typeof(BaseWizardStepNavigator))
                  .SelectAllClasses()
                  .WhichAreNotGeneric()
                  .InheritedFrom<BaseWizardStepNavigator>()
                  .BindAllBaseClasses()
                  .Configure(c => c.InRequestScope())
                  );

// pass in all children of BaseWizardStepNavigator to the manager instance
Bind<NavigatorManager>().ToSelf()
                        .InRequestScope()
                        .WithConstructorArgument(typeof(IEnumerable<BaseWizardStepNavigator>),
                                                    n => n.Kernel.GetAll<BaseWizardStepNavigator>());

The .InRequestScope() is specific to web applications. Change as appropriate if you're using this in your own code to .InSingletonScope(), etc.

DiskJunky
  • 4,750
  • 3
  • 37
  • 66