Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Ninject with Owin and InRequestScope

We are trying to use Ninject within an Owin with WebAPI pipeline. We have everything setup according to this documentation, but we cannot get InRequestScope() to work.

Here's the significant part of the startup.cs

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();

        // Web API routes
        config.MapHttpAttributeRoutes();

        // Ninject Setup
        app.UseNinjectMiddleware(NinjectConfig.CreateKernel);
        app.UseNinjectWebApi(config);
    }

}

NinjectConfig looks something like this:

public sealed class NinjectConfig
{
    public static IKernel CreateKernel()
    {
        var kernel = new StandardKernel();

        INinjectModule[] modules =
        {
            new ApplicationModule()
        };       

        instance.Load(modules);

        // Do we still need to do this wtih Owin?
        instance.Bind<IHttpModule>().To<OnePerRequestHttpModule>();
    }
}

Our ApplicationModule lives in a separate infrastructure project with access to all of our different layers, for handling DI & Mapping:

public class ApplicationModule: NinjectModule
{

    public override void Load()
    {
        // IUnitOfWork / EF Setups
        Bind<ApplicationContext>().ToSelf().InRequestScope();

        Bind<IUnitOfWork>().ToMethod(ctx => ctx.Kernel.Get<ApplicationContext>()});

        Bind<ApplicationContext>().ToMethod(ctx => ctx.Kernel.Get<ChromLimsContext>()}).WhenInjectedInto<IDal>();

        // other bindings for dals and business objects, etc.
    }
}

Then we have a couple interfaces:

public interface IUnitOfWork()
{
    void SaveChanges();

    Task SaveChangesAsync();
}

and

public interface IDal()
{
    // Crud operations, Sync and Async
}

then our actual classes using these:

public class SomeBusinessObject
{
    private IUnitOfWork _uow;
    private IDal _someDal;

    public SomeBusinessObject(IUnitOfWork uow, IDal someDal)
    {
        _uow = uow;
        _someDal = someDal;
    }

    public Task<SomeResult> SaveSomething(Something something)
    {
        _someDal.Save(something);
        _uow.SaveChanges();
    }
}

Some Dal

public class SomeDal : IDal {

    private ApplicationContext _applicationContext;

    public SomeDal(ApplicationContext applicationContext)
    {
        _applicationContext = applicationContext;
    }

    public void Save(Something something)
    {
        _applicationContext.Somethings.Add(something);
    }
}

Our EF DbContext

public class ApplicationContext : DbContext, IUnitOfWork
{
    // EF DBSet Definitions

    public void SaveChanges()
    {
        base.SaveChanges();
    }

}

The expectation is that for every request, a single instance of ApplicationContext is created and injected into the business objects as an IUnitOfWork implementation and into the IDals as an ApplicationContext.

Instead what is happening is that a new instance of ApplicationContext is being created for every single class that uses it. If I switch the scope from InRequestScope to InSingletonScope, then (as expected) exactly 1 instance is created for the entire application, and injected properly into the specified classes. Since that works, I'm assuming this isn't a binding issue, but instead an issue with the InRequestScope extension.

The only issue I could find similar to what I'm experiencing is this one, but unfortunately the solution did not work. I'm already referencing all of the packages he specified in both the WebApi and Infrastructure projects, and I double checked to make sure they are being copied to the build directory.

What am I doing wrong?

Edit: Some additional information. Looking at the Ninject source code in both Ninject.Web.WebApi.OwinHost and Ninject.Web.Common.OwinHost, it appears that the Owin Middleware adds the OwinWebApiRequestScopeProvider as the IWebApiRequestScopeProvider. This provider is then used in the InRequestScope() extension method to return a named scope called "Ninject_WebApiScope". This will be present until the target class that being injected into switches. The named scope then disappears, and a new scope is created. I think this may be what @BatteryBackupUnit was referring to in their comment, but I don't know how to correct it.

like image 562
aasukisuki Avatar asked Dec 04 '14 20:12

aasukisuki


2 Answers

This thread is related to this issue...

https://github.com/ninject/Ninject.Web.WebApi/issues/17

I've found that behavior of InRequestScope seems to change depending on how you inject them. For example...

public ValuesController(IValuesProvider valuesProvider1, IValuesProvider valuesProvider2)
{
    this.valuesProvider1 = valuesProvider1;
    this.valuesProvider2 = valuesProvider2;
}

Ninject will create and inject the same instance of IValuesProvider. However if the method were written as...

/// <summary>
/// Initializes a new instance of the <see cref="ValuesController"/> class.
/// </summary>
/// <param name="valuesProvider">The values provider.</param>
public Values2Controller(IKernel kernel)
{
    this.valuesProvider1 = kernel.Get<IValuesProvider>();
    this.valuesProvider2 = kernel.Get<IValuesProvider>();
}

...this will create two new instances.

like image 187
Mick Avatar answered Nov 10 '22 00:11

Mick


Based on the information in the link in @Mick's post I ended up adding my own extension method like this. I am not sure about the downsides.

public static class CustomRequestScope
{
    public static Ninject.Syntax.IBindingNamedWithOrOnSyntax<T> InCustomRequestScope<T>(this Ninject.Syntax.IBindingInSyntax<T> syntax)
    {
        return syntax.InScope(ctx => HttpContext.Current.Handler == null ? null : HttpContext.Current.Request);
    }
}

I do consider switching to another container because of this issue.

like image 22
Stilgar Avatar answered Nov 10 '22 02:11

Stilgar