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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With