DEV Community

loading...

Wiring up Ninject with ASP.NET Core 2.0

Ivan
・4 min read

This guide is for Ninject versions 3.3.x

Ninject is a lightning-fast, ultra-lightweight dependency injector for .NET applications. It helps you split your application into a collection of loosely-coupled, highly-cohesive pieces, and then glue them back together in a flexible manner. By using Ninject to support your software's architecture, your code will become easier to write, reuse, test, and modify.

To test if the injector is working correctly, create a service that implements an interface

    public interface ITestService
    {
        string GetData();
    }

    public class TestService : ITestService
    {
        public string GetData()
        {
            return "some magic string";
        }
    }
Enter fullscreen mode Exit fullscreen mode

Download the package from Nuget

Using the package manager console Install-Package Ninject -Version 3.3.4

Using dotnet cli dotnet add package Ninject --version 3.3.4

Add these members to Startup.cs class as shown below

    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;  

    private sealed class Scope : DisposableObject { }

Enter fullscreen mode Exit fullscreen mode

Add the following binding in the end of ConfigureServices (Startup.cs)

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

Create class RequestScopingStartupFilter implementing the IStartupFilter interface

    public sealed class RequestScopingStartupFilter : IStartupFilter
    {
        private readonly Func<IDisposable> requestScopeProvider;

        public RequestScopingStartupFilter(Func<IDisposable> requestScopeProvider)
        {
            if (requestScopeProvider == null)
            {
                throw new ArgumentNullException(nameof(requestScopeProvider));
            }

            this.requestScopeProvider = requestScopeProvider;
        }

        public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> nextFilter)
        {
            return builder =>
            {
                ConfigureRequestScoping(builder);

                nextFilter(builder);
            };
        }

        private void ConfigureRequestScoping(IApplicationBuilder builder)
        {
            builder.Use(async (context, next) =>
            {
                using (var scope = this.requestScopeProvider())
                {
                    await next();
                }
            });
        }
    }
Enter fullscreen mode Exit fullscreen mode

Create a static class AspNetCoreExtensions with the following extension method

    using System;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.DependencyInjection;

    public static class AspNetCoreExtensions
    {
        public static void AddRequestScopingMiddleware(this IServiceCollection services, 
            Func<IDisposable> requestScopeProvider)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            if (requestScopeProvider == null)
            {
                throw new ArgumentNullException(nameof(requestScopeProvider));
            }

            services
                .AddSingleton<IStartupFilter>(new
                    RequestScopingStartupFilter(requestScopeProvider));
        }
    }
Enter fullscreen mode Exit fullscreen mode

Configure to use the middleware in ConfigureServices(in the end of the method)

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

Create a file Activators.cs with the following

   public sealed class DelegatingControllerActivator : IControllerActivator
        {
            private readonly Func<ControllerContext, object> controllerCreator;
            private readonly Action<ControllerContext, object> controllerReleaser;

            public DelegatingControllerActivator(Func<ControllerContext, object> controllerCreator,
                Action<ControllerContext, object> controllerReleaser = null)
            {
                this.controllerCreator = controllerCreator ?? 
                    throw new ArgumentNullException(nameof(controllerCreator));
                this.controllerReleaser = controllerReleaser ?? ((_, __) => { });
            }

            public object Create(ControllerContext context) => this.controllerCreator(context);
            public void Release(ControllerContext context, object controller) =>             
                this.controllerReleaser(context, controller);
        } 
Enter fullscreen mode Exit fullscreen mode

Add the following extension method to AspNetCoreExtensions.cs

        public static void AddCustomControllerActivation(this IServiceCollection services,
            Func<Type, object> activator)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
            if (activator == null) throw new ArgumentNullException(nameof(activator));

            services.AddSingleton<IControllerActivator>(new DelegatingControllerActivator(
                context => activator(context.ActionDescriptor.ControllerTypeInfo.AsType())));
        }
Enter fullscreen mode Exit fullscreen mode

Append the following to the end of ConfigureServices

services.AddCustomControllerActivation(Resolve);

Add another class to Activators.cs

        public sealed class DelegatingViewComponentActivator : IViewComponentActivator
        {
            private readonly Func<Type, object> viewComponentCreator;
            private readonly Action<object> viewComponentReleaser;

            public DelegatingViewComponentActivator(Func<Type, object> viewComponentCreator,
                Action<object> viewComponentReleaser = null)
            {
                this.viewComponentCreator = viewComponentCreator ?? 
                    throw new ArgumentNullException(nameof(viewComponentCreator));
                this.viewComponentReleaser = viewComponentReleaser ?? (_ => { });
            }

            public object Create(ViewComponentContext context) =>
                this.viewComponentCreator(context.ViewComponentDescriptor.TypeInfo.AsType());

            public void Release(ViewComponentContext context, object viewComponent) =>
                this.viewComponentReleaser(viewComponent);
        }
Enter fullscreen mode Exit fullscreen mode

And another extension method in AspNetCoreExtensions.cs

       public static void AddCustomViewComponentActivation(this IServiceCollection services, 
            Func<Type, object> activator)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
            if (activator == null) throw new ArgumentNullException(nameof(activator));

            services.AddSingleton<IViewComponentActivator>(
new DelegatingViewComponentActivator(activator));
        }
Enter fullscreen mode Exit fullscreen mode

then call it form ConfigureServices (should be the last invoked)

This is what ConfigureServices should look like now

        public void ConfigureServices(IServiceCollection services)
        {
            // Other configurations

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

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

        }
Enter fullscreen mode Exit fullscreen mode

Create an ApplicationBuilderExtensions.cs with a static class in it

    using System;
    using System.Globalization;
    using System.Linq;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc.ApplicationParts;
    using Microsoft.AspNetCore.Mvc.Controllers;
    using Microsoft.Extensions.DependencyInjection;
    using Ninject;

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

        public static Type[] GetControllerTypes(this IApplicationBuilder builder)
        {
            var manager = builder.ApplicationServices.GetRequiredService<ApplicationPartManager>();

            var feature = new ControllerFeature();
            manager.PopulateFeature(feature);

            return feature.Controllers.Select(t => t.AsType()).ToArray();
        }

        public static T GetRequestService<T>(this IApplicationBuilder builder) where T : class
        {
            if (builder == null) throw new ArgumentNullException(nameof(builder));

            return GetRequestServiceProvider(builder).GetService<T>();
        }

        private static IServiceProvider GetRequestServiceProvider(IApplicationBuilder builder)
        {
            var accessor = builder.ApplicationServices.GetService<IHttpContextAccessor>();

            if (accessor == null)
            {
                throw new InvalidOperationException(      
          typeof(IHttpContextAccessor).FullName);
            }

            var context = accessor.HttpContext;

            if (context == null)
            {
                throw new InvalidOperationException("No HttpContext.");
            }

            return context.RequestServices;
        }
    }

Enter fullscreen mode Exit fullscreen mode

Add the following method in Startup class

        private IKernel RegisterApplicationComponents(IApplicationBuilder app)
        {
            // IKernelConfiguration config = new KernelConfiguration();
            var kernel = new StandardKernel();

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

            // This is where our bindings are configurated
             kernel.Bind<ITestService>().To<TestService>().InScope(RequestScope);            

            // Cross-wire required framework services
            kernel.BindToMethod(app.GetRequestService<IViewBufferScope>);

            return kernel;
        }
Enter fullscreen mode Exit fullscreen mode

and call it from Configure (in the beginning)

this.Kernel = this.RegisterApplicationComponents(app);

Create a TestController to see if our DI works

    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        private readonly ITestService testService;

        public ValuesController(ITestService testService)
        {
            this.testService = testService;
            this.factory = factory;
        }

        [HttpGet]
        public IActionResult Get()
        {
            var result = this.testService.GetData();

            return this.Ok(result);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Place a breakpoint in the constructor of our TestService and in the Get action to see the magic.

I have compiled this article from this answer on stackoverflow with a little help from this dotnetjunkie repo.

Discussion (8)

Collapse
nigelgos profile image
Nigel Gossage

Nice little write up. I've hit an issue where I want my service to take in IOptions as a constructor but the standard way of adding an IOptions using DI are not working with this tutorial. Any tips?

Collapse
cwetanow profile image
Ivan Author

Could you show some code ?

Collapse
nigelgos profile image
Nigel Gossage

The comments system is throwing loads of errors, it's making it hard to post :-/

Thread Thread
lr12003 profile image
David Lopez • Edited

I had the same issue, I added it as a parameter in the Configure method in the Startup file

Collapse
ataraxia89 profile image
ataraxia89 • Edited

All of this works fine except for a couple of tweaks needed to use a custom DbContext. I have ApplicationDbContext, which inherits from IdentityDbContext.

Within Startup.cs, I added an IConfiguration parameter to the Configure and RegisterApplicationComponents methods (one feeds the other of course), then within RegisterApplicationComponents I added:

kernel.Bind<IConfiguration>().ToConstant(configurationVariable);

My API is running in Azure and the IConfiguration gets injected at runtime. Then, within the ApplicationDbContext class, I had to add the following constructor:

public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IConfiguration configurationVariable) : base(options)

Within the constructor I call configurationVariable.GetConnectionString("MyConnectionStringName") and assign it to a private string variable "_connectionStringVariable" within the ApplicationDbContextClass. Then added the following override:

protected override void OnModelCreating(ModelBuilder builder)

...which contains a couple of bits specific to my application but most importantly calls base.OnModelCreating(builder). I then added the following override:

protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.UseSqlServer(_connectionStringVariable);
_connectionString = null;
base.OnConfiguring(builder);
}

The ApplicationDbContext can then be used by my internal services.

Collapse
tarakeshp profile image
Tarakesh Pulikonda • Edited

public class BaseController : Controller
{
public BaseController()
{
var ctx = HttpContext;
}
}

public class ValuesController: BaseController
{
public ValuesController() : base() {

}
}

HttpContext is null.
Any help is appreciated

Collapse
sahilkhan99 profile image
Irfan Khan

Nice Article. I have also added IOptions to inject in to constructor.
Please see the gist here
github.com/ninject/Ninject/issues/270

Thank you.

Collapse
lr12003 profile image
David Lopez

Nice walkthrough! I have an issue, when I decorate my Controllers e.g. [Authorize(Policy = "ApiUser")] it doesn't work :/ before it was working!