All Projects → sungam3r → SteroidsDI

sungam3r / SteroidsDI

Licence: MIT license
Advanced Dependency Injection to use every day.

Programming Languages

C#
18002 projects

Projects that are alternatives of or similar to SteroidsDI

Factory
Factory for object creation and dependency injection. Works with normal C# apps or under Unity3d
Stars: ✭ 50 (+233.33%)
Mutual labels:  factory, dependency-injection
movie-booking
An example for booking movie seat, combined of Android Data Binding, State Design Pattern and Multibinding + Autofactory. iOS version is: https://github.com/lizhiquan/MovieBooking
Stars: ✭ 80 (+433.33%)
Mutual labels:  factory, dependency-injection
Python Dependency Injector
Dependency injection framework for Python
Stars: ✭ 1,203 (+7920%)
Mutual labels:  factory, dependency-injection
Stampit
OOP is better with stamps: Composable object factories.
Stars: ✭ 3,021 (+20040%)
Mutual labels:  factory, dependency-injection
Container Ioc
Inversion of Control container & Dependency Injection for Javascript and Node.js apps powered by Typescript.
Stars: ✭ 89 (+493.33%)
Mutual labels:  factory, dependency-injection
microcore
.NET Core framework for inter-service communication
Stars: ✭ 24 (+60%)
Mutual labels:  dependency-injection
zf-dependency-injection
Advanced dependency injection for laminas framework
Stars: ✭ 17 (+13.33%)
Mutual labels:  dependency-injection
angular-youtube-api-factory
AngularJS Factory for Youtube JSON REST API requests
Stars: ✭ 21 (+40%)
Mutual labels:  factory
ioc-ts
Inversion of control container on TypeScript
Stars: ✭ 14 (-6.67%)
Mutual labels:  dependency-injection
design-patterns-cpp14
🙏 Design patterns implemented in C++14
Stars: ✭ 35 (+133.33%)
Mutual labels:  factory
factoria
Simplistic model factory for Node/JavaScript
Stars: ✭ 49 (+226.67%)
Mutual labels:  factory
kaop
Advanced OOP Library with createClass, inheritance, providers, injectors, advices which enables handy Inversion of Control techniques
Stars: ✭ 40 (+166.67%)
Mutual labels:  dependency-injection
AnnotationInject
Compile-time Swift dependency injection annotations
Stars: ✭ 40 (+166.67%)
Mutual labels:  dependency-injection
Asp.net MVC5 DDD EF6 IoC
Asp.net C# MVC5, EF6, DDD, IoC
Stars: ✭ 14 (-6.67%)
Mutual labels:  dependency-injection
NoMansWallpaperApp
Looking for your next No Man's Sky wallpaper?
Stars: ✭ 35 (+133.33%)
Mutual labels:  dependency-injection
ComponentsManager
A service locator that makes work with Dagger2 in multi-module project easier. Allows binding components life to Activity/Fragment life-cycle.
Stars: ✭ 70 (+366.67%)
Mutual labels:  dependency-injection
swift-di-explorations
Functional DI explorations in Swift
Stars: ✭ 28 (+86.67%)
Mutual labels:  dependency-injection
dargo
Dependency Injection for GO
Stars: ✭ 26 (+73.33%)
Mutual labels:  dependency-injection
Tangle
Android injection using the Anvil compiler plugin
Stars: ✭ 65 (+333.33%)
Mutual labels:  dependency-injection
Components.js
🧩 A semantic dependency injection framework
Stars: ✭ 34 (+126.67%)
Mutual labels:  dependency-injection

SteroidsDI

Buy Me A Coffee

License

Activity Activity Activity

Size

Run unit tests Publish preview to GitHub registry Publish release to Nuget registry CodeQL analysis

codecov Total alerts Language grade: C#

Advanced Dependency Injection to use every day.

Installation

This repository provides the following packages:

Package Downloads Nuget Latest Description
SteroidsDI.Core Nuget Nuget Dependency Injection primitives
SteroidsDI Nuget Nuget Advanced Dependency Injection for Microsoft.Extensions.DependencyInjection: AddDefer, AddFunc, AddFactory; depends on SteroidsDI.Core
SteroidsDI.AspNetCore Nuget Nuget Scope Provider for ASP.NET Core; depends on SteroidsDI.Core

You can install the latest stable version via NuGet:

> dotnet add package SteroidsDI
> dotnet add package SteroidsDI.Core
> dotnet add package SteroidsDI.AspNetCore

What is it ? Why do I need it ?

.NET Core has built-in support for Dependency Injection. It works and works quite well. We can use the dependencies of the three main lifetimes: singleton, scoped, transient. There are rules that specify possible combinations of passing objects with one lifetime in objects with another lifetime. For example, you may encounter such an error message:

Error while validating the service descriptor 'ServiceType: I_XXX Lifetime: Singleton ImplementationType: XXX': Cannot consume scoped service 'YYY' from singleton 'I_XXX'.

The error says that you cannot pass an object with a shorter lifetime to the constructor of a long-living object. Well that's right! The problem is clear. But how to solve it? Obviously, when using constructor dependency injection (.NET Core has built-in support only for constructor DI), we must follow these rules. So we have at least 3 options:

  1. Lengthen lifetime for injected object.
  2. Shorten lifetime for an object that injects a dependency.
  3. Remove such a dependency.
  4. Change the design of dependencies so as to satisfy the rules.

The first method is far from always possible. The second method is much easier to implement, although this will lead to a decrease in performance due to the repeated creation of objects that were previously created once. The third way... well, you understand, life will not become easier. So it remains to somehow change the design. This project just offers such a way to solve the problem, introducing a number of auxiliary abstractions. As many already know

Any programming problem can be solved by introducing an additional level of abstraction with the exception of the problem of an excessive number of abstractions.

The project provides three such abstractions:

  1. Well known Func<T> delegate.
  2. Defer<T>/IDefer<T> abstractions which look like Lazy<T> but have a significant difference - Defer<T>/IDefer<T> do not cache the value.
  3. A named factory interface, when implementation type is generated at runtime.

All these abstractions solve the same problem, approaching the design of their API from different angles. The challenge is to provide a dependency T through some intermediary object X where an explicit dependency on T is either not possible or not desirable. Important! No implementation in this package caches dependency T.

As mentioned above an example of impossibility is a dependency on a scoped lifetime in an object with a singleton lifetime. And an example of non-desirability is creating dependency is expensive and not always required.

There is one important point to make - injecting dependency is not the same as using dependency. In fact, in the case of constructor injection, the injection of the dependency in the constructor leads (in most cases) to storing a reference to the passed value in the some field. The dependency will be used later when calling the methods of "parent" object.

Func<T>

This method is the easiest and offers to inject Func<T> instead of T:

Before:

class MyObject
{
    private IRepository _repo;

    public MyObject(IRepository repo) { _repo = repo; }
    
    public void DoSomething() { _repo.DoMagic(); }
}

After:

class MyObject
{
    private Func<IRepository> _repo;

    public MyObject(Func<IRepository> repo) { _repo = repo; }
    
    public void DoSomething() { _repo().DoMagic(); }
}

How to configure in DI:

public void ConfigureServices(IServiceCollection services)
{
    // First register your IRepository and then call
    services.AddFunc<IRepository>();
}

Note that you should call AddFunc for each dependency T which you want to inject as Func<T>.

IDefer<T> and Defer<T>

This method suggests more explicit API - inject IDefer<T> or Defer<T> instead of T:

Before:

class MyObject
{
    private IRepository _repo;

    public MyObject(IRepository repo) { _repo = repo; }
    
    public void DoSomething() { _repo.DoMagic(); }
}

After:

class MyObject
{
    private Defer<IRepository> _repo;

    public MyObject(Defer<IRepository> repo) { _repo = repo; }
    
    public void DoSomething() { _repo.Value.DoMagic(); }
}

How to configure in DI:

public void ConfigureServices(IServiceCollection services)
{
    // First register your IRepository and then call
    services.AddDefer();
}

Note that unlike AddFunc<T>, the AddDefer method needs to be called only once. Use IDefer<T> interface if you need covariance.

Named factory

This method is the most difficult to implement, but from the public API point of view it is just as simple as previous two. It assumes that you declare a factory interface with one or more methods without parameters. Method name does not matter. Each factory method should return some dependency type configured in DI container:

public interface IRepositoryFactory
{
    IRepository GetPersonsRepo();
}

And inject this factory into your "parent" type:

class MyObject
{
    private IRepositoryFactory _factory;

    public MyObject(IRepositoryFactory factory) { _factory = factory; }
    
    public void DoSomething() { _factory.GetPersonsRepo().DoMagic(); }
}

How to configure in DI:

public void ConfigureServices(IServiceCollection services)
{
    // First register your IRepository and then call
    services.AddFactory<IRepositoryFactory>();
}

Implementation for IRepositoryFactory will be generated at runtime.

In fact, each factory method can take one parameter of an arbitrary type - string, enum, custom class, whatever. In this case, a named binding should be specified.

public interface IRepositoryFactory
{
    IRepository GetPersonsRepo(string mode);
}

public interface IRepository
{
    void Save(Person person); 
}

public class DemoRepository : IRepository
{
...
}

public class ProductionRepository : IRepository
{
...
}

public class RandomRepository : IRepository
{
...
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IRepository, DemoRepository>()
            .AddTransient<IRepository, ProductionRepository>()
            .AddTransient<IRepository, RandomRepository>()
            .AddFactory<IRepositoryFactory>()
                .For<IRepository>()
                    .Named<DemoRepository>("demo")
                    .Named<ProductionRepository>("prod")
                    .Named<RandomRepository>("rnd");
}

public class Person
{
    public string Name { get; set; }
}

public class SomeClassWithDependency
{
    private readonly IRepositoryFactory _factory;

    public SomeClassWithDependency(IRepositoryFactory factory)
    {
        _factory = factory;
    }

    public void DoSomething(Person person)
    {
        if (person.Name == "demoUser")
            _factory.GetPersonsRepo("demo").Save(person);
        else if (person.Name.StartsWith("tester"))
            _factory.GetPersonsRepo("rnd").Save(person);
        else
            _factory.GetPersonsRepo("prod").Save(person);
    }
}

In the example above, the GetPersonsRepo method will return the corresponding implementation of the IRepository interface, configured for the provided name.

How it works?

Everything is simple here. All three methods come down to delegating dependency resolution to the appropriate IServiceProvider. What does appropriate mean? As a rule, in a ASP.NET Core application, everyone is used to working with one (scoped) provider obtained from IHttpContextAccessor - HttpContext.RequestServices. But in the general case, there can be many such providers. In addition, dependency-consuming code is not aware of their existence. This code may be a general purpose library no tightly coupled with application specific environment. Therefore abstraction for obtaining the appropriate IServiceProvider is introduced. Yes, one more abstraction again!

This project provides two built-in providers:

  1. AspNetCoreHttpScopeProvider for ASP.NET Core apps.
  2. GenericScopeProvider<T> for general purpose libraries.

How to configure in DI:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpScope();
    services.AddGenericScope<SomeClass>();
}

And of course you can always write your own provider:

public class MyScopeProvider : IScopeProvider
{
    public IServiceProvider? GetScopedServiceProvider(IServiceProvider rootProvider) => rootProvider.ReturnSomeMagic();
}

And provide its registration in DI:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyScope(this IServiceCollection services)
    {
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IScopeProvider, MyScopeProvider>());
        return services;
    }
}

Advanced behavior

You can customize the behavior of AddFunc/AddDefer/AddFactory APIs via ServiceProviderAdvancedOptions: Just use standard extension methods from Microsoft.Extensions.Options/Microsoft.Extensions.Options.ConfigurationExtensions packages.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<ServiceProviderAdvancedOptions>(options => options.AllowRootProviderResolve = true)
    services.Configure<ServiceProviderAdvancedOptions>(Configuration.GetSection("Steroids"));
}

Examples

You can see how to use all the aforementioned APIs in the example project.

FAQ

Q. Wait a moment. Doesn't Microsoft.Extensions.DependencyInjection have support for this out of the box?

A. Unfortunately no. I myself would rather be able to use the existing feature than to write my own package.


Q. Isn't what you offer is a ServiceLocator? I heard that the ServiceLocator is anti-pattern.

A. Yes, ServiceLocator is a known antipattern. The fundamental difference with the proposed solutions is that ServiceLocator allows you to resolve any dependency in runtime while Func<T>, Defer<T> and Named Factory are designed to resolve only known dependencies specified at the compile-time. Thus, the principal difference is that all the dependences of the class are declared explicitly and are injected into it. The class itself does not pull these dependencies secretly within its implementation. This is so called Explicit Dependencies Principle.


Q. Is this some kind of new dependency injection approach?

A. Actually not. A description of this approach can be found in articles/blogs many years ago, for example here.


Q. What should I prefer - Func<> or [I]Defer<>?

A. The main thing is they all work equally under the hood. The difference is in which context you are going to use these APIs.

There are two main differences:

  1. The advantage of Func<> is that the code in which you inject Func<> does not require any new dependency, it is well-known .NET delegate type. On the contrary [I]Defer<> requires a reference to SteroidsDI.Core package.

  2. You should call AddFunc for each dependency T which you want to inject as Func<T>. On the contrary the AddDefer method needs to be called only once.


Q. What if I want to create my own scope to work with, i.e. not only consume it but also provide?

A. First you should somehow get an instance of root IServiceProvider. Then create scope by calling CreateScope() method on it and set it into GenericScope:

var rootProvider = ...;
using var scope = rootProvider.CreateScope();
GenericScope<SomeClass>.CurrentScope = scope;
...
... Some code here that works with scopes.
... All registered Func<T>, [I]Defer<T> and
... factories use created scope.
...
GenericScope<T>.CurrentScope = null;

Or you can use a bit simpler approach with Scoped<T> struct.

IScopeFactory scopeFactory = ...; // can be obtained from DI, see AddMicrosoftScopeFactory extension method
using (new Scoped<SomeClass>(scopeFactory))
{
...
... Some code here that works with scopes.
... All registered Func<T>, [I]Defer<T> and
... factories use created scope.
...
} 

Also see ScopedTestBase and ScopedTestDerived for more info. This example shows how you can add scope support to all unit tests.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].