15
votes

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?

1
Here's an example of how to integrate Ninject with ASP.NET Core: github.com/dotnetjunkie/Missing-Core-DI-Extensions/blob/master/…Steven
@Steven - 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).Alexei - check Codidact
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.Steven
@Steven - 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.Alexei - check Codidact

1 Answers

20
votes

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());
}