Injector.NET 10.1.0

dotnet add package Injector.NET --version 10.1.0
NuGet\Install-Package Injector.NET -Version 10.1.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Injector.NET" Version="10.1.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Injector.NET --version 10.1.0
#r "nuget: Injector.NET, 10.1.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Injector.NET as a Cake Addin
#addin nuget:?package=Injector.NET&version=10.1.0

// Install Injector.NET as a Cake Tool
#tool nuget:?package=Injector.NET&version=10.1.0

Injector.NET Build status AppVeyor tests Coverage Status GitHub last commit (branch)

A featherweight dependency injector written in C#.

Package name NuGet link
Injector.NET Nuget (with prereleases)
Injector.NET.Interfaces Nuget (with prereleases)

This documentation refers the version 10.X of the library (which is a complete reimplementation of the library)

Overview

Dependency Injection is a design pattern that helps you separate the dependencies of your code from its behavior. Additionaly it makes the code easy to test by let you mock the dependencies in your unit tests.

Key features

  • Dependency injection via constructor parameters or setters
  • Lazy dependency resolution
  • Custom service decoration (using AOP or interceptors)
  • Generic service support
  • All well-known service lifecycles are supported: Singleton, Transient, Scoped, Pooled, Instance
  • Configurable constructor selection (using attributes)
  • Extensible
  • and many more...

About services in general

  • They are class instances (most probably imlementing a particular interface).

  • They are declared in a IServiceCollection.

  • They are provided by an IInjector.

  • Every service can be requested multiple times.

  • Producible services are instantiated only when they are requested.

  • Every producible service has its own lifetime, which can be:

    • Singleton:
      • Instantiated only once in the root scope (on the first request) and released automatically when the root is disposed.
      • Dependency resolution is also done from the root scope.
    • Scoped:
      • Instantiated only once per parent scope (on the first request) and released automatically when the parent is disposed.
      • Dependency resolution is done from the parent scope.
    • Transient:
      • Instantiated on every request and released automatically when the parent scope is disposed.
      • Dependency resolution is done from the parent scope.
    • Pooled:
      • Instantiated in a separate pool (if necessary) and released automatically when the root scope is disposed
      • Dependency resolution is done from a dedicated scope.

    As you can see you should never free producible services manually.

  • Since Singleton and Instance services may be accessed parallelly they (and their dependencies) have to be thread safe.

Workflow of DI

  1. Creating a scope factory
  2. Registering services
  3. Decorating services (optional)
  4. Creating a scope
  5. Requesting service instances
  6. Destroying the injector
  7. Destroying the root scope

Points from 1 to 3 are done in initialization time (typically at application startup), points from 4 to 6 are executed multiple times, parallelly (e.g. per WEB request). Point 7 is done in finalization time (mostly at termination).

Creating a scope factory

The first step before we'd start is creating a scope factory:

using Solti.Utils.DI.Interfaces;
using Solti.Utils.DI;

using(IScopeFactory scopeFactory = ScopeFactory.Create(svcs => /*registering services*/))
{
  ...
}

In most of the cases you should use only one factory although you can have as much as you want.

Registering services

Registering a service can be done via several patterns (I name them recipes):

  • Service recipe: This is the most common way to file a service. To register a simple service just call the Service() generic method with the desired interface, implementation and lifetime:
    svcs.Service<IMyService, MyService>(Lifetime.Transient);
    
    You can register generic services as well:
    svcs.Service(typeof(IMyGenericService<>), typeof(MyGenericService<>), Lifetime.Singleton);
    
    Remarks:
    • Implementations must not have more than one public constructor (or you must annotate the appropriate one with the ServiceActivatorAttribute)!
    • A service may request other services via the constructor parameters or properties:
      • public class MyService: IMyService
        {
          public MyService(IInjector injector, IService_1 dep1, IService_2 dep2) {...}
          [Inject]
          publiuc IService3 Dep3 {get; set; /*or init*/}
        }
        
      • Deferred resolution also possible:
        public class MyService: IMyService
        {
          public MyService(ILazy<IService_1> /*or Lazy<IService_1>*/ dep1, ILazy<IService_2> dep2) {...}
        }
        
      • You can mark the requested dependency as optional. In this case the system won't throw if the dependency cannot be found:
        public class MyService: IMyService
        {
          public MyService(IService_1 dep1, [Options(Optional = true)]IService_2 dep2) {...}
        }
        
    • It's also possible to pass arbitrary constructor arguments:
      svcs.Service<IMyService, MyService>(new Dictionary<string, object?>{["paramName"] = someValue}, Lifetime.Transient);
      // or
      svcs.Service<IMyService, MyService>(new {paramName = someValue}, Lifetime.Transient);
      
  • Factory recipe: As the name suggests services registered by this way have a factory function:
    svcs.Factory<IMyService>
    (
      factoryExpr: injector => new MyService(injector, injector.Get<IService_1>(), injector.Get<IService_2>())
      {
        Dep3 = injector.Get<IService_3>()
      },
      Lifetime.Singleton
    );
    // or
    svcs.Factory<IMyService>
    (
      factory: injector => { ...complex logic... },
      Lifetime.Singleton
    );
    
    It can be useful e.g. if a service has more than one public constructor. In case of generic services the factory function is called with the concrete id:
    svcs.Factory(typeof(IMyGenericService<>), (injector, serviceInterface) => 
    {
      Assert.That(serviceInterface.IsGenericTypeDefinition, Is.False);
      Assert.That(serviceInterface.GetGenericTypeDefinition(), Is.EqualTo(typeof(IMyGenericService<>)));
      ...  
    });
    
  • Provider recipe: Providers are factory services having well-defined layout (see the IServiceProvider interface) and dependencies:
    using System;
    
    using ServiceStack.Data;
    using ServiceStack.OrmLite;
    
    namespace Services
    {
      using API;
    
      public class MySqlDbConnectionFactoryProvider : IServiceProvider
      {
          public IConfig Config { get; }
    
          public MySqlDbConnectionFactoryProvider(IConfig config) => Config = config ?? throw new ArgumentNullException(nameof(config));
    
          public object GetService(Type serviceType)
          {
              if (serviceType != typeof(IDbConnectionFactory))
                  throw new NotSupportedException();
    
              return new OrmLiteConnectionFactory(Config.ConnectionString, MySqlDialect.Provider)
              {
                  AutoDisposeConnection = true
              };
          }
      }
    }
    ...
    svcs.Provider<IDbConnectionFactory, MySqlDbConnectionFactoryProvider>(Lifetime.Singleton);
    
  • Instance recipe: Instances are "predefined values" that act as a service:
    svcs.Instance<IMyService>(service);
    
    Instances are NEVER disposed by the system, you have to do it manually.

Remarks:

  • You may also register more service with the same interface by naming them:
    svcs.Service<IMyService, MyServiceImeplementation_1>("svc1", Lifetime.Transient);
    svcs.Factory<IMyService>("svc2", i => ..., Lifetime.Singleton);
    ...
    
    Later you can request them individually:
    class MyOtherService: IMyOtherService
    {
      public MyOtherService([Options(Name = "svc2")]IMyService dep) {...}
      ...
    }  
    
    or in a batched form using the magic IEnumerable<> service:
    class MyOtherService: IMyOtherService
    {
      public MyOtherService(IEnumerable<IMyService> deps)
      {
        Assert.That(deps.Count(), Is.EqualTo(2));
        ...
      }
      ...
    }  
    
  • You should not register the injector itself it is done by the system automatically.

Decorating services

In practice, it's useful to separate common functionality (e.g. parameter validation) from the implementation. In this library this can be achieved by proxy pattern. In a brief example:

using Solti.Utils.DI.Interfaces;
using Solti.Utils.DI;
...
public interfce IMyModule
{
    // When using the Decorate() function, only the interface members can be annotated
    void DoSomethig([NotNull] string param);
}
...
svcs
  .Service<IMyModule, MyModule>(Lifetime.Scoped).Decorate<ParameterValidatorProxy>();

Where the ParameterValidatorProxy is an IInterfaceInterceptor implementation containing the parameter validation logic:

using Solti.Utils.DI.Interfaces; 
...
// Base class of all the validator attributes
public abstract class ParameterValidatorAttribute: Attribute
{
  public abstract void Validate(ParameterInfo param, object value);
}
// Sample validator
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class NotNullAttribute : ParameterValidatorAttribute
{
  public override void Validate(ParameterInfo param, object value)
  {
    if (value is null)
      throw new ArgumentNullException(param.Name);
  }
}
...
public class ParameterValidatorProxy : IInterfaceInterceptor
{
  public ParameterValidator(IDependency dependency)
  {
      ...
  }

  public object? Invoke(IInvocationContext context, CallNextDelegate<IInvocationContext, object?> callNext)
  {
    foreach(var descr in context.TargetMethod.GetParameters().Select(
      (p, i) => new 
      { 
        Parameter = p, 
        Value = context.Args[i], 
        Validators = p.GetCustomAttributes<ParameterValidatorAttribute>() 
      }))
    {
      foreach (var validator in descr.Validators) 
      {
        validator.Validate(descr.Parameter, descr.Value);
      }
    }

    return callNext(context);
  }
}

Remarks:

  • Proxy pattern can be applied in any number against a service.
  • Applying proxies is done on the service request.
  • Trying to decorate a non-producible (generic, instance) or non-interface service will throw.
  • Proxies may also have dependencies.
  • The underlying functionality is provided by the ProxyGen.NET library

Aspects

Decorating services can be done using attributes as well. In this case we declare an attribute (derived from the AspectAttribute) that instructs the system which interceptors should be used. Doing so we introduce AOP in our code:

...
// Define an aspect for the ParameterValidatorProxy (see above)
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = false)]
public sealed class ParameterValidatorAspect : AspectAttribute
{
  public ParameterValidatorAspect(): base(typeof(ParameterValidatorProxy)) { }
}
...
// Then annotate the desired interface ...
[ParameterValidatorAspect]
public interface IService
{
  void DoSomething([NotNull] object arg); 
}

// ... OR class (recommended)

[ParameterValidatorAspect]
public class Service: IService
{
  // Only methods implementing the above declared interface can be annoteted 
  void DoSomething([NotNull] object arg) {...} 
}

Notes:

  • There is no need for registering the ParameterValidatorProxy manually.
  • Applying aspects is done in the order you use them:
    // On service invocations the system first validates the user then the method parameters.
    [UserValidatorAspect, ParameterValidatorAspect]
    public interface MyService {...}
    

Creating a scope

using (IInjector injector = scopeFactory.CreateScope()) // creating a scope is a thread-safe operation
{
  ...
}

or

await using (IInjector injector = scopeFactory.CreateScope())
{
  ...
}

Remarks:

  • IInjector instances are NOT thread-safe so every session / worker must have its own scope.
  • To release resources held by the scope, at the end of the session you should dispose the IInjector instance.

Requesting services

IMyService svc = injector.Get<IMyService>();

or

IMyService svc = injector.Get<IMyService>("servicename");

Remarks:

  • Requesting an unregistered or an open generic service will throw by default. If your dependency is optional, use the injector.TryGet() method.
  • Requesting services as a constructor parameter is more convenient than using the injector.Get() method.
  • You may request the closed pair of an open generic service without registering it:
    using(IScopeFactory scopeFactory = ScopeFactory.Create(svcs => svcs.Service(typeof(IMyGenericService<>), ...)))
    {
      ...
      using(IInjector injector = scopeFactory.CreateScope())
      {  
        IMyGenericService<string> svc = injector.Get<IMyGenericService<string>>();
      }
    }
    
  • To access all services with the given interface, use the magic IEnumerable<> service:
    // Returns all the IMyService instances regardless their names
    IEnumerable<IMyService> svcs = injector.Get<IEnumerable<IMyService>>();
    // Service instantiation is done during enumeration
    

Inline dependencies

A service can request its owner IInjector as a regular dependency (via constructor parameter). Doing this makes it possible to get services anywhere in the implementation (by invoking the IInjector.Get() method). This is we call inline dependencies.

Notes:

  • Requesting the owner scope is an anti-pattern so try to avoid it
  • Disposing inline dependencies is done by the system as well.

Strict DI

Consider the following registration:

...
svcs
  .Service<IMyService, MyService>(Lifetime.Transient)
  .Service<IMyOtherService, MyOtherServiceDependsOnIMyService>(Lifetime.Singleton);
...
using (IInjector injector = scopeFactory.CreateScope())
{
  var svc = injector.Get<IMyOtherService>();
  ...
}

Leaving the using block the IMyService instance requested by MyOtherServiceDependsOnIMyService won't be released because the requester still alives (until the root scope is disposed). This situation is called captive dependency. To avoid it you have two options:

  • Declare your consuming service with a Lifetime less than or equal to the Lifetime of consumed service.
  • Enable Strict DI validation (disabled by default) which will throw in such situations:
    using(IScopeFactory scopeFactory = ScopeFactory.Create(svcs => ..., new ScopeOptions {StrictDI = true}))
    {
      ...
    }
    

MS preferred DI

Microsoft also defines its own interface for dependency resolution. This library has built in support for it:

using System;

using Solti.Utils.DI;
using Solti.utils.DI.Interfaces;

...
using(IScopeFactory scopeFactory = ScopeFactory.Create(svcs => ..., new ScopeOptions {SupportsServiceProvider = true}))
{
  ...
  using(scopeFactory.CreateScope(out IServiceProvider sp))
  {
  }
}

Differences compared to IInjector:

  • Every requested service is optional (so IServiceProvider.GetService() won't throw if a service cannot be found). This rule applies to services requested via constructor parameters, too.
  • You can request named services only by using the OptionsAttribute (IServiceProvider.GetService() has no name parameter).
  • Since the IServiceProvider interface is not an IDisposable descendant you should release the disposable returned by the CreateScope() to end the scope lifetime.

Diagnostics

You can also visualize the dependency graph belongs to a particular service using the Solti.Utils.DI.Diagnostics namespace and a Graphviz renderer:

using Solti.Utils.DI.Diagnostics;
using Solti.utils.DI.Interfaces;

...
using(IScopeFactory scopeFactory = ScopeFactory.Create(svcs => ...))
{
  // The returned graph descriptor is supposed to be compatible with any Graphviz rendrer
  string dotGraph = scopeFactory.GetDependencyGraph<IMyService>(newLine: "\n");
  ...
}

The rendered graph should look like this: Dependency Graph

Resources

You can browse the detailed API docs or the benchmark results.

Supported frameworks

This project currently targets .NET Standard 2.0 and 2.1.

Migrating from version 1.X

  • All the attributes have been moved to the Solti.Utils.DI namespace so you just have to remove the Solti.Utils.DI.Annotations usings.
  • Lazy recipe has completely been removed. To preserve this functionality you can implement your own deferring logic in a Factory function.
  • .NET Standard 1.6 support has been dropped. Since modern .NET projects should not target the v1.X branch you might not be affected by this.
  • Other breaking changes were done on API's you should not call in everyday use.

Migrating from version 2.X

  • You should add +1 using (Solti.Utils.DI.Interfaces) in files where you use the general interfaces.
  • There is NO need for referencing the Solti.Utils.DI.Interfaces assembly directly.

Migrating from version 3.X

  • Auto service registration has been removed, so you should register all the services manually
  • Configuration has been moved to runtimeconfig.json (and got a new layout). See this as a reference

Migrating from version 4.X

  • Custom converter support has been dropped so instances returned by factory functions must implement the service interface.
  • Renamed built in service IServiceGraph to IServicePath.

Migrating from version 5.X

  • The ServiceContainer class has been dropped. You can register services when creating the (newly introduced) scope factory:
    using IScopeFactory scopeFactory = ScopeFactory.Create(svcs => svcs
      .Service(...)
      .Factory(...)
      .Provider(...));
    
    or
    using IScopeFactory scopeFactory = ScopeFactory.Create(new ServiceCollection()
      .Service(...)
      .Factory(...)
      .Provider(...)); 
    
  • runtimeconfig configuration has been removed. Scopes can be tweaked via the ScopeOptions class:
    ScopeFactory.Create(svcs => ..., new ScopeOptions {...})
    
  • Container inheritance and abstract service recipe have been dropped so remove codes that depend on it.
  • As the name suggests, IScopeFactory is responsible for creating scopes:
    /*await*/ using IInjector scope = scopeFactory.CreateScope();
    
    Note that IInjector instance MUST be freed at the end of the session (scope factory doesn't maintain the lifetime of the created scopes)
  • Due to performance considerations, the concept of service references are gone
  • The layout of the InterfaceInterceptor<>.Invoke() method has been changed: Now it has only a single parameter (InvocationContext) that contains all the invocation related attributes.

Migrating from version 6.X

  • Factory and Provider recipe now takes Expression<> instead of Func<>. Compile time created expressions have their limitations (for e.g. they cannot contain if or any complex statements). To work this around:
    svcs.Factory<IMyService>(injector => 
    {
        IDependency dep = injector.Get<IDependency>();
        ...DO SOME ELABORATE STUFFS...
        return result;
    }, ...);
    
    needs to be refactored like:
    private static IMyService MyServiceFactory(IDependency dep)
    {
        ...DO SOME ELABORATE STUFFS...
        return result;
    }
    ...
    svcs.Factory<IMyService>(injector => MyServiceFactory(injector.Get<IDependency>()), ...);
    
  • IServiceCollection(Basic|Advanced)Extensions.WithProxy() has been renamed to Decorate()
  • Interceptors must implement the IInterfaceInterceptor interfacce (instead of descending from InterfaceInterceptor<> class directly)
  • AspectAttribute got a new layout. Now it returns the interceptor type via property.

Migrating from version 7.X

  • AspectAttribute got a new layout. Interceptors now shall be passed as constructor parameters. So refactor this
    [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = false)]
    public sealed class ParameterValidatorAspect : AspectAttribute
    {
        public override Type UnderlyingInterceptor { get; } = typeof(ParameterValidatorProxy);
    }
    
    to
    [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = false)]
    public sealed class ParameterValidatorAspect : AspectAttribute
    {
        public ParameterValidatorAspect(): base(typeof(ParameterValidatorProxy)) { }
    }
    

Migrating from version 8.X

  • IInterfaceInterceptor.Invoke() layout has been altered (next() now requires the context to be passed), so refactor this
    public class LoggerInterceptor : IInterfaceInterceptor
    {   
        public object Invoke(IInvocationContext context, Next<object> callNext)
        {
            Console.WriteLine(context.InterfaceMethod);
            return callNext();
        }
    }
    
    to
    public class LoggerInterceptor : IInterfaceInterceptor
    {
        public object Invoke(IInvocationContext context, Next<IInvocationContext, object> callNext)
        {
            Console.WriteLine(context.InterfaceMethod);
            return callNext(context);
        }
    }
    

Migrating from version 9.X

  • Factory recipe now accepts concrete FactoryDelegate alongside the Expression<FactoryDelegate>. In case the compiler can't decide which overload to utilize, use named arguments:
    svcs.Factory<IMyService>(factory: injector => 
    {
        IDependency dep = injector.Get<IDependency>();
        ...DO SOME ELABORATE STUFFS...
        return result;
    }, ...);
    
    or
    svcs.Factory<IMyService>(factoryExpr: injector => /*simple expression*/, ...);
    
  • name parameter has been changed to key in all named service related methods.
  • Next delegate has been renamed to CallNextDelegate

Version history

Can be found here

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Injector.NET:

Package Downloads
RPC.NET.Server

SDK designed for building lightweight RPC servers.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
10.1.0 258 12/28/2023
10.0.0 122 12/15/2023
9.0.0 148 6/1/2023
8.0.1 211 3/12/2023
8.0.0 220 2/25/2023
7.0.0 252 2/19/2023
7.0.0-preview5 112 1/22/2023
7.0.0-preview4 96 11/20/2022
7.0.0-preview3 142 7/24/2022
7.0.0-preview2 137 6/25/2022
7.0.0-preview1 124 4/23/2022
6.2.1 452 3/17/2022
6.2.0 411 3/15/2022
6.1.0 577 1/28/2022
6.0.0 891 12/12/2021
6.0.0-preview2 460 10/11/2021
6.0.0-preview1 226 10/9/2021
5.0.1 725 7/1/2021
5.0.0 354 6/20/2021
4.0.1 1,168 3/19/2021
4.0.0 377 3/13/2021
4.0.0-preview8 369 3/4/2021
4.0.0-preview7 313 3/2/2021
4.0.0-preview6 189 3/1/2021
4.0.0-preview5 336 2/20/2021
4.0.0-preview4 246 11/17/2020
4.0.0-preview3 314 10/7/2020
4.0.0-preview2 299 9/22/2020
4.0.0-preview1 283 8/28/2020
3.4.1 1,098 8/26/2020
3.4.0 562 8/18/2020
3.3.1 1,367 7/9/2020
3.3.0 482 6/25/2020
3.2.0 452 6/15/2020
3.1.0 484 6/3/2020
3.0.1 489 5/25/2020
3.0.0 492 5/25/2020
3.0.0-preview2 303 5/22/2020
3.0.0-preview1 318 5/22/2020
2.1.0 463 5/5/2020
2.0.1 442 4/28/2020
2.0.0 457 4/17/2020
2.0.0-preview4 318 4/14/2020
2.0.0-preview3 306 3/31/2020
2.0.0-preview2 344 3/27/2020
2.0.0-preview1 328 3/20/2020
1.3.2 470 3/6/2020
1.3.1 438 3/3/2020
1.3.0 449 2/27/2020
1.3.0-preview2 337 2/21/2020
1.3.0-preview1 318 2/14/2020
1.2.0 424 2/9/2020
1.2.0-preview1 328 2/7/2020
1.1.1 550 1/27/2020
1.1.0 517 1/27/2020
1.0.0 479 1/18/2020
1.0.0-preview7 348 1/15/2020
1.0.0-preview6 346 12/12/2019
1.0.0-preview5 320 11/27/2019
1.0.0-preview4 330 11/7/2019
1.0.0-preview3 330 10/20/2019
1.0.0-preview2 361 10/10/2019
1.0.0-preview1 352 10/9/2019
0.0.3 552 6/21/2019
0.0.2 494 6/15/2019
0.0.1 585 6/6/2019
0.0.1-a 427 6/6/2019