Wiring up Ninject with ASP.NET Core 2.0

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";
}
}

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 { }

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

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

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

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

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

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

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

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

Add the following method in Startup class

private IKernel RegisterApplicationComponents(IApplicationBuilder app)
{
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;
}

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

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.


Originally published at dev.to.