IoC via DI - Inversion of Control via Dependency Injection with H.Necessaire

H.Necessaire's mechanism for Inversion of Control via Dependency Injection

IoC via DI - Inversion of Control via Dependency Injection with H.Necessaire
Photo by Shubham Dhage / Unsplash
H.Necessaire.Usage.Samples/Src/H.Necessaire.Samples/H.Necessaire.Samples.IoC/Program.cs at master · hinteadan/H.Necessaire.Usage.Samples
Usage Samples for H.Necessaire Docs WebSite. Contribute to hinteadan/H.Necessaire.Usage.Samples development by creating an account on GitHub.

NuGets

dotnet add package H.Necessaire

H.Necessaire

Basics

public interface ImADataGenerator
{
    Task<int[]> GenerateIntegers();
}

Define an operation contract (aka interface)

internal class StaticDataGenerator : ImADataGenerator
{
    public Task<int[]> GenerateIntegers()
    {
        return 0.AsArray().AsTask();
    }
}

Write a concrete implementation for the contract

ImADependencyRegistry depsRegistry = H.Necessaire.IoC.NewDependencyRegistry();

depsRegistry.Register<ImADataGenerator>(() => new BLL.Concrete.StaticDataGenerator());

Register the concrete implementation

ImADependencyProvider depsProvider = depsRegistry;

ImADataGenerator dataGenerator = depsProvider.Get<ImADataGenerator>();

int[] integers = await dataGenerator.GenerateIntegers();

Inject and use the dependency

Grouping

internal class DependencyGroup : ImADependencyGroup
{
    public void RegisterDependencies(ImADependencyRegistry dependencyRegistry)
    {
        dependencyRegistry.Register<ImADataGenerator>(() => new Concrete.StaticDataGenerator());
    }
}

Define a Dependency Group by implementing ImADependencyGroup

ImADependencyRegistry depsRegistry = H.Necessaire.IoC.NewDependencyRegistry();

depsRegistry.Register<BLL.DependencyGroup>(() => new BLL.DependencyGroup());

Register the group with the container

Multiple Implementations

[ID("random")]
[Alias("rnd")]
internal class RandomDataGenerator : ImADataGenerator
{
    public Task<int[]> GenerateIntegers()
    {
        return
            Enumerable
            .Range(0, Random.Shared.Next(1, 10))
            .Select(i => Random.Shared.Next())
            .ToArray()
            .AsTask()
            ;
    }
}

[ID("static")]
[Alias("st")]
internal class StaticDataGenerator : ImADataGenerator
{
    public Task<int[]> GenerateIntegers()
    {
        return 0.AsArray().AsTask();
    }
}

Declare multiple concrete implementations

ImADataGenerator staticDataGenerator = depsProvider.Build<ImADataGenerator>("static");
ImADataGenerator randomDataGenerator = depsProvider.Build<ImADataGenerator>("random");

Console.WriteLine(string.Join(", ", await staticDataGenerator.GenerateIntegers()));
Console.WriteLine(string.Join(", ", await randomDataGenerator.GenerateIntegers()));

Build/Resolve the desired implementation

Note that registering multiple implementations of the same type will overwrite existing registrations; they will not resolve to an enumeration of the given type.

Dependency Injection

Injecting dependencies into a class is done explicitly by implementing ImADependency.

internal class ComposedDataGenerator : ImADataGenerator, ImADependency

Implement ImADependency to inject dependencies into a class

[ID("composed")]
internal class ComposedDataGenerator : ImADataGenerator, ImADependency
{
    ImADataGenerator staticDataGenerator;
    ImADataGenerator randomDataGenerator;
    public void ReferDependencies(ImADependencyProvider dependencyProvider)
    {
        staticDataGenerator = dependencyProvider.Build<ImADataGenerator>("static");
        randomDataGenerator = dependencyProvider.Build<ImADataGenerator>("random");
    }

    public async Task<int[]> GenerateIntegers()
    {
        int[][] dataParts = await Task.WhenAll(
            staticDataGenerator.GenerateIntegers(),
            randomDataGenerator.GenerateIntegers()
        );

        return dataParts.SelectMany(part => part).ToArray();
    }
}

Full example of a class with injected dependencies


All in all, the DI mechanism in H.Necessaire is kept very simple, as described above.

Any registration (via .Register<>()) is singleton within the given container.

Transient registrations can also be defined via .RegisterAlwaysNew<>().

There is no scoping concept, but it can be easily achieved by creating a new container wherever needed.

The core dependencies of H.Necessaire are all bundled in HNecessaireDependencyGroup.

Therefore you can simply add them to your container like so:

ImADependencyRegistry depsRegistry
    = H.Necessaire.IoC
    .NewDependencyRegistry()
    .Register<HNecessaireDependencyGroup>(() => new HNecessaireDependencyGroup())
    ;

Adding the core dependencies of H.Necessaire to your container

But note that this is not required when you're using the AppWireup mechanisms or Runtime mechanism, since it's already done by them.