Forked from https://github.com/FrenziedMarmot/DependencyInjection and modified for Project Atlas.
By default, using dependency injection in C# requires that every time you have a new class to inject, you end up having to create it AND register it in the dependency injection system - usually in Startup.cs
. Using this library, you modify your Startup
once and then each time you create a class you decorate it with the [Injectable]
attribute. This way, you define how something is injected with the class you're injecting. As long as the assembly that class is contained in is scanned, then it gets picked up and adding a class is one less step.
Install via nuget package manager or use dotnet.
dotnet add package AltV.Atlas.IoC
The library works by specifying injection via attributes AND finding those mappings by scanning for them.
Using the constructor on the InjectableAttribute
you will be able to specify:
- What class/interface is being injected
- The service lifetime the injection lives for
and either:
- The implementation being injected
- The type acting as a factory for the injection
In addition, you can also enable injection from your appsettings.json
by specifying the class to hold the settings and instead using the InjectableOptionsAttribute
and specifying:
- The json path, using
:
to indicate nesting. If not specified, will use the class name. - The type to deserialize to. If not specified, uses the decorated class.
There are 2 extension methods which provide the functionality for this library:
services.ScanForAttributeInjection(GetType().Assembly)
.ScanForOptionAttributeInjection(Configuration, GetType().Assembly);
In Startup.cs when you're configuring dependency injection, utilize the extension method ScanForAttributeInjection
and supply a list of assemblies or types for it to scan. The method utilizes the params
keyword so multiple types/assemblies can be provided.
services.ScanForAttributeInjection(GetType().Assembly);
Optionally, there's a method that will take an IAssemblyScanner
and a class is provided that will scan an entire appdomain. However, note that it will require marking up the assemblies that you want it to actually scan with the InjectableAssembly
attribute. A good location to put this is usually AssemblyInfo.cs
which is common, however, anywhere in the code for that assembly will work. This flag can optionally be turned off.
services.ScanForAttributeInjection(new AppDomainAssemblyScanner(AppDomain.CurrentDomain));
For scanning for IOptions<T>
to provide a type from appsettings, you need to additionally call ScanForOptionAttributeInjection
and provide an IConfiguration
object. The same overload exists for an IAssemblyScanner
.
services.ScanForOptionAttributeInjection(Configuration, GetType().Assembly);
The [Injectable]
attribute is for injecting services and allows for a fairly flexible method of specifying your injections declaratively.
Specifying a concrete implementation as an interface, the following is the equivalent of coding services.AddScoped<IGreetingService, GreetingService>()
public interface IGreetingService
{
public string Greet();
}
[Injectable(typeof(IGreetingService), typeof(GreetingService), ServiceLifetime.Scoped)]
public class GreetingService : IGreetingService
{
public string Greet()
{
return $"{GetType().Name} was injected!";
}
}
If any parameter is left off, it uses the class it's attached to as an argument and ServiceLifetime.Scoped
is the default service lifetime.
For example, the attribute usage above for GreetingService
could be simplified to:
[Injectable(typeof(IGreetingService))]
public class GreetingService : IGreetingService
For example, the following is the same as services.AddScoped<MyClass>()
:
[Injectable]
public class MyClass
Property initializers can be used to set only a specific property. For example, the following is the same as services.AddSingleton<MyClass>()
:
[Injectable(Lifetime = ServiceLifetime.Singleton)]
public class MyClass
The Factory
property can be used to specify a factory class that will be called for the implementation. The factory class provided must implement IInjectableFactory
. For example, a factory attached to an injectable would look like:
[Injectable(Factory = typeof(MyClassFactory))]
public class MyClass
{
public MyClass(string someString)
{
//...
}
}
public class MyClassFactory : IInjectableFactory
{
public object Create(IServiceProvider serviceProvider)
{
return new MyClass("I was injected via Factory!");
}
}
Type-safety through the factory is also provided in 1.0.2
via IInjectableFactory<T>
. This also implements IInjectableFactory
so to reduce boilerplace AbstractInjectableFactor<TTarget>
and AbstractInjectableFactory<TTarget, TImpl>
are also provided and encouraged.
public class MyClassFactory : AbstractInjectableFactory<MyClass>
{
public MyClass Create(IServiceProvider serviceProvider)
{
return new MyClass("I was injected via Factory!");
}
}
public class AnotherFactory : AbstractInjectableFactory<IAnother, AnotherClass>
{
public AnotherClass Create(IServiceProvider serviceProvider)
{
return new AnotherClass("I was injected via Factory!");
}
}
Note: If you specify the same factory type twice, it will NOT inject the same instance to both. It will create 2 instances.
The [InjectableOptions]
attribute is for IOption<T>
objects useful for injecting from your appsettings.json
file.
For example, the class you would provide for
[InjectableOptions("My:Injected:Options")]
public class InjectedOptions
{
public string SomeValue { get; set; }
//...
}
Will deserialize:
"My": {
"Injected": {
"Options": {
"SomeValue": "The value"
//...
}
}
}
And you can inject it into your class:
public IndexModel(IOptions<InjectedOptions> opts)
{
DoSomething(opts.Value.SomeValue);
}
You can optionally pass a second argument if the decorated type isn't the one you intend to inject. This is useful if, for example, the type you're intending to inject is in a different assembly:
[InjectableOptions(Implementation = typeof(SomeApiOptions))]
[InjectableOptions("OtherApi", typeof(SomeOtherApiOptions))]
public class Startup