Search code examples
c#ninjectninject-extensions

Pass a dependency instance to a factory method parameter to make Ninject use it within the resolution


I have an abstract factory which creates some service represented by IService interface. In the factory I have two Create methods, because at one of them I allow the consumer to pass an existing IServiceLogger instance to be used by the constructed service tree.

public interface IMyServiceFactory {
  IMyService Create(IServiceLogger loggerInstance);
  IMyService Create();
}

Because an IServiceLogger should be shared among the service tree, I use the InCallScope when binding it to a concrete implementation.

How can I implement this scenario with Ninject? I've tried the following approaches.

1. Manually create a factory implementation

internal class MyServiceFactory : IMyServiceFactory {

  private IResolutionRoot _kernel;

  public MyServiceFactory  

  public IMyService Create(IServiceLogger loggerInstance) {
    // what should go here? how can I pass the existing instance to Ninject Get method and make Ninject to use it for the whole resolution tree, just as it were created by Ninject and used as InCallScope?
  }

  // this one is trivial...
  pulbic IMyService Create() {
    return _kernel.Get<IMyService>();
  }
}  

UPDATE

Actually I've found a messy and not too safe way for this. I can get the current bindings via GetBindings, then Rebind IServiceLogger ToConstant, then Get the IMyService instance, and finally restore the original bindings with AddBinding. I don't like it, it feels stinky and what's worse, it's not thread-safe, because another thread can request for a IMyService in the middle of this code and hence use the local temporary binding.

2. Use Ninject.Extensions.Factory

Just use the ToFactory binding, but that's not working, because it just tries to use the parameter as a simple constructor argument (if applicable), and not as an object for the whole resolution tree.


Solution

  • IMPORTANT UPDATE

    While my original answer gave me a working solution, by an accidental InteliSense navigation I've just found that there is a built-in tool for exactly this issue. I just have to use the built-in TypeMatchingArgumentInheritanceInstanceProvider which does this, and even more, because there are no more needs for naming conventions due to the parameter type matching.

    It would be good to have a more detailed documentation about these options, or maybe it's just me who can't find it currently.


    ORIGINAL ANSWER

    I tried a few ways, and ended up with a slightly different, kind of a convention based approach utilizing Ninject's context parameter inheritance.

    The convention is used at constructor argument naming through the dependency tree. For example whenever an IServiceLogger instance is injected to a service class, the argument should be called serviceLogger.

    With the above convention in mind, I've tested the following approach. Firstly I've implemented a custom instance provider for the factory extension. This custom provider overrides the mechanism for creating constructor parameters for the context to let the developer specify several named arguments which should be set as inherited. This way all the parameters with the specified names will inherit through the whole request graph during the get operation.

    public class ParameterInheritingInstanceProvider : StandardInstanceProvider
    {
        private readonly List<string> _parametersToInherit = new List<string>();
    
        public ParameterInheritingInstanceProvider(params string[] parametersToInherit)
        {
            _parametersToInherit.AddRange(parametersToInherit);
        }
    
        protected override IConstructorArgument[] GetConstructorArguments(MethodInfo methodInfo, object[] arguments)
        {
            var parameters = methodInfo.GetParameters();
            var constructorArgumentArray = new IConstructorArgument[parameters.Length];
            for (var i = 0; i < parameters.Length; ++i)
                constructorArgumentArray[i] = new ConstructorArgument(parameters[i].Name, arguments[i], _parametersToInherit.Contains(parameters[i].Name));
            return constructorArgumentArray;
        }
    }
    

    Then after at binding configuration I just threw it in with the corresponding parameter name.

    kernel.Bind<IMyServiceFactory>().ToFactory(() => new ParameterInheritingInstanceProvider("serviceLogger"));
    

    Finally I reviewed parameter naming, and for exampled changed loggerInstance in the factory interface to serviceLogger to match the convention.

    This solution is still not the nicest one as it has several limitations.

    1. It is error prone. One can make bugs which are hard to track by not keeping the naming convention, because currently it silently fails if the convention does not match. This could be improved probably, I'll think about it later.
    2. It handles only constructor injection, however this should not be a big issue as that's the suggested technique. For example I almost never do other kind of injections.