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)
{
// 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.
Top comments (8)
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?
Could you show some code ?
The comments system is throwing loads of errors, it's making it hard to post :-/
I had the same issue, I added it as a parameter in the Configure method in the Startup file
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.
public class BaseController : Controller
{
public BaseController()
{
var ctx = HttpContext;
}
}
public class ValuesController: BaseController
{
public ValuesController() : base() {
}
}
HttpContext is null.
Any help is appreciated
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.
Nice walkthrough! I have an issue, when I decorate my Controllers e.g. [Authorize(Policy = "ApiUser")] it doesn't work :/ before it was working!