Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ninject - Request scope has already been disposed

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.

like image 370
Matt B Avatar asked Oct 05 '22 06:10

Matt B


2 Answers

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.

like image 66
Kambiz Shahim Avatar answered Oct 09 '22 11:10

Kambiz Shahim


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.

like image 34
Remo Gloor Avatar answered Oct 09 '22 10:10

Remo Gloor