FrenziedMarmot.DependencyInjection 1.0.2-rc

This is a prerelease version of FrenziedMarmot.DependencyInjection.
There is a newer version of this package available.
See the version list below for details.
dotnet add package FrenziedMarmot.DependencyInjection --version 1.0.2-rc
NuGet\Install-Package FrenziedMarmot.DependencyInjection -Version 1.0.2-rc
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="FrenziedMarmot.DependencyInjection" Version="1.0.2-rc" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add FrenziedMarmot.DependencyInjection --version 1.0.2-rc
#r "nuget: FrenziedMarmot.DependencyInjection, 1.0.2-rc"
#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 FrenziedMarmot.DependencyInjection as a Cake Addin
#addin nuget:?package=FrenziedMarmot.DependencyInjection&version=1.0.2-rc&prerelease

// Install FrenziedMarmot.DependencyInjection as a Cake Tool
#tool nuget:?package=FrenziedMarmot.DependencyInjection&version=1.0.2-rc&prerelease

FrenziedMarmot.DependencyInjection

The library, FrenziedMarmot.DependencyInjection provides the ability to map dependency injection via attributes.

Github: GitHub license GitHub issues GitHub stars .NET

Sonar: Coverage Maintainability Rating Quality Gate Status

Nuget: NuGet Status

Purpose

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.

Installation

Install via nuget package manager or use dotnet.

dotnet add package FrenzedMarmot.DependencyInjection

Description

The library works by specifying injection via attributes AND finding those mappings by scanning for them.

Step 1: Specifying Injection

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

⚠️ If you provide both the implementation and a factory, the Factory will take precedence.

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.

Step 2: Scanning for Injection Specified by Attributes

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);

⚠️ These injection scanning options are independent. They do not rely on each other. You can use either or both. The important thing is to ensure that if you're using either of the attributes that you also scan for them or the attribute will be useless.


Example Attribute Usage

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.


Example Option Attribute Usage

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

If you want to fuel our continued work:

Buy Me A Coffee

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 is compatible.  net462 was computed.  net463 was computed.  net47 is compatible.  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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.2 1,900 6/24/2022
1.0.2-rc 596 5/10/2022
1.0.1 746 8/10/2021
1.0.0 754 7/6/2021

v1.0.2
- Add `SortOrder` to `InjectableAttribute`
- Added generic typing to `IInjectableFactory` with error checking
- Added attribute `InjectableAssemblyAttribute` to facilitate assembly filtering. For example, add `[assembly: InjectableAssembly]` to `AssemblyInfo.cs` (or anywhere really) and filter your assemblies by it.
- Added overload to scan an entire AppDomain. ~!NOTE!~: Filters by `InjectableAssemblyAttribute` by default with an optional switch to turn off auto-filtering.

v1.0.1
- Add ability to map dependency injection for `IOptions<T>` via `[InjectableOptions]`
- Added scanner for `[InjectableOptions]`

v1.0.0 - Initial Release
- Map dependency injection via attributes
- Define factories