Dependency injection extensions

Warm up

When you use the DI pattern in your .Net application you could need a warm up after the build of your services. And with Rystem you can simply do it.

builder.Services.AddWarmUp(() => somethingToDo());

and after the build use the warm up

var app = builder.Build();
await app.Services.WarmUpAsync();

Population service

You can use the population service to create a list of random value of a specific Type. An example from unit test explains how to use the service.

IServiceCollection services = new ServiceCollection();
services.AddPopulationService();
var serviceProvider = services.BuildServiceProvider().CreateScope().ServiceProvider;
var populatedModel = serviceProvider.GetService<IPopulation<PopulationModelTest>>();
IPopulation<PopulationModelTest> allPrepopulation = populatedModel!
    .Setup()
    .WithPattern(x => x.J!.First().A, "[a-z]{4,5}")
        .WithPattern(x => x.Y!.First().Value.A, "[a-z]{4,5}")
        .WithImplementation(x => x.I, typeof(MyInnerInterfaceImplementation))
        .WithPattern(x => x.I!.A!, "[a-z]{4,5}")
        .WithPattern(x => x.II!.A!, "[a-z]{4,5}")
        .WithImplementation<IInnerInterface, MyInnerInterfaceImplementation>(x => x.I!);
var all = allPrepopulation.Populate();

Abstract factory

You can use this abstract factory solution when you need to setup more than one service of the same kind and you need to distinguish them by a name.

I have an interface

public interface IMyService
{
    string GetName();
}

Some options for every service

public class SingletonOption
{
    public string ServiceName { get; set; }
}
public class TransientOption
{
    public string ServiceName { get; set; }
}
public class ScopedOption
{
    public string ServiceName { get; set; }
}

with built options which is a IServiceOptions, a options class that ends up with another class. Used for example when you have to add a settings like a connection string but you want to use a service like a client that uses that connection string.

public class BuiltScopedOptions : IServiceOptions<ScopedOption>
{
    public string ServiceName { get; set; }

    public Task<Func<ScopedOption>> BuildAsync()
    {
        return Task.FromResult(() => new ScopedOption
        {
            ServiceName = ServiceName
        });
    }
}

And six different services

public class SingletonService : IMyService, IServiceWithOptions<SingletonOption>
{
    public SingletonOption Options { get; set; }
    public string Id { get; } = Guid.NewGuid().ToString();
    public string GetName()
    {
        return $"{Options.ServiceName} with id {Id}";
    }
}
public class TransientService : IMyService, IServiceWithOptions<TransientOption>
{
    public TransientOption Options { get; set; }
    public string Id { get; } = Guid.NewGuid().ToString();
    public string GetName()
    {
        return $"{Options.ServiceName} with id {Id}";
    }
}
public class ScopedService : IMyService, IServiceWithOptions<ScopedOption>
{
    public ScopedOption Options { get; set; }
    public string Id { get; } = Guid.NewGuid().ToString();
    public string GetName()
    {
        return $"{Options.ServiceName} with id {Id}";
    }
}
public class ScopedService2 : IMyService, IServiceWithOptions<ScopedOption>
{
    public ScopedOption Options { get; set; }
    public string Id { get; } = Guid.NewGuid().ToString();

    public string GetName()
    {
        return $"{Options.ServiceName} with id {Id}";
    }
}
public class ScopedService3 : IMyService, IServiceWithOptions<ScopedOption>
{
    public ScopedOption Options { get; set; }
    public string Id { get; } = Guid.NewGuid().ToString();

    public string GetName()
    {
        return $"{Options.ServiceName} with id {Id}";
    }
}
public class ScopedService4 : IMyService, IServiceWithOptions<ScopedOption>
{
    public ScopedOption Options { get; set; }
    public string Id { get; } = Guid.NewGuid().ToString();

    public string GetName()
    {
        return $"{Options.ServiceName} with id {Id}";
    }
}

I can setup them in this way

var services = new ServiceCollection();
services.AddFactory<IMyService, SingletonService, SingletonOption>(x =>
{
    x.ServiceName = "singleton";
},
"singleton",
ServiceLifetime.Singleton);

services.AddFactory<IMyService, TransientService, TransientOption>(x =>
{
    x.ServiceName = "transient";
},
"transient",
ServiceLifetime.Transient);

services.AddFactory<IMyService, ScopedService, ScopedOption>(x =>
{
    x.ServiceName = "scoped";
},
"scoped",
ServiceLifetime.Scoped);

services.AddFactory<IMyService, ScopedService2, ScopedOption>(x =>
{
    x.ServiceName = "scoped2";
},
"scoped2",
ServiceLifetime.Scoped);

await services.AddFactoryAsync<IMyService, ScopedService3, BuiltScopedOptions, ScopedOption>(
    x =>
    {
        x.ServiceName = "scoped3";
    },
    "scoped3"
);

await services.AddFactoryAsync<IMyService, ScopedService3, BuiltScopedOptions, ScopedOption>(
    x =>
    {
        x.ServiceName = "scoped3_2";
    },
    "scoped3_2"
);

await services.AddFactoryAsync<IMyService, ScopedService4, BuiltScopedOptions, ScopedOption>(
    x =>
    {
        x.ServiceName = "scoped4";
    },
    "scoped4"
);

and use them in this way

var serviceProvider = services.BuildServiceProvider().CreateScope().ServiceProvider;
var factory = serviceProvider.GetService<IFactory<IMyService>>()!;
var factory2 = serviceProvider.GetService<IFactory<IMyService>>()!;

var singletonFromFactory = factory.Create("singleton").Id;
var singletonFromFactory2 = factory2.Create("singleton").Id;
var transientFromFactory = factory.Create("transient").Id;
var transientFromFactory2 = factory2.Create("transient").Id;
var scopedFromFactory = factory.Create("scoped").Id;
var scopedFromFactory2 = factory2.Create("scoped").Id;
var scoped2FromFactory = factory.Create("scoped2").Id;
var scoped2FromFactory2 = factory2.Create("scoped2").Id;
var scoped3FromFactory = factory.Create("scoped3").Id;
var scoped3FromFactory2 = factory2.Create("scoped3").Id;
var scoped3_2FromFactory = factory.Create("scoped3_2").Id;
var scoped3_2FromFactory2 = factory2.Create("scoped3_2").Id;
var scoped4FromFactory = factory.Create("scoped4").Id;
var scoped4FromFactory2 = factory2.Create("scoped4").Id;

Assert.Equal(singletonFromFactory, singletonFromFactory2);
Assert.NotEqual(transientFromFactory, transientFromFactory2);
Assert.Equal(scopedFromFactory, scopedFromFactory2);
Assert.Equal(scoped2FromFactory, scoped2FromFactory2);
Assert.NotEqual(scoped3FromFactory, scoped3FromFactory2);
Assert.NotEqual(scoped3_2FromFactory, scoped3_2FromFactory2);
Assert.NotEqual(scoped4FromFactory, scoped4FromFactory2);

Decorator

You may add a decoration for your services, based on the abstract factory integration. The decorator service replaces the previous version and receives it during the injection.

Setup

services
    .AddService<ITestWithoutFactoryService, TestWithoutFactoryService>(lifetime);
services
    .AddDecoration<ITestWithoutFactoryService, TestWithoutFactoryServiceDecorator>(null, lifetime);

Usage

var decorator = provider.GetRequiredService<ITestWithoutFactoryService>();
var previousService = provider.GetRequiredService<IDecoratedService<ITestWithoutFactoryService>>();

In decorator you may find the previousService in the method SetDecoratedService which runs in injection

public class TestWithoutFactoryServiceDecorator : ITestWithoutFactoryService, IDecoratorService<ITestWithoutFactoryService>
{
    public string Id { get; } = Guid.NewGuid().ToString();
    public ITestWithoutFactoryService Test { get; private set; }
    public void SetDecoratedService(ITestWithoutFactoryService service)
    {
        Test = service;
    }

    public void SetFactoryName(string name)
    {
        return;
    }
}

Decorator with Abstract Factory integration

You may add a decoration only for one service of your factory integration.

Setup

services.AddFactory<ITestService, TestService, TestOptions>(x =>
{
    x.ClassicName = classicName;
},
factoryName,
lifetime);
services
    .AddDecoration<ITestService, DecoratorTestService>(factoryName, lifetime);

Usage

var decoratorFactory = provider.GetRequiredService<IFactory<ITestService>>();
var decorator = decoratorFactory.Create(factoryName);
var previousService = decoratorFactory.CreateWithoutDecoration(factoryName);

Factory Fallback

You may add a fallback for your factory integration. The fallback service is called when the factory service key is not found.

services.AddFactoryFallback<TService, TFactoryFallback>();

where TFactoryFallback is class and an IFactoryFallback

You may add a fallback with an action fallback too.

services.AddActionAsFallbackWithServiceProvider<TService>(Func<FallbackBuilderForServiceProvider, TService> fallbackBuilder);

Scan dependency injection

You may scan your assemblies in search of types you need to add to dependency injection. For instance I have an interface IAnything and I need to add all classes which implements it.

public interface IAnything
{
}
internal class ScanModels : IAnything
{
}

and in service collection I can add it.

serviceCollection
    .Scan<IAnything>(ServiceLifetime.Scoped, typeof(IAnything).Assembly);

I can add to my class the interface IScannable of T to scan automatically. For instance.

public interface IAnything
{
}
internal class ScanModels : IAnything, IScannable<IAnything>
{
}

and in service collection I could add it in this way

serviceCollection
    .Scan(ServiceLifetime.Scoped, typeof(IAnything).Assembly);

Furthermore with ISingletonScannable, IScopedScannable and ITransientScannable I can override the service lifetime. For instance.

public interface IAnything
{
}
internal class ScanModels : IAnything, IScannable<IAnything>, ISingletonScannable
{
}

serviceCollection
    .Scan(ServiceLifetime.Scoped, typeof(IAnything).Assembly);

ScanModels will be installed as a Singleton service, overwriting the service lifetime from Scan method.

You also automatically use different assembly sources.

serviceCollection
    .ScanDependencyContext(ServiceLifetime.Scoped);

or

serviceCollection
    .ScanCallingAssembly(ServiceLifetime.Scoped);

or

serviceCollection
    .ScanCurrentDomain(ServiceLifetime.Scoped);

or

serviceCollection
    .ScanEntryAssembly(ServiceLifetime.Scoped);

or

serviceCollection
    .ScanExecutingAssembly(ServiceLifetime.Scoped);

or

serviceCollection
    .ScanFromType<T>(ServiceLifetime.Scoped);

or

serviceCollection
    .ScanFromTypes<T1, T2>(ServiceLifetime.Scoped);

Finally with ScanWithReferences you may call all the assemblies you want plus all referenced assemblies by them.