2

I have a console application which I wrote a privately constructed Bootstrapper class, exposing a Default property that gives access to the Bootstrapper with only one public method available. I have an issue where the NamedScope that I can not resolve instances registered as InNamedScope from the NamedScope with the same name. Code follows:

public class Bootstrapper
{
    private const string SCOPENAME = "BOOTSTRAPPED";
    private static object KernelConstructionLocker = new object();
    private StandardKernel Kernel;
    private readonly Bootstrapper defaultBootstrapper = new Bootstrapper();
    private Bootstrapper Default {get { return defaultBootstrapper; }}
    private Bootstrapper() 
    { }

    public GetResolutionRoot()
    {
        if (Kernel == null)
        {
            //Kernel ctor not thread safe
            lock(KernelConstructionLocker)
            {
                //double locked incase thread created while locked
                if (Kernel == null)
                {
                    Kernel = CreateKernel(); 
                }
            }
        }

        return new TaskExecutionScope(Kernel.CreateNamedScope(SCOPENAME));
    }

    private CreateKernel()
    {
        Kernel = new StandardKernel();

        //bindings, etc...
    }
}

    public class TaskExecutionScope : IResolutionRoot, IDisposable
    {
        private readonly NamedScope scope;

        internal TaskExecutionScope(NamedScope scope)
        {
            this.scope = scope;
        }

        public bool CanResolve(Ninject.Activation.IRequest request, bool ignoreImplicitBindings)
        {
            var canResolve = scope.CanResolve(request, ignoreImplicitBindings);

            return canResolve;
        }

        public bool CanResolve(Ninject.Activation.IRequest request)
        {
            var canResolve = scope.CanResolve(request);

            return canResolve;
        }

        public Ninject.Activation.IRequest CreateRequest(Type service, Func<Ninject.Planning.Bindings.IBindingMetadata, bool> constraint, System.Collections.Generic.IEnumerable<Ninject.Parameters.IParameter> parameters, bool isOptional, bool isUnique)
        {
            var request = scope.CreateRequest(service, constraint, parameters, isOptional, isUnique);

            return request;
        }

        public bool Release(object instance)
        {
            var release = scope.Release(instance);

            return release;
        }

        public System.Collections.Generic.IEnumerable<object> Resolve(Ninject.Activation.IRequest request)
        {
            var resolve = scope.Resolve(request);

            return resolve;
        }

        public void Dispose()
        {
            scope.Dispose();
        }
    }

Then, when I attempt to resolve a binding that was registered in the CreateKernel method as

kernel.Bind<IUnitOfWorkService>()
      .To<UnitOfWorkService>()
      .InNamedScope(SCOPENAME);

It fails to resolve with the error:

Error activating IUnitOfWorkService
The scope BOOTSTRAPPED is not known in the current context.
No matching scopes are available, and the type is declared InNamedScope(BOOTSTRAPPED).
Activation path:
  1) Request for IUnitOfWorkService

Suggestions:
  1) Ensure that you have defined the scope BOOTSTRAPPED.
  2) Ensure you have a parent resolution that defines the scope.
  3) If you are using factory methods or late resolution, check that the correct IResolutionRoot is being used.

Stacktrace:

at Ninject.Extensions.NamedScope.NamedScopeExtensionMethods.GetNamedScope(IContext context, String scopeParameterName)
at Ninject.Extensions.NamedScope.NamedScopeExtensionMethods.<>c__DisplayClass1`1.<InNamedScope>b__0(IContext context)
at Ninject.Planning.Bindings.BindingConfiguration.GetScope(IContext context)
at Ninject.Planning.Bindings.Binding.GetScope(IContext context)
at Ninject.Activation.Context.GetScope()
at Ninject.Activation.Context.Resolve()
at Ninject.KernelBase.<>c__DisplayClass15.<Resolve>b__f(IBinding binding)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters)
at SomeConsoleApp.Work.ScheduledWork.ScheduledWork.Quartz.IJob.Execute(IJobExecutionContext context) in ***

At this point, I don't know what else to try, any help would be appreciated.

EDIT

Sorry, thought I had how I was using it in here.. Here is a snippet of how it would be used:

using (TaskExecutionScope scope = Bootstrapper.Default.GetResolutionRoot())
{
    var unitOfWork = scope.Get<IUnitOfWorkService>();                   
    //do things with unit of work service
}

This is a bare bones example of how it is used. It fails on that call to Get<IUnitOfWorkService>. Now, while this WAS failing, I was able to get it to work by including the Context Preservation plugin, and changing up the TaskExecutionScope a little bit (only changed the Resolve(IRequest request) method from the above code:

    public System.Collections.Generic.IEnumerable<object> Resolve(Ninject.Activation.IRequest request)
    {
        var attempt = request.ParentContext.GetContextPreservingResolutionRoot().Resolve(request);

        return attempt;
    }

While this works, I hate having something in my code where I don't know WHY I have to have it in my code, and this is one of those examples. I would expect that since before I was calling Resolve directly on the NamedScope, that it should function, because it IS a resolution root - and I can't understand how a resolution root of a NamedScope doesn't even know its own name??? So, I would love to know either-

A) Why did I have to do this to get it to work?

B) This is the worst thing ever - you should do THIS instead!

C) This will work, but there is a small bug...

EDIT 2

So, I have attempted what @BatteryBackupUnit suggested, and it is failing with the same error as before... I added this line to the CreateKernel method:

kernel.Bind<TaskExecutionScope>().ToSelf().DefinesNamedScope(SCOPENAME);

And changed the GetResolutionRoot method to return Kernel.Get<TaskExecutionScope>(); . At this point I'm reverting back to the working code mentioned below.

tostringtheory
  • 877
  • 8
  • 19
  • How are you resolving the object? Using `NamedScope.Get`,.. or how? Please show that piece of the code, too. – BatteryBackupUnit Sep 06 '14 at 13:41
  • @BatteryBackupUnit , added an example of how I was using it, and how I got it to work. Please still look at it and give me any feedback - is it wrong, buggy, correct, and/or why did I have to do that... – tostringtheory Sep 06 '14 at 17:39

1 Answers1

2

Normally you would use NamedScope like

Bind<FooTask>().ToSelf().DefinesNamedScope(SomeScopeName);

Which would not require the creation of a NamedScope instance. However, it seems that you would like it to work for every task. You could switch to .InCallScope(), which would achieve the same in this scenario. Another alternative is to have the TaskExecutionScope created by ninject, and do:

Bind<TaskExecutionScope>().ToSelf().DefinesNamedScope(SomeScopeName);

since you're allways using the TaskExecutionScope to create a scoped object, right?

NamedScope and ContextPreservation

Without ContextPreservation, you can put parameters on the context of a request, but they are only kept until the object and it's dependencies are resolved. Injecting a Func<Foo>? It won't know about the parameters. ContextPreservation changes that and preserves the parametesr of the context, so that Func<Foo> will pass the parameter along to the request of Foo. Now NamedScope is very similar. It's basically a factory. The way it was implemented however, when creating the NamedScope, the NamedScope itself does not know the scope, but rather it's instanciated similarly to Bind<NamedScope>().ToSelf().DefinesNamedScope(SomeScopeName). To be honest i think NamedScope is named and implemented poorly. The scope itself is actually defined by a NamedScopeParameter on the context! If you do Bind<Foo>().ToSelf().DefinesNamedScope("Foo") there's actually no NamedScope object involved. So if there's no context preservation, any object that's created after creating the NamedScope,.. well the named scope definition (NamedScopeParameter) is actually gone. That's why it doesn't work without the ContextPreservation Extension.

Quartz and TaskScopes

The easiest way would be to adapt the quartz job factory (see how to inject quartz's job with ninject?). Instead of just doing IResolutionRoot.Get<TTask>(), you should do IResolutionRoot.Get<TTask>(new NamedScopeParameter(scopeName); (also see CreateNamedScope(string scopeName) method).

Hint: In case your task is creating object by factory - and the objects need to know about scopes - you still will need the ContextPreservation extension.

Community
  • 1
  • 1
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • I have seen the first example you have of the `DefinesNamedScope` function, but since I had no top-level object, and I have multiple bindings that I want to only be gen'd once per scope, that's why I didn't use `DefinesNamedScope` to begin with... However, with the fact that I DO basically have a top level object - the `TaskExecutionScope`, that might just work... I'm gonna try it out real quick and see. Can you give any insight into why resolving from the NamedScope fails to realize itself as an active scope without the ContextPreservation though? – tostringtheory Sep 06 '14 at 20:20
  • So - quick question, do I leave `TaskExecutionScope` as defined in the first code snippet above? Or, do I remove the constructor `NamedScope` argument and take in something else? – tostringtheory Sep 06 '14 at 20:25
  • you should also have a look at: http://stackoverflow.com/questions/25339941/ninject-scoping-for-dbcontext-used-in-quartz-net-job?lq=1 – BatteryBackupUnit Sep 07 '14 at 18:05
  • Thanks for your info regarding the Context/NamedScope, and where the Name is being stored. I would agree that it seems like a poor implementation in this specific scenario, but at least now I understand what I was seeing. Also, sorry for the reference to Quartz in my StackTrace-it was actually superfluous to this situation, as I also had this occurring in just standard execution, I just took the StackTrace from my Azure job instead of the console app. I ended up leaving the method as changed above, since I don't want my program to be more aware of Ninject, then the resolution root. Thanks! – tostringtheory Sep 08 '14 at 02:41