Leveraging built-in Dependency injection in .NET Core

Summary

.NET Core comes with a built-in dependency injection in the form of IServiceCollection which serves as the registry and IServiceProvider which serves as the service locator. You typically register a service by adding it to the IServiceCollection. A very minimal example could look like this:

NOTE: Using the Nuget package Microsoft.Extensions.DependencyInjection

using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public static IServiceProvider Initialise()
    {
        var serviceCollection = new ServiceCollection();
        ConfigureServices(serviceCollection);
        return serviceCollection.BuildServiceProvider();
    }

    private static void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IService, ServiceImplementation>();
    }
}

1. Scenario: Register a service with constructor parameters known at the time of registration

services.AddTransient<IMyService>(s => new MyService("MyConnectionString"));

2. Scenario: Register an open generic

services.AddScoped(
            typeof(IGenericRepository<>),
            typeof(EFGenericRepository<>));

// Alternative Approach
services.Add(
            new ServiceDescriptor(
                typeof(IPipelineBehavior<,>),
                typeof(LoggingBehavior<,>), ServiceLifetime.Scoped));

3. Scenario: ValidateScopes as a parameter when building ServiceProvider

Detailed explanation on Stackoverflow.

This option will prevent resolving of scoped services from the singleton container, that is if you accidentally try to resolve a scoped service within Configure method, you will get an exception. Whereas if you disable it you shouldn't.

4. Scenario: Use decorator pattern with Scrutor library

Scrutor follows a convention based approach to wire up an interface to implementation. Andrew Lock's Blog

5. Scenario: Multiple implementations of an interface, Use named resolution

Stackoverflow

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<Consumer>();

        services.AddTransient<ServiceA>();
        services.AddTransient<ServiceB>();
        services.AddTransient<ServiceC>();

        services.AddTransient<Func<string, IService>>(serviceProvider => key =>
        {
            switch(key)
            {
                case "A":
                    return serviceProvider.GetService<ServiceA>();
                case "B":
                    return serviceProvider.GetService<ServiceB>();
                case "C":
                    return serviceProvider.GetService<ServiceC>();
                default:
                    throw new KeyNotFoundException(); // or maybe return null, up to you
            }
        });
    }
}

public class Consumer
{
    private readonly Func<string, IService> serviceAccessor;

    public Consumer(Func<string, IService> serviceAccessor)
    {
        this.serviceAccessor = serviceAccesor;
    }

    public void UseServiceA()
    {
        //use serviceAccessor field to resolve desired type
        serviceAccessor("A").DoIServiceOperation();
    }
}