Injector.NET
6.1.0
See the version list below for details.
dotnet add package Injector.NET --version 6.1.0
NuGet\Install-Package Injector.NET -Version 6.1.0
<PackageReference Include="Injector.NET" Version="6.1.0" />
paket add Injector.NET --version 6.1.0
#r "nuget: Injector.NET, 6.1.0"
// Install Injector.NET as a Cake Addin #addin nuget:?package=Injector.NET&version=6.1.0 // Install Injector.NET as a Cake Tool #tool nuget:?package=Injector.NET&version=6.1.0
Injector.NET
A featherweight dependency injector written in C#.
Package name | NuGet link |
---|---|
Injector.NET | |
Injector.NET.Interfaces |
This documentation refers the version 6.X 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 proxies)
- Duck typing (using proxies)
- 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 interfaces.
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
andInstance
services may be accessed parallelly they (and their dependencies) have to be thread safe.
Workflow of DI
- Creating a scope factory
- Registering services
- Decorating services (optional)
- Creating a scope
- Requesting service instances
- Destroying the injector
- 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 may 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:
You can register generic services as well:svcs.Service<IMyService, MyService>(Lifetime.Transient);
Remarks:svcs.Service(typeof(IMyGenericService<>), typeof(MyGenericService<>), Lifetime.Singleton);
- 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(Lazy<IService_1> dep1, Lazy<IService_2> dep2) {...} }
- You can mark the requested dependency as optional. In this case the system won't throw if the dependency can not be found:
public class MyService: IMyService { public MyService(IService_1 dep1, [Options(Optional = true)]IService_2 dep2) {...} }
-
- By default, services must not have non-interface dependency. To work around this limitation you can provide custom constructor arguments:
svcs.Service<IMyService, MyService>(new Dictionary<string, object?>{["paramName"] = someValue}, Lifetime.Transient);
- Implementations must not have more than one public constructor (or you must annotate the appropriate one with the
- Factory recipe: As the name suggests services registered by this way have a factory function:
It can be useful e.g. if the service has more than one public constructor. In case of generic services the factory function will be called with the specialized interface:svcs.Factory<IMyService> ( injector => new MyService(injector, injector.Get<IService_1>(), injector.Get<IService_2>()) { Dep3 = injector.Get<IService_3>() }, Lifetime.Singleton );
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 with 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 can act as a service:
Instances are NEVER disposed by the system, you have to do it manually.svcs.Instance<IMyService>(service);
Remarks:
- You may also register more service with the same interface by naming them:
Later you can request them individually:svcs.Service<IMyService, MyServiceImeplementation_1>("svc1", Lifetime.Transient); svcs.Factory<IMyService>("svc2", i => ..., Lifetime.Singleton); ...
or in a batched form with the magicclass MyOtherService: IMyOtherService { public MyOtherService([Options(Name = "svc2")]IMyService dep) {...} ... }
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;
using Solti.Utils.Proxy;
...
svcs
.Service<IMyModule, MyModule>(Lifetime.Scoped)
.Proxy<IMyModule, ParameterValidatorProxy<IMyModule>>();
Where the ParameterValidatorProxy<TInterface>
is an InterfaceInterceptor<TInterface>
descendant containing the parameter validation logic:
using Solti.Utils.Proxy;
...
// 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<TInterface> : InterfaceInterceptor<TInterface> where TInterface : class
{
public ParameterValidator(TInterface target) : base(target)
{
}
public override object Invoke(InvocationContext context)
{
foreach(var descr in context.Method.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 base.Invoke(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 service (generic, instance) will throw.
ProxyFactory
is an individual component, so you can use it anywhere in your code.- Proxies may also have dependencies (
ProxyFactory
supports this scenario). - The underlying functionality is provided by the ProxyGen.NET library
Aspects
Decorating services can be done by 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 the AOP in our code:
...
// Define an aspect for the ParameterValidatorProxy (see above)
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public sealed class ParameterValidatorAspect : AspectAttribute
{
public override Type GetInterceptor(Type iface) => typeof(ParameterValidatorProxy<>).MakeGenericType(iface);
}
...
// Then annotate the desired interface(s):
[ParameterValidatorAspect]
public interface IService
{
void DoSomething([NotNull] object arg);
}
Notes:
- As you can see 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 IMyService {...}
Naked aspects
An aspect is naked when it references its interceptor by name (and the interceptor is placed in a separate assembly). This practice ensures the separation of concerns (the project containing the service interface won't reference logic related assemblies implicitly).
...
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class NakedValidatorAspectAttribute: AspectAttribute
{
public override Type GetInterceptor(Type iface)
{
Type interceptor = Type.GetType("Solti.Utils.Aspects.ParameterValidator`1, Solti.Utils.Aspects, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", throwOnError: true);
return interceptor.MakeGenericType(iface);
}
}
Creating a scope
using (IInjector injector = scopeFactory.CreateScope())
{
...
}
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
.
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:// Return all the IMyService instances regardless their names IEnumerable<IMyService> svcs = injector.Get<IEnumerable<IMyService>>(); // Service instantiation is done during enumeration
Alternatively you can use the injector.Instantiate()
function that takes a Type
(non-abstract class) you want to instantiate, resolves its dependencies (via constructor parameters) and creates a new instance from it:
public class OuterService
{
// Parameter names must not be obfuscated.
public OuterService(IMyService dep, int num){...}
}
...
using(IScopeFactory scopeFactory = ScopeFactory.Create(svcs => svcs.Service<IMyService, MyService>(Lifetime.Scoped)))
{
using(IInjector injector = scopeFactory.CreateScope())
{
OuterService instance = injector.Instantiate<OuterService>(new Dictionary<string, object>
{
{"num", 10}
});
} // Using the instantiated object is safe in the using block only
}
As you can see there are three major differences compared to injector.Get()
:
- The instantiated
Type
does not have to be a registered service. - You can pass arbitrary arguments to the constructor via the
explicitArgs
parameter (which means the instantiated type may have non-registered/non-interface dependency). - The caller is responsible for freeing the returned object (that must be done before the scope disposal).
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:
- 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 theLifetime
of consumed service. - Enable Strict DI validation (disabled by default):
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()
does not throw if a service can not 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 anIDisposable
descendant you should release the disposable returned by theCreateScope()
to end the scope lifetime.
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 theSolti.Utils.DI.Annotations
usings. Lazy
recipe has completely been removed. To preserve this functionality you can implement your own deferring logic in aFactory
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
toIServicePath
.
Migrating from version 5.X
- The
ServiceContainer
class has been dropped. You can register services when creating the (newly introduced) scope factory:
orusing IScopeFactory scopeFactory = ScopeFactory.Create(svcs => svcs .Service(...) .Factory(...) .Provider(...));
using IScopeFactory scopeFactory = ScopeFactory.Create(new ServiceCollection() .Service(...) .Factory(...) .Provider(...));
runtimeconfig
configuration has been removed. Scopes can be tweaked via theScopeOptions
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:
Note that/*await*/ using IInjector scope = scopeFactory.CreateScope();
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.
Version history
Can be found here
Product | Versions 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. |
-
.NETStandard 2.0
- IndexRange (>= 1.0.0)
- Injector.NET.Interfaces (>= 6.1.0)
- Microsoft.CodeAnalysis.CSharp (>= 3.8.0)
- Microsoft.Experimental.Collections (>= 1.0.6-e190117-3)
- ProxyGen.NET (>= 5.0.1)
- Solti.Utils.Primitives (>= 6.1.2)
-
.NETStandard 2.1
- Injector.NET.Interfaces (>= 6.1.0)
- Microsoft.CodeAnalysis.CSharp (>= 3.8.0)
- Microsoft.Experimental.Collections (>= 1.0.6-e190117-3)
- ProxyGen.NET (>= 5.0.1)
- Solti.Utils.Primitives (>= 6.1.2)
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 | 378 | 12/28/2023 |
10.0.0 | 152 | 12/15/2023 |
9.0.0 | 181 | 6/1/2023 |
8.0.1 | 237 | 3/12/2023 |
8.0.0 | 248 | 2/25/2023 |
7.0.0 | 280 | 2/19/2023 |
7.0.0-preview5 | 140 | 1/22/2023 |
7.0.0-preview4 | 118 | 11/20/2022 |
7.0.0-preview3 | 171 | 7/24/2022 |
7.0.0-preview2 | 163 | 6/25/2022 |
7.0.0-preview1 | 148 | 4/23/2022 |
6.2.1 | 480 | 3/17/2022 |
6.2.0 | 437 | 3/15/2022 |
6.1.0 | 623 | 1/28/2022 |
6.0.0 | 956 | 12/12/2021 |
6.0.0-preview2 | 521 | 10/11/2021 |
6.0.0-preview1 | 253 | 10/9/2021 |
5.0.1 | 772 | 7/1/2021 |
5.0.0 | 384 | 6/20/2021 |
4.0.1 | 1,252 | 3/19/2021 |
4.0.0 | 399 | 3/13/2021 |
4.0.0-preview8 | 426 | 3/4/2021 |
4.0.0-preview7 | 326 | 3/2/2021 |
4.0.0-preview6 | 223 | 3/1/2021 |
4.0.0-preview5 | 378 | 2/20/2021 |
4.0.0-preview4 | 271 | 11/17/2020 |
4.0.0-preview3 | 333 | 10/7/2020 |
4.0.0-preview2 | 326 | 9/22/2020 |
4.0.0-preview1 | 308 | 8/28/2020 |
3.4.1 | 1,167 | 8/26/2020 |
3.4.0 | 605 | 8/18/2020 |
3.3.1 | 1,476 | 7/9/2020 |
3.3.0 | 507 | 6/25/2020 |
3.2.0 | 481 | 6/15/2020 |
3.1.0 | 511 | 6/3/2020 |
3.0.1 | 522 | 5/25/2020 |
3.0.0 | 517 | 5/25/2020 |
3.0.0-preview2 | 333 | 5/22/2020 |
3.0.0-preview1 | 349 | 5/22/2020 |
2.1.0 | 489 | 5/5/2020 |
2.0.1 | 472 | 4/28/2020 |
2.0.0 | 483 | 4/17/2020 |
2.0.0-preview4 | 342 | 4/14/2020 |
2.0.0-preview3 | 329 | 3/31/2020 |
2.0.0-preview2 | 368 | 3/27/2020 |
2.0.0-preview1 | 352 | 3/20/2020 |
1.3.2 | 495 | 3/6/2020 |
1.3.1 | 466 | 3/3/2020 |
1.3.0 | 483 | 2/27/2020 |
1.3.0-preview2 | 345 | 2/21/2020 |
1.3.0-preview1 | 344 | 2/14/2020 |
1.2.0 | 449 | 2/9/2020 |
1.2.0-preview1 | 357 | 2/7/2020 |
1.1.1 | 573 | 1/27/2020 |
1.1.0 | 541 | 1/27/2020 |
1.0.0 | 499 | 1/18/2020 |
1.0.0-preview7 | 368 | 1/15/2020 |
1.0.0-preview6 | 366 | 12/12/2019 |
1.0.0-preview5 | 346 | 11/27/2019 |
1.0.0-preview4 | 355 | 11/7/2019 |
1.0.0-preview3 | 350 | 10/20/2019 |
1.0.0-preview2 | 383 | 10/10/2019 |
1.0.0-preview1 | 374 | 10/9/2019 |
0.0.3 | 579 | 6/21/2019 |
0.0.2 | 523 | 6/15/2019 |
0.0.1 | 610 | 6/6/2019 |
0.0.1-a | 452 | 6/6/2019 |