Matt
Matt

Reputation: 1678

Injecting Ninject scoped objects for deterministic dispose

I'd like to use a Ninject custom scope within my application in order to contain the scope of a single activation of a DbContext to my core domain handler. I'm having trouble however, because the CommandScope object implements the INotifyWhenDisposed interface, which is a part of Ninject, and I don't want to take a dependency on Ninject within my domain.

I've tried a number of other ways to get the dependency into the code, with no success, including the use of Ninject Factories to expose an IScopeFactory, and a Func dependency. In the latter case, the problem is that (I think) Ninject does not wire up INotifyWhenDisposed.Dispose event because the binding target is Func not an IDisposable itself.

Anyway, here's the code of what I'm trying to achieve.

IoC

 Kernel
     .Bind<MyDbContext>()
     .ToSelf()
     .InScope(x => CommandScope.Current)
     .OnDeactivation(x => x.SaveChanges());

CommandScope

public class CommandScope : INotifyWhenDisposed
{
    public event Dispose;

    public bool IsDisposed { get; private set; }

    public static CommandScope Current { get; private set; }

    public static CommandScope Create()
    {
        CommandScope result = new CommandScope();
        Current = result;
        return result;
    }

    public void Dispose()
    {
        IsDisposed = true;
        Current = null;
        Dispose?.Invoke(this, EventArgs.Empty);
    }
}

Inside my domain...

public class Pipeline<TRequest, TResponse>
{
    readonly IRequestHandler<TRequest, TResponse> innerHandler;

    public Pipeline(IRequestHandler<TRequest, TResponse> handler)
    {
        innerHandler = handler;
    }

    public TResponse Handle(TRequest request)
    {
         using(CommandScope.Create())
         {
             handler.Handle(request);
         }
    }
}

Upvotes: 0

Views: 540

Answers (2)

Dave Thieben
Dave Thieben

Reputation: 5427

clearing the scope object from Ninject's ICache will force Ninject to close and dispose all instances controlled by the scope:

var activationCache = Kernel.Get<Ninject.Activation.Caching.ICache>();
activationCache.Clear(CommandScope.Current);

you may also consider the NamedScope extension to have better alternatives to your singleton pattern CommandScope.

Upvotes: 0

BatteryBackupUnit
BatteryBackupUnit

Reputation: 13233

You could use the decorator pattern (requires an interface) and only have the decorator implement the INotifyWhenDisposed interface - then put the decorator in the composition root - there you need a ninject reference anyway.

Alternatevily, you can use a Func Factory, create an IDisposable that actuall implements INotifyWhenDisposed (the consuming library however doesn't need to know about this). When accessing the instance in your composition root, you can still cast it to INotifyWhenDisposed (might actually not be necessary). For example:

.InScope(x => (INotifyWhenDisposed)CommandScope.Current)

A comment on the design: I would highyl recommend to split CommandScope into two classes: a factory and the actual scope. Also, in the long run you can probably save a lot of headache by making sure that a new scope (created by Create) does not replace an old one that has not been disposed yet. Otherwise you might very well miss a leak you introduce.

Upvotes: 2

Related Questions