How to integrate Ninject into ASP.NET Core 2.0 Web applications?
Asked Answered
B

1

16

I have found out that Ninject has recently introduced support for .NET Standard 2.0 / .NET Core 2.0.

However, I cannot find any extension to actually integrate it in the Web application (e.g similar to Ninject.Web.Common)

Looking on the code from an old ASP.NET MVC solution, I realized that the whole mechanism is different as the classic one relied on WebActivatorEx.PreApplicationStartMethod and WebActivatorEx.ApplicationShutdownMethodAttribute which are no longer available in ASP.NET Core.

Also, the old Ninject.Web.Common assembly provided several useful classes used for initialization - Bootstrapper, OnePerRequestHttpModule, NinjectHttpModule:

public static void Start()
{
    DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
    DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
    Bootstrapper.Initialize(CreateKernel);
}

Question: is there any example of how to integrate Ninject into an ASP.NET Core 2.0 Web application?

Begat answered 11/10, 2017 at 16:30 Comment(11)
See: https://mcmap.net/q/128498/-continued-ninject-support-in-asp-net-core-mvcSectorial
Here's an example of how to integrate Ninject with ASP.NET Core: github.com/dotnetjunkie/Missing-Core-DI-Extensions/blob/master/…Sectorial
@Sectorial - I have also find this example and tried to use it, but got stuck because it uses some types I could not find: Scope and IReadOnlyKernel . Last commit was done about 1 year ago, so it might not be compatible with the current stable version of Ninject (3.3.0) and/or ASP.NET Core (2.0).Begat
You need to use Ninject 4.0 to work with those examples, or do some small changes to get it working with Ninject 3.3. Those examples use Ninject 4, since Ninject 3 is not compatible with .NET Standard.Sectorial
@Sectorial - latest stable version (3.3.1) lists .NET Standard 2.0 as a dependency and can be installed in a .NET Core 2.0 project.Begat
Ah, it can? I missed that.Sectorial
@Sectorial - I have tested with both 4.0 and 3.3.1 and it seems to work (not sure if injected instance in controller is caused by Ninject or ASP.NET though, however kernel.Get<TInterface> works, so at least the ninject part is properly initialized. However, I would stick with 3.3.1, as 4.0 is beta and the referenced answer argues about the final version not coming any time soon.Begat
Hi Alexei, try to understand how this integration model works. It's important to understand this. There's no magic. Dive into the code, set break points.Sectorial
@Sectorial - yes, my test service is being created via the custom code (ConfigureRequestScoping), so it works. Should I provide an answer containing working examples for both versions? It is a little bit different for version 3.3.1 like you said and also a minor change is required to fix an issue for 4.0.Begat
It's always good to get your question anwsered, and answering your own question will be highly appreciated by other developers who are googling for an answer.Sectorial
Created a git repo implementing @Alexei answer above, so the next guy can jut download a complete solution instead of coping from here... - works great!Nikitanikki
B
21

Short answer:

check this project. However, it relies on Ninject 4.0.0 which is still in beta version and it seems far from a final version (source). For Ninject 3.3.x look below.

Long answer:

Thanks to @Steven I managed to create a working solution of ASP.NET Core 2.0 and Ninject (both 3.3.x and 4.0). The code is mainly from Missing-Core-DI-Extensions Git repo, so great thanks to dotnetjunkie.

The following must be performed regardless of referenced Ninject version:

1) Include AspNetCoreExtensions.cs and AspNetCoreMvcExtensions.cs in your project.

2) Create a very simple service to use for testing the DI: TestService that implements ITestService:

public class TestService : ITestService
{
    public int Data { get; private set; }

    public TestService()
    {
        Data = 42;
    }
} 

public interface ITestService
{
    int Data { get; }
}

Ninject 3.3.x

Change Startup.cs as indicated below:

1) add these members

private readonly AsyncLocal<Scope> scopeProvider = new AsyncLocal<Scope>();
private IKernel Kernel { get; set; }

private object Resolve(Type type) => Kernel.Get(type);
private object RequestScope(IContext context) => scopeProvider.Value;  

2) Add to ConfigureServices(IServiceCollection services) (at the end)

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

services.AddRequestScopingMiddleware(() => scopeProvider.Value = new Scope());
services.AddCustomControllerActivation(Resolve);
services.AddCustomViewComponentActivation(Resolve);

3) Add to Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) (at the beginning)

Kernel = RegisterApplicationComponents(app, loggerFactory);

4) Add the following method and inner class:

private IKernel RegisterApplicationComponents(
    IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    // IKernelConfiguration config = new KernelConfiguration();
    Kernel = new StandardKernel();

    // Register application services
    foreach (var ctrlType in app.GetControllerTypes())
    {
        Kernel.Bind(ctrlType).ToSelf().InScope(RequestScope);
    }

    Kernel.Bind<ITestService>().To<TestService>().InScope(RequestScope);

    // Cross-wire required framework services
    Kernel.BindToMethod(app.GetRequestService<IViewBufferScope>);
    Kernel.Bind<ILoggerFactory>().ToConstant(loggerFactory);

    return Kernel;
}

private sealed class Scope : DisposableObject { }

5) Create BindToMethod extension method

public static class BindingHelpers
{
    public static void BindToMethod<T>(this IKernel config, Func<T> method) => 
        config.Bind<T>().ToMethod(c => method());
}

6) Test DI by injecting custom service into a controller, setting a breakpoint into test service constructor and checking the call stack. Besides providing an instance, the call stack should hit custom code to integrate Ninject (e.g. ConfigureRequestScoping method)

Ninject 4.0.0

IKernel has been deprecated in version 4, so IReadOnlyKernel and IKernelConfiguration classes should be used (although above code should work).

1) Use the new class (IReadOnlyKernel)

private readonly AsyncLocal<Scope> scopeProvider = new AsyncLocal<Scope>();
private IReadOnlyKernel Kernel { get; set; }

private object Resolve(Type type) => Kernel.Get(type);
private object RequestScope(IContext context) => scopeProvider.Value;

2) 3) are the same

4) The method is slightly different:

private IReadOnlyKernel RegisterApplicationComponents(
    IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    IKernelConfiguration config = new KernelConfiguration();

    // Register application services
    foreach (var ctrlType in app.GetControllerTypes())
    {
        config.Bind(ctrlType).ToSelf().InScope(RequestScope);
    }

    config.Bind<ITestService>().To<TestService>().InScope(RequestScope);

    // Cross-wire required framework services
    config.BindToMethod(app.GetRequestService<IViewBufferScope>);
    config.Bind<ILoggerFactory>().ToConstant(loggerFactory);

    return config.BuildReadonlyKernel();
}

5) The extension must use an IKernelConfiguration

public static class BindingHelpers
{
    public static void BindToMethod<T>(
        this IKernelConfiguration config, Func<T> method) =>
            config.Bind<T>().ToMethod(c => method());
}
Begat answered 14/10, 2017 at 18:20 Comment(17)
Step 3) is missing the code that actually needs to be added to the Configure(..) method, which I assume to be Kernel = RegisterApplicationComponents(app, loggerFactory);Thereafter
@Thereafter - it was indeed missing. Fixed it. Thanks. I am glad that more programmers are fond of Ninject and try to use in ASP.NET Core.Begat
@Alexei: There's a line in AspNetCoreMvcExtensions that doesn't compile: applicationTypeSelector = applicationTypeSelector ?? (type => !type.GetTypeInfo().Namespace.StartsWith("Microsoft")); Any thoughts? It's trying to apply ?? to Predicate<Type> and a lambda expression.Guppy
@Guppy - I checked in my project and it seems to work. I am using Target framewor = .NET Core 2.0. What is your target framework? Also, are you using Visual Studio 2017?Begat
@Alexei: Same thing: .NET Core 2.0 in VS 2017. That exact line compiles for you?Guppy
@Guppy - yes. Which language version are you using? My version is C# latest major version (default). I think this means C# 7.0.Begat
@Alexei: Mine is also C# latest major version (default). Same build error if I manually set it to C# 7.2.Guppy
@Guppy - I have no other suggestions other than posting a question here, on StackOverflow. Just make sure to include the whole function body.Begat
Are these external files from a random project still needed? Or have they been combined with a Ninject library/package since this was posted?Vlf
I am also running into issues, parsing RequestScope into config.Bind<ITestService>().To<TestService>().InScope(RequestScope); tells me `cannot convert from 'method group' to 'func<icontext, object>'Warta
@Alexei great feedback, do you know if the same code can be used for .NET Core 3? Or there is an easy way? ThanksCiapas
@Ciapas - never got to check it against .net core 3 yet (still swamped in 2.2 projects).Begat
@Alexei thanks a lot, last question. Whats the benefit of using ninject instead of the built in DI framework that comes with core?Ciapas
@Ciapas - it provides support for more complex contexts. I am using the built-in DI framework for some time and I recommend using it unless there is a strong reason to switch something else.Begat
@Ciapas - it provides support for more complex contexts. I am using the built-in DI framework for some time and I recommend using it unless there is a strong reason to switch something else.Begat
Appreciate it a lot @AlexeiCiapas
How to register Microsoft.AspNetCore.Identity using this solution? I try to use userManager in my controllers but when I do kernel.Bind<UserManager<AppUser>>().ToConstant(usrMgr).InRequestScope(); where usrMgr is parameter of Configure method I have an error while using userManager in Controller : ObjectDisposedException: Cannot access a disposed object. Object name: 'UserManager`1'.Shetrit

© 2022 - 2024 — McMap. All rights reserved.