Alva

Alva

programmer
github
telegram
email

Dependency Injection in dotnet - 04. Lifecycle

This article provides sample code, all of which uses .NET 6. The specific code can be obtained from this repository Articles.DI.

In the previous articles, when registering services, the services.AddSingleton<TService, TImplementation>() form was uniformly used to register services. What does this method specifically mean? Are there any other similar methods? And we also have a question: what is the lifecycle of a service when the container constructs it? Is a service constructed every time it is requested, or only once during the entire program lifecycle? If we want each service to have a different lifecycle, how can we achieve that? Below, we will explore the lifecycle of services together.

Lifecycle#

When registering with the container, the lifecycle of a service can be divided into three types:

LifecycleWhen CreatedMethod
TransientA new instance is created for each requestAddTransient()
ScopedA new instance is created for the first request within a specified scope
Subsequent requests return the same instance
AddScoped()
SingletonOnly one instance is created for the entire program lifecycle
All subsequent requests return the same instance
AddSingleton()

By referring to the actual code, we can have a more intuitive understanding of the three lifecycles in dependency injection. The following code can be found in Microsoft's .NET documentation in the corresponding article - Tutorial: Use dependency injection in .NET. Alternatively, you can directly view the source code in this repository.

// https://github.com/alva-lin/Articles.DI/tree/master/ConsoleApp1
// 01. Declare interfaces and implementation classes
public interface IOperation
{
    string OperationId { get; }
}

public interface ITransientOperation : IOperation {}

public interface IScopedOperation : IOperation {}

public interface ISingletonOperation : IOperation {}

public class DefaultOperation
    : ITransientOperation, IScopedOperation, ISingletonOperation
{
    // Create a Guid and take the last 4 characters
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
// 02. Create a service that depends on the above operations
public class OperationLogger
{
    private readonly ITransientOperation _transientOperation;
    private readonly IScopedOperation    _scopedOperation;
    private readonly ISingletonOperation _singletonOperation;

    public OperationLogger(ITransientOperation transientOperation,
                           IScopedOperation    scopedOperation,
                           ISingletonOperation singletonOperation)
    {
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void LogOperations(string scope)
    {
        LogOperation(_transientOperation, scope, "Always different");
        LogOperation(_scopedOperation, scope, "Changes only with scope");
        LogOperation(_singletonOperation, scope, "Always the same");
    }

    private static void LogOperation<T>(T operation, string scope, string message)
        where T : IOperation
    {
        Console.WriteLine($"{scope}: {typeof(T).Name,-19} [{operation.OperationId} {message,-23}]");
    }
}
// 03. Register services and start the program
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using IHost host = Host.CreateDefaultBuilder(args)
   .ConfigureServices(services =>
    {
        // Register the three types of operations with their corresponding lifecycles
        services.AddTransient<OperationLogger>()
           .AddTransient<ITransientOperation, DefaultOperation>()
           .AddScoped<IScopedOperation, DefaultOperation>()
           .AddSingleton<ISingletonOperation, DefaultOperation>();
    })
   .Build();

ExemplifyScoping(host.Services, "Scope 1");
ExemplifyScoping(host.Services, "Scope 2");

await host.RunAsync();


static void ExemplifyScoping(IServiceProvider services, string scope)
{
    using IServiceScope serviceScope = services.CreateScope();
    IServiceProvider    provider     = serviceScope.ServiceProvider;

    var logger = provider.GetRequiredService<OperationLogger>();
    logger.LogOperations($"{scope}: Call 1 ...");
    Console.WriteLine();

    logger = provider.GetRequiredService<OperationLogger>();
    logger.LogOperations($"{scope}: Call 2 ...");
    Console.WriteLine();
}

By running the above code, the following results can be obtained:

Scope 1: Call 1 ...: ITransientOperation [b672 Always different       ]
Scope 1: Call 1 ...: IScopedOperation    [afd8 Changes only with scope]
Scope 1: Call 1 ...: ISingletonOperation [21b3 Always the same        ]

Scope 1: Call 2 ...: ITransientOperation [b6fc Always different       ]
Scope 1: Call 2 ...: IScopedOperation    [afd8 Changes only with scope]
Scope 1: Call 2 ...: ISingletonOperation [21b3 Always the same        ]

Scope 2: Call 1 ...: ITransientOperation [ef31 Always different       ]
Scope 2: Call 1 ...: IScopedOperation    [46d1 Changes only with scope]
Scope 2: Call 1 ...: ISingletonOperation [21b3 Always the same        ]

Scope 2: Call 2 ...: ITransientOperation [9864 Always different       ]
Scope 2: Call 2 ...: IScopedOperation    [46d1 Changes only with scope]
Scope 2: Call 2 ...: ISingletonOperation [21b3 Always the same        ]

By comparing the program's output logs, we can see that the output of each ITransientOperation is different, while the IScopedOperation outputs the same content within the same scope and different content between different scopes. The last ISingletonOperation always outputs the same content. This output result also matches the content displayed in the table above.

Dependency injection in .NET

Tutorial: Use dependency injection in .NET

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.