static void

ASP.Net Core Dependency Injection

See docs.asp.net

There is a simple DI built into core (using IServiceProvider, which goes back to .net 1.1 but now is in Microsoft.Extensions.DependencyInjection.Abstractions).
It's functional enough that a full DI container isn't required, but the usual suspects have hooks.

See autofac below

Configuration

Services are configured in Startup.ConfigureServices(IServiceCollection services)

Most middleware services are added using the standard services.AddX convention.

public void ConfigureServices(IServiceCollection services)
{
    // Add middleware services.
    services.AddMvc();
    // Add application services.

    //newly created each time
    services.AddTransient<IServiceService>();
    //created for request, reused within request
    services.AddScoped<IServiceService>();
    //create a singleton, created here
    services.AddSingleton<IService>(new Service());
    //create a singleton, created lazily
    services.AddSingleton<IServiceService>();
}

Use

Consume using constructor injection- chained dependencies are fine.

If absolutely necessary to break DI, you can also access Service-Locator style, from HttpContext.RequestServices

Consoles

You can use something similar in consoles, but it's overkill when you just want simple configuration, DI and logging. It's best to call the Host.CreateDefaultBuilder, grab the IServiceProvider from it, and GetRequiredService of your class, which is now fully DI configured for you.

This sample uses NLog. If you have disposables in your services, you'll need to cast the service as IDisposable within a using block.

class Program
{
    static async Task Main(string[] args)
    {
        var logger = LogManager.GetCurrentClassLogger();
        try
        {
            var host = CreateHostBuilder(args).Build();
            //we don't use the host, just grab the DI container which has been built with options
            var serviceProvider = host.Services;
            var runner = serviceProvider.GetRequiredService<Runner>();
            runner.Run();
        }
        catch (Exception ex)
        {
            // NLog: catch any exception and log it.
            logger.Error(ex, "Top level exception");
            
            throw;
        }
        finally
        {
            // Flush and stop internal timers/threads
            LogManager.Shutdown();
        }
    }
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.Configure<ConfigDetails>(hostContext.Configuration.GetSection("ConfigDetails"));
                services.AddTransient<Runner>();
                services.AddLogging(loggingBuilder =>
                {
                    // configure Logging with NLog
                    loggingBuilder.ClearProviders();
                    loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
                    loggingBuilder.AddNLog();
                });
            });
}

Autofac

Eg Autofac, add Autofac.Extensions.DependencyInjection and in Program.cs:

Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory()) //etc

Fuller example

            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            //grab the connection string
            var connString = builder.Configuration.GetConnectionString("Database");

            //autofac
            builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .ConfigureContainer<ContainerBuilder>(cb =>
                {
                    cb.RegisterModule(new RegistryModule());
                    //inject the connection string
                    cb.Register(c => new ConnectionFactory(connString)).As<IConnectionFactory>();
                });

Extract the DI config itself into an autofac module. Especially if you have multiple websites/apis/consoles/tests, one or more DI config modules is the best way to build them in the same way.

using Autofac;

namespace MyWebApi;

public class RegistryModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        //hook up all your assemblies
        builder.RegisterAssemblyTypes(typeof(MyService).Assembly).PublicOnly().AsImplementedInterfaces();
    }
}

DI with user

The key here is to hook into IHttpContextAccessor in a lifetime scope.

public interface IUserContext
{
    ClaimsPrincipal User { get; }
}
public class UserContext : IUserContext
{
    public UserContext(IHttpContextAccessor httpContextAccessor)
    {
        User = httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal();
    }

    public ClaimsPrincipal User { get; }
}

using Autofac;
using Logic;
using Microsoft.AspNetCore.Http;

namespace Api.Startup
{
    public class RegistryModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            //register interfaces
            builder.RegisterAssemblyTypes(typeof(ConnectionFactory).Assembly).PublicOnly().AsImplementedInterfaces();
            //register also concrete classes
            builder.RegisterAssemblyTypes(typeof(ConnectionFactory).Assembly).PublicOnly();

            //register httpContext (yes, as a singleton)
            builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
            //and the user is scoped
            builder.RegisterType<UserContext>().As<IUserContext>().InstancePerLifetimeScope();
        }
    }
}

And hook up the module from your Program.Main

public class Program
{

    public static void Main(string[] args)
    {

        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        //grab the connection string
        var connectionString = builder.Configuration.GetConnectionString("Db");

        //autofac
        builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
            .ConfigureContainer<ContainerBuilder>(cb =>
            {
                cb.RegisterModule(new RegistryModule());
                //inject the connection string
                cb.Register(c => new ConnectionFactory(connectionString)).As<IConnectionFactory>();
            });

Testing DI

You should have some sort of test for UI, plus, except for simple unit tests, you probably want DI in tests.

Here's the test structure we want (this is an integration test - we save and read a record but the non-committing transaction ensures nothing remains in the db).

using Autofac;
using Services;
using Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Transactions;

namespace IntegrationTests
{
    [TestClass]
    public class TestService
    {
        private readonly TestContainer _container = new TestContainerBuilder().Build();

        [TestMethod]
        public void TestSave()
        {
            using (var scope = _container.LifetimeScope)
            {
                var service = scope.Resolve<Service>();
                using (new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
                {
                    var model = new Model();
                    var result = service.Save(model);
                    Assert.IsTrue(result.IsSuccess);
                    var dbModel = service.Read(result.Id);
                    Assert.IsNotNull(dbModel);
                }
            }
        }

We have a TestContainer to wrap the DI container

public class TestContainer : IDisposable
{
    private readonly IContainer _container;

    public TestContainer(IContainer container)
    {
        _container = container;
    }

    public ILifetimeScope? LifetimeScope => _container.BeginLifetimeScope();

    public void Dispose()
    {
        _container.Dispose();
    }
}

This is setup in a builder.

using Autofac;
using Services.AutoMapper;
using DataAccess;
using Models;
using Microsoft.Extensions.Caching.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace IntegrationTests
{
    public class TestContainerBuilder
    {
        public TestContainer Build()
        {
            //if you use automapper, call the MapperConfiguration.CreateMapper() 
            AutoMap.RegisterMappings();

            //read the configuration (appsettings.json)
            Host.CreateDefaultBuilder()
                .ConfigureServices((contextservices) =>
                {
                    Configuration = context.Configuration;
                })
                .Build();
#pragma warning disable CS8604 // Possible null reference argument.
            var connectionString = Configuration.GetConnectionString("Db");
#pragma warning restore CS8604 // Possible null reference argument.

            //build the Autofac container
            var builder = new ContainerBuilder();
            builder.Register(c => new ConnectionFactory(connectionString)).As<IConnectionFactory>();
            builder.RegisterModule<TestRegistryModule>();
            //Microsoft.Extensions.Caching.Memory added manually
            builder.Register(m=> new MemoryCache(new MemoryCacheOptions())).As<IMemoryCache>().SingleInstance();
            var container = builder.Build();
            return new TestContainer(container);
        }

        public IConfiguration? Configuration { getset; }
    }
}

If you're injecting the user (see the IHttpContextAccessor trick above), you can create a special test user context (see IUserContext above). If you use an IClaimsTransformer in your website pipeline, you'll need to run that too.

using Model;
using System;
using System.Collections.Generic;
using System.Security.Claims;

namespace IntegrationTests
{
    //register this in DI
    //builder.RegisterType<TestUserContext>().As<IUserContext>().InstancePerLifetimeScope();
    public class TestUserContext : IUserContext
    {
        public TestUserContext()
        {
            //using the name of the person running the test, or a fixed string eg "TestUser"
            var name = new Claim(ClaimTypes.Name, Environment.UserName);
            var principal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { name }, "Basic"));
            User = principal;
        }

        public ClaimsPrincipal User { get; }
    }
}