Ninject - Request scope has already been disposed
Asked Answered
D

2

6

I'm using Ninject and the extensions EventBroker and DependencyCreation in an MVC 3 application. I've installed and am using the Ninject.MVC3 package and therefore the OnePerRequestModule.

I'm attempting to inject a service, called IParentService into a controller. IParentService has a dependency on ChildService created via the DependencyCreation extension (no hard reference).

Both services are registered on a local event broker instance (local to ParentService).

I want the IParentService to be scoped per request and I want the dependency and event broker to be disposed of at the same time as the IParentService, however, I'm getting a ScopeDisposedException. What am I doing wrong?

Some code:

Service Definitions:

public interface IParentService
{
}

public class ParentService : IParentService
{
    [EventPublication("topic://ParentService/MyEvent")]
    public event EventHandler<EventArgs> MyEvent;
}

public class ChildService
{
    [EventSubscription("topic://ParentService/MyEvent", typeof(bbv.Common.EventBroker.Handlers.Publisher))]
    public void OnMyEvent(object sender, EventArgs eventArgs)
    {            
    }
}

Kernel registration (NinjectWebCommon)

    private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<IParentService>().To<ParentService>()
            .InRequestScope()
            .OwnsEventBroker("ParentServiceBroker")
            .RegisterOnEventBroker("ParentServiceBroker");

        kernel.DefineDependency<IParentService, ChildService>();
        kernel.Bind<ChildService>().ToSelf()
            .WhenInjectedInto<ParentService>()
            .InDependencyCreatorScope()
            .RegisterOnEventBroker("ParentServiceBroker");            
    }  

Stack trace:

[ScopeDisposedException: The requested scope has already been disposed.]
   Ninject.Extensions.NamedScope.NamedScopeExtensionMethods.GetScope(IContext context, String scopeParameterName) in c:\Projects\Ninject\ninject.extensions.namedscope\src\Ninject.Extensions.NamedScope\NamedScopeExtensionMethods.cs:118
   Ninject.Extensions.NamedScope.NamedScopeExtensionMethods.GetScope(IContext context, String scopeParameterName) in c:\Projects\Ninject\ninject.extensions.namedscope\src\Ninject.Extensions.NamedScope\NamedScopeExtensionMethods.cs:126
   Ninject.Extensions.NamedScope.<>c__DisplayClass1`1.<InNamedScope>b__0(IContext context) in c:\Projects\Ninject\ninject.extensions.namedscope\src\Ninject.Extensions.NamedScope\NamedScopeExtensionMethods.cs:40
   Ninject.Planning.Bindings.BindingConfiguration.GetScope(IContext context) in c:\Projects\Ninject\ninject\src\Ninject\Planning\Bindings\BindingConfiguration.cs:119
   Ninject.Planning.Bindings.Binding.GetScope(IContext context) in c:\Projects\Ninject\ninject\src\Ninject\Planning\Bindings\Binding.cs:224
   Ninject.Activation.Context.GetScope() in c:\Projects\Ninject\ninject\src\Ninject\Activation\Context.cs:123
   Ninject.Activation.Caching.Cache.TryGet(IContext context) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Caching\Cache.cs:110
   Ninject.Activation.Context.Resolve() in c:\Projects\Ninject\ninject\src\Ninject\Activation\Context.cs:150
   Ninject.<>c__DisplayClass10.<Resolve>b__c(IBinding binding) in c:\Projects\Ninject\ninject\src\Ninject\KernelBase.cs:386
   System.Linq.WhereSelectEnumerableIterator`2.MoveNext() +145
   System.Linq.<CastIterator>d__b1`1.MoveNext() +85
   System.Linq.Enumerable.Single(IEnumerable`1 source) +191
   Ninject.ResolutionExtensions.Get(IResolutionRoot root, String name, IParameter[] parameters) in c:\Projects\Ninject\ninject\src\Ninject\Syntax\ResolutionExtensions.cs:50
   Ninject.Extensions.ContextPreservation.ContextPreservationExtensionMethods.ContextPreservingGet(IContext context, String name, IParameter[] parameters) in c:\Projects\Ninject\ninject.extensions.contextpreservation\src\Ninject.Extensions.ContextPreservation\ContextPreservationExtensionMethods.cs:56
   Ninject.Extensions.bbvEventBroker.<>c__DisplayClass2`1.<RegisterOnEventBroker>b__0(IContext ctx, T instance) in c:\Projects\Ninject\ninject.extensions.bbveventbroker\src\Ninject.Extensions.bbvEventBroker\EventBrokerExtensionMethods.cs:45
   Ninject.Planning.Bindings.<>c__DisplayClass29`1.<OnDeactivation>b__28(IContext context, Object instance) in c:\Projects\Ninject\ninject\src\Ninject\Planning\Bindings\BindingConfigurationBuilder.cs:513
   Ninject.Activation.Strategies.<>c__DisplayClass4.<Deactivate>b__3(Action`2 action) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Strategies\BindingActionStrategy.cs:42
   Ninject.Infrastructure.Language.ExtensionsForIEnumerableOfT.Map(IEnumerable`1 series, Action`1 action) in c:\Projects\Ninject\ninject\src\Ninject\Infrastructure\Language\ExtensionsForIEnumerableOfT.cs:32
   Ninject.Activation.Strategies.BindingActionStrategy.Deactivate(IContext context, InstanceReference reference) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Strategies\BindingActionStrategy.cs:42
   Ninject.Activation.<>c__DisplayClass6.<Deactivate>b__4(IActivationStrategy s) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Pipeline.cs:72
   Ninject.Infrastructure.Language.ExtensionsForIEnumerableOfT.Map(IEnumerable`1 series, Action`1 action) in c:\Projects\Ninject\ninject\src\Ninject\Infrastructure\Language\ExtensionsForIEnumerableOfT.cs:32
   Ninject.Activation.Pipeline.Deactivate(IContext context, InstanceReference reference) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Pipeline.cs:72
   Ninject.Activation.Caching.Cache.Forget(CacheEntry entry) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Caching\Cache.cs:253
   Ninject.Activation.Caching.Cache.Forget(IEnumerable`1 cacheEntries) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Caching\Cache.cs:242
   Ninject.Activation.Caching.Cache.Clear(Object scope) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Caching\Cache.cs:197
   Ninject.Web.Common.<>c__DisplayClass2.<DeactivateInstancesForCurrentHttpRequest>b__1(IKernel kernel) in c:\Projects\Ninject\Ninject.Web.Common\src\Ninject.Web.Common\OnePerRequestHttpModule.cs:74
   Ninject.GlobalKernelRegistration.MapKernels(Action`1 action) in c:\Projects\Ninject\ninject\src\Ninject\GlobalKernelRegistration.cs:75
   Ninject.Web.Common.OnePerRequestHttpModule.DeactivateInstancesForCurrentHttpRequest() in c:\Projects\Ninject\Ninject.Web.Common\src\Ninject.Web.Common\OnePerRequestHttpModule.cs:74
   Ninject.Web.Common.OnePerRequestHttpModule.<Init>b__0(Object o, EventArgs e) in c:\Projects\Ninject\Ninject.Web.Common\src\Ninject.Web.Common\OnePerRequestHttpModule.cs:56
   System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +136
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +69

EDIT - MORE DETAILS

The error is thrown within a deactivation delegate that is set in the call to RegisterOnEventBroker, where the code attempts to unregister any objects registered on the event broker. It fails because the event broker scope has been disposed, presumably because the parent service has been disposed. As far as I am aware, Ninject will only call OnDeactivation delegates for objects with lifetimes other than Transient scope, so why this doesn't work when the parent service is registered in RequestScope confuses me. Transient scope is not sufficient for the parent service because I'm experiencing memory leaks because of this issue.

I'm starting to wonder if this is a bug in the EventBroker extension.

Donkey answered 30/4, 2013 at 19:52 Comment(0)
V
2

You must first bind IParentService to ParentService then use self-binding of concrete type kernel.Bind<ParentService>().ToSelf() to define Object scope and event broker.

    private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<IParentService>().To<ParentService>();


        kernel.Bind<ParentService>().ToSelf()
       .InRequestScope()
       .OwnsEventBroker("ParentServiceBroker")
       .RegisterOnEventBroker("ParentServiceBroker");

        kernel.DefineDependency<IParentService, ChildService>();
        kernel.Bind<ChildService>().ToSelf()
            .WhenInjectedInto<ParentService>()
            .InDependencyCreatorScope()
            .RegisterOnEventBroker("ParentServiceBroker"); 
    }    

Edited: if the type you’re resolving is a concrete type (like ParentService above), Ninject will automatically create a default association via a mechanism called implicit self binding. Like this:

 kernel.Bind<ParentService>().ToSelf();

On the other hand implicit self-bindings are generated in the default Object Scope which is Transient. This is why your code does not run in Request scope.

For more information see here

Edited 2:

There is a bug in bbvEventBroker extension in Request scope which cause EventBroker was disposed before disposing of the object which registers on that EventBroker. Therefore in OnDeactivation method of the object there is no EventBroker which its Unregister can be invoked and ScopeDisposedException wan thrown.

    public static IBindingOnSyntax<T> OwnsEventBroker<T>(this IBindingOnSyntax<T> syntax, string eventBrokerName)
    {
        string namedScopeName = "EventBrokerScope" + eventBrokerName;
        syntax.DefinesNamedScope(namedScopeName);
        syntax.Kernel.Bind<IEventBroker>().To<EventBroker>().InNamedScope(namedScopeName).Named(eventBrokerName);
        syntax.Kernel.Bind<IEventBroker>().ToMethod(ctx => ctx.ContextPreservingGet<IEventBroker>(eventBrokerName)).WhenTargetNamed(eventBrokerName);
        return syntax;
    }

You can see in OwnsEventBroker method NamedScope define in scope of the object (ParentService) which enforce it to dispose before the object (ParentService).

On the other hand in OnDeactivation of the object (ParentService) there is need to EventBroker which disposed earlier.

    public static IBindingOnSyntax<T> RegisterOnEventBroker<T>(
        this IBindingOnSyntax<T> syntax, string eventBrokerName)
    {
        return
            syntax.OnActivation((ctx, instance) => ctx.ContextPreservingGet<IEventBroker>(eventBrokerName).Register(instance))
                  .OnDeactivation((ctx, instance) => ctx.ContextPreservingGet<IEventBroker>(eventBrokerName).Unregister(instance));
    }

EventBrokerExtensionMethods.cs

The solution is creating the object tree with NamedScope. Define parent in Request scope while it define a NamedScope for its children (Publisher/Subscriber) and owns the event broker (OwnsEventBroker). Then define a Publisher(ChildService1) and a Subscriber (ChildService2) in the named scope was defined by the parent. In this way you can ensure that the event broker's owner will be disposed after their childen.

Vacant answered 8/5, 2013 at 15:8 Comment(3)
Thanks Kambiz, that does resolve the error. Can you elaborate a little more on what was wrong with the way I was doing it and why it needs to be done this way?Donkey
I've doubled checked this but in my test I'm injecting IParentService not the concrete type. This appeared to fix my problem but actually it was just using your first binding instead of the second 'self' binding. If I use your code and inject a 'ParentService' instance I get the same exception.Donkey
@nukefusion You are right; I think this is bug. I found that When Scope is set to Request, EventBorker NamedScope is disposed while ParentService is in disposing stage. When OnDeactivation try to call ctx.ContextPreservingGet<IEventBroker>(eventBrokerName) to get EventBroker instance, the exception is thrown.Vacant
C
2

Ninject core currently deactivates objects that are in the scope of an object before deactivating the object itself.

Changing the order seems to fix that problem. Although before pushing that change I have to check what side effects this can have for other situations.

Caylor answered 11/5, 2013 at 11:19 Comment(1)
Thanks for looking into this Remo. Is there an open issue for this so I can track it?Donkey

© 2022 - 2024 — McMap. All rights reserved.