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) { // 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; }
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.