Looking for a Ninject scope that behaves like InRequestScope
Asked Answered
C

2

6

On my service layer I have injected an UnitOfWork and 2 repositories in the constructor. The Unit of Work and repository have an instance of a DbContext I want to share between the two of them. How can I do that with Ninject ? Which scope should be considered ?

I am not in a web application so I can't use InRequestScope.

I try to do something similar... and I am using DI however, I need my UoW to be Disposed and created like this.

using (IUnitOfWork uow = new UnitOfWorkFactory.Create())
{
    _testARepository.Insert(a);
    _testBRepository.Insert(b);

    uow.SaveChanges();
}

EDIT: I just want to be sure i understand… after look at https://github.com/ninject/ninject.extensions.namedscope/wiki/InNamedScope i though about my current console application architecture which actually use Ninject.

Lets say :

Class A is a Service layer class

Class B is an unit of work which take into parameter an interface (IContextFactory)

Class C is a repository which take into parameter an interface (IContextFactory)

The idea here is to be able to do context operations on 2 or more repository and using the unit of work to apply the changes.

Class D is a context factory (Entity Framework) which provide an instance (keep in a container) of the context which is shared between Class B et C (.. and would be for other repositories aswell).

The context factory keep the instance in his container so i don’t want to reuse this instance all the name since the context need to be disposed at the end of the service operaiton.. it is the main purpose of the InNamedScope actually ?

The solution would be but i am not sure at all i am doing it right, the services instance gonna be transcient which mean they actually never disposed ? :

Bind<IScsContextFactory>()
    .To<ScsContextFactory>()
    .InNamedScope("ServiceScope")
    .WithConstructorArgument(
         "connectionString", 
         ConfigurationUtility.GetConnectionString());

Bind<IUnitOfWork>().To<ScsUnitOfWork>();

Bind<IAccountRepository>().To<AccountRepository>();
Bind<IBlockedIpRepository>().To<BlockedIpRepository>();

Bind<IAccountService>().To<AccountService>().DefinesNamedScope("ServiceScope");
Bind<IBlockedIpService>().To<BlockedIpService>().DefinesNamedScope("ServiceScope");
Capillaceous answered 24/3, 2013 at 13:56 Comment(2)
related: #14554651Kelvin
related: #10585978Kelvin
K
5

UPDATE: This approach works against NuGet current, but relies in an anomaly in the InCallscope implementation which has been fixed in the current Unstable NuGet packages. I'll be tweaking this answer in a few days to reflect the best approach after some mulling over. NB the high level way of structuring stuff will stay pretty much identical, just the exact details of the Bind<DbContext>() scoping will work. (Hint: CreateNamedScope in unstable would work or one could set up the Command Handler as DefinesNamedScope. Reason I dont just do that is that I want to have something that composes/plays well with InRequestScope)


I highly recommend reading the Ninject.Extensions.NamedScope integration tests (seriously, find them and read and re-read them)

The DbContext is a Unit Of Work so no further wrapping is necessary.

As you want to be able to have multiple 'requests' in flight and want to have a single Unit of Work shared between them, you need to:

Bind<DbContext>()
    .ToMethod( ctx => 
        new DbContext( 
            connectionStringName: ConfigurationUtility.GetConnectionString() ))
    .InCallScope();

The InCallScope() means that:

  1. for a given object graph composed for a single kernel.Get() Call (hence In Call Scope), everyone that requires an DbContext will get the same instance.
  2. the IDisposable.Dispose() will be called when a Kernel.Release() happens for the root object (or a Kernel.Components.Get<ICache>().Clear() happens for the root if it is not .InCallScope())

There should be no reason to use InNamedScope() and DefinesNamedScope(); You don't have long-lived objects you're trying to exclude from the default pooling / parenting / grouping.

If you do the above, you should be able to:

var command = kernel.Get<ICommand>();
try {
    command.Execute();
} finally {
    kernel.Components.Get<ICache>().Clear( command ); // Dispose of DbContext happens here
}

The Command implementation looks like:

class Command : ICommand {
    readonly IAccountRepository _ar;
    readonly IBlockedIpRepository _br;
    readonly DbContext _ctx;
    public Command(IAccountRepository ar, IBlockedIpRepository br, DbContext ctx){
        _ar = ar;
        _br = br;
        _ctx = ctx;
    }
    void ICommand.Execute(){
        _ar.Insert(a);
        _br.Insert(b);
        _ctx.saveChanges();
    }
}

Note that in general, I avoid having an implicit Unit of Work in this way, and instead surface it's creation and Disposal. This makes a Command look like this:

class Command : ICommand {
    readonly IAccountService _as;
    readonly IBlockedIpService _bs;
    readonly Func<DbContext> _createContext;
    public Command(IAccountService @as, IBlockedIpServices bs, Func<DbContext> createContext){
        _as = @as;
        _bs = bs;
        _createContext = createContext;
    }
    void ICommand.Execute(){
        using(var ctx = _createContext()) {
            _ar.InsertA(ctx);
            _br.InsertB(ctx);
            ctx.saveChanges();
        }
   }

This involves no usage of .InCallScope() on the Bind<DbContext>() (but does require the presence of Ninject.Extensions.Factory's FactoryModule to synthesize the Func<DbContext> from a straightforward Bind<DbContext>().

Kelvin answered 24/3, 2013 at 22:22 Comment(10)
I can't use singleton .. cause the services are located in commands which are created per threads. This is a game server. But the InNamedScope seem to be what i was looking for.. when the scope end does others instances (see above example added) which are transcient but par of the instance that has a scope get disposed somehow ? or do i have to call Release after the service is executed ? What happen if the parent instance get GC ?Capillaceous
Thanks that a very nice example. Just wondering.. what happen if the composition root isn't exactly like that ? I mean i do not have access to the kernel.. except if i use the service locator. Any idea ? It all start from a singleton.. I am integrating my architecture over some other library.. and the only entry point i found is the commands (server queries where i inject my services). So basically they are all instancied once.. and requests are redirected to them.Capillaceous
@Capillaceous Not sure what you mean here. Perhaps a separate question with an illustration of your constraints might be in order. Firstly, you can use github.com/ninject/ninject.extensions.factory/wiki to give appropriately constrained access to the creation of Command Handlers etc. at the right point in your processing cycle. e.g. kernel.Get<ICommand>(); can become you having an ICommandFactory (with an ICommand CreateCommand()) provided to your ServerQueryProcessor 'server query' implementation. In other words, your query processor becomes a Composition Root.Kelvin
Unfortunately that's all just hand waving until you give an example [problem and someone shows an appropriate impl approach. I think this question has gone way beyond SRP so best not to edit any further into it methinks!Kelvin
I have also worked on a solution on my side. It may not be as elegan or clean than yours but this is still a nice solution which enable me to have many transactions in a service and enable me to make transaction between different contexts (which i may need later). Its an UnitOfWorkFactory that have a ContextFactory injected. The idea of the unit of work (i know DbContext is already one..) is to abstract away EF from the service layer which i think i have been able to do with this solution. The only downside ive found is the repos are using the uow to do their operations on the context.Capillaceous
@Capillaceous As long as you have your actual problem solved, that's good. Without being with you it does still sound like wrapping a wrapper (memegenerator.net/instance/20247106) though :P Also extra layers can hide other design improvements such as infoq.com/news/2013/02/ddd-bounded-context-large-domainKelvin
I know that DbContext is a wrapper.. but using it mean that you expose EF in your service layer which i am not sure its the best idea of all. Plus its more testable so that the reason poeple seem to abstract away the dbcontext.Capillaceous
@Capillaceous The wrapping a wrapper bit is more prompted by the need to have an IScsContextFactory because you dont use the DbContext directly. I wouldnt want either a Unit of Work that can span DbContexts or someone to have to understand me and my unit of work. I personally dont wrap them and keep them private. But there are lots of ways of skinning the cat so no need to have a winner today. I'd question the 'its more testable' theory; if the Unit of Work is being passed outside of a given layer, you're going to start running into Law of Demeter issues pretty soon.Kelvin
Yeah you right here. I removed the need of IScsContextFactory. Because an unit of work actually should only have one dbcontext associated.Capillaceous
@Capillaceous Obv you can still put a static Factory Method on the DbContext partial to maintain encapsulation - having a new in a ToMethod as in the snippet above and pasting that around the place is obviously going to be unmaintainable unless you only have one of themKelvin
K
2

As discussed in the other answer, InCallScope is not a good approach to solving this problem.

For now I'm dumping some code that works against the latest NuGet Unstable / Include PreRelease / Instal-Package -Pre editions of Ninject.Web.Common without a clear explanation. I will translate this to an article in the Ninject.Extensions.NamedScope wiki at some stagehave started to write a walkthrough of this technique in the Ninject.Extensions.NamedScope wiki's CreateNamedScope/GetScope article.

Possibly some bits will become Pull Request(s) at some stage too (Hat tip to @Remo Gloor who supplied me the outline code). The associated tests and learning tests are in this gist for now), pending packaging in a proper released format TBD.

The exec summary is you Load the Module below into your Kernel and use .InRequestScope() on everything you want created / Disposed per handler invocation and then feed requests through via IHandlerComposer.ComposeCallDispose.

If you use the following Module:

public class Module : NinjectModule
{
    public override void Load()
    {
        Bind<IHandlerComposer>().To<NinjectRequestScopedHandlerComposer>();

        // Wire it up so InRequestScope will work for Handler scopes
        Bind<INinjectRequestHandlerScopeFactory>().To<NinjectRequestHandlerScopeFactory>();
        NinjectRequestHandlerScopeFactory.NinjectHttpApplicationPlugin.RegisterIn( Kernel );
    }
}

Which wires in a Factory[1] and NinjectHttpApplicationPlugin that exposes:

public interface INinjectRequestHandlerScopeFactory
{
    NamedScope CreateRequestHandlerScope();
}

Then you can use this Composer to Run a Request InRequestScope():

public interface IHandlerComposer
{
    void ComposeCallDispose( Type type, Action<object> callback );
}

Implemented as:

class NinjectRequestScopedHandlerComposer : IHandlerComposer
{
    readonly INinjectRequestHandlerScopeFactory _requestHandlerScopeFactory;

    public NinjectRequestScopedHandlerComposer( INinjectRequestHandlerScopeFactory requestHandlerScopeFactory )
    {
        _requestHandlerScopeFactory = requestHandlerScopeFactory;
    }

    void IHandlerComposer.ComposeCallDispose( Type handlerType, Action<object> callback )
    {
        using ( var resolutionRoot = _requestHandlerScopeFactory.CreateRequestHandlerScope() )
            foreach ( object handler in resolutionRoot.GetAll( handlerType ) )
                callback( handler );
    }
}

The Ninject Infrastructure stuff:

class NinjectRequestHandlerScopeFactory : INinjectRequestHandlerScopeFactory
{
    internal const string ScopeName = "Handler";

    readonly IKernel _kernel;

    public NinjectRequestHandlerScopeFactory( IKernel kernel )
    {
        _kernel = kernel;
    }

    NamedScope INinjectRequestHandlerScopeFactory.CreateRequestHandlerScope()
    {
        return _kernel.CreateNamedScope( ScopeName );
    }

    /// <summary>
    /// When plugged in as a Ninject Kernel Component via <c>RegisterIn(IKernel)</c>, makes the Named Scope generated during IHandlerFactory.RunAndDispose available for use via the Ninject.Web.Common's <c>.InRequestScope()</c> Binding extension.
    /// </summary>
    public class NinjectHttpApplicationPlugin : NinjectComponent, INinjectHttpApplicationPlugin
    {
        readonly IKernel kernel;

        public static void RegisterIn( IKernel kernel )
        {
            kernel.Components.Add<INinjectHttpApplicationPlugin, NinjectHttpApplicationPlugin>();
        }

        public NinjectHttpApplicationPlugin( IKernel kernel )
        {
            this.kernel = kernel;
        }

        object INinjectHttpApplicationPlugin.GetRequestScope( IContext context )
        {
            // TODO PR for TrgGetScope
            try
            {
                return NamedScopeExtensionMethods.GetScope( context, ScopeName );
            }
            catch ( UnknownScopeException )
            {
                return null;
            }
        }

        void INinjectHttpApplicationPlugin.Start()
        {
        }

        void INinjectHttpApplicationPlugin.Stop()
        {
        }
    }
}
Kelvin answered 5/4, 2013 at 14:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.