Dependent Injection in.NET: Registration Method

The sample code for this article uses. NET 6, specific code can be in this repository Articles.DI Get in.

From the previous article, you learned about the three declaration cycles of a service. So what APIs are provided by the framework if we need to register services? How do we write code when we want to declare services and specific implementations based on our own needs? This article will discuss. NET built-in DI framework provides APIs, and how to use them, specific roles.

NET API browser, provides detailed API documentation. this link The API related to the registration service is shown.

Registration Method

There must be more than Add {LifeTime}<TService, TImplementation>() to register a service. Click this for the specific method type link To see the table provided in the document, and I'll paste it directly below.

Method Automatic release Multiple implementations Pass-through parameters
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() yes yes no
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) yes yes yes
Add{LIFETIME}<{IMPLEMENTATION}>() yes no no
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) no yes yes
AddSingleton(new {IMPLEMENTATION}) no no yes

The table above lists only methods that support generics, and methods such as Add {LIFETIME} (typeof ({SERVICE}), and typeof ({IMPLEMENTATION}), which are essentially the same as generic methods, are not listed here. The three columns on the right of the table describe the limitations of these methods.

Service Release

Containers are responsible for building services, but if a service expires, who is responsible for releasing the service? Are you a service dependant? Or container?

The auto-release column in the previous table, if yes, indicates that services registered by such methods are released uniformly by containers. If IDisposable interfaces are implemented in these services, then the Dispose method is also called automatically by containers instead of explicitly calling the release method in code.

Looking at the table, you can see that AddSingleton <{SERVICE}> (new {IMPLEMENTATION}) and AdSingleton (new {IMPLEMENTATION}) methods do not automatically release, container frameworks do not release themselves, so developers are responsible for the release of services themselves.

Multiple implementations

In the previous example, we registered as an interface, as an implementation class, and as a dependency function injection, as a single service. If we want to register multiple implementations as the same interface, what methods should we use?

As you can see in the previous table, there are two types of methods that do not support multiple implementations, Add {LIFETIME}<{IMPLEMENTATION}>() and Add Singleton (new {IMPLEMENTATION}). In fact, you can see from the description of the method that when the former is registered, it directly implements the IMPLEMENTATION class and registers it as a service of type IMPLEMENTATION, which can not achieve multiple implementations. Similar to the latter, directly registering an instance will naturally fail to achieve multiple implementations.

Multiple implementations here refer to multiple implementation classes that implement the same interface and are registered with that interface when registering services. In the previous article, we used an implementation class to implement multiple interfaces. When registering a service, this class registered multiple interfaces at the same time. The two situations are different. The latter is not a multiple implementation.

Next, we'll use the code directly to show you how to do a variety of things:

// https://github.com/alva-lin/Articles.DI/tree/master/WorkerService4
// 1. Set an interface IMyDependency and multiple implementation classes that implement the interface
public interface IMyDependency
{
    string Id { get; }

    bool Flag { get; }
}

public class MyDependency0 : IMyDependency
{
    public string Id { get; } = Guid.NewGuid().ToString()[^4..];

    public bool Flag { get; init; } = false;
}

public class MyDependency1 : MyDependency0 {}
public class MyDependency2 : MyDependency0 {}
public class MyDependency3 : MyDependency0 {}
public class MyDependency4 : MyDependency0 {}
// 2. Registration Services
using Microsoft.Extensions.DependencyInjection.Extensions;

IHost host = Host.CreateDefaultBuilder(args)
   .ConfigureServices(services =>
    {
        // Register multiple implementations of an interface at the same time
        services.AddTransient<IMyDependency, MyDependency1>();
        services.AddTransient<IMyDependency, MyDependency2>();

        // TryAdd If the interface is already registered, it will not be registered
        // IMyDependency is already registered, so MyDependency3 will not be registered as its implementation
        services.TryAddTransient<IMyDependency, MyDependency3>();

        // TryAddEnumerable If the same implementation of an interface has been registered, it will not be re-registered
        // IMyDependency -> MyDependency4, this relationship has not been registered, so it can be successfully registered
        var descriptor = new ServiceDescriptor(
            typeof(IMyDependency),
            typeof(MyDependency4),
            ServiceLifetime.Transient);
        services.TryAddEnumerable(descriptor);

        // The interface is not checked for registration, the relationship is not checked for registration, and registration is repeated
        // MyDependency2 has been registered twice
        // Then when getting IEnumerable <IMyDependency>
        // There are two instances of MyDependency2 type
        services.AddTransient<IMyDependency, MyDependency2>(_ => new MyDependency2
        {
            Flag = true // Use the factory method alone to construct this service to distinguish the last registered service
        });
    })
   .Build();

Fun(host.Services);

static void Fun(IServiceProvider serviceProvider)
{
    using var scopeServices = serviceProvider.CreateScope();
    var       services      = scopeServices.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    GetData(myDependency);
    Console.WriteLine();

    var list = services.GetRequiredService<IEnumerable<IMyDependency>>();
    foreach (var dependency in list)
    {
        GetData(dependency);
    }
    Console.WriteLine();
}

static void GetData<T>(T item) where T : IMyDependency
{
    Console.WriteLine($"{item.GetType().Name} {item.Id}, {item.Flag}");
}

The results of the program are as follows:

MyDependency2 c432, True

MyDependency1 ea48, False
MyDependency2 9b9a, False
MyDependency4 c4ce, False
MyDependency2 77e9, True

View the output of the entire program, because all services are registered as a declaration cycle of type Transient, so the Id of each instance is different.

Look at the first line of output, item.Flag is True, which means that when the IMyDependency type is resolved, the last successfully registered implementation class will be constructed and injected (that is, the myDependency variable in this case).

Comparing the following four lines of output, the order is exactly the order in which the service was registered, that is, when you get variables of type IEnumerable < IMyDependency >, all successfully registered types are injected in the registration order. The second and fourth lines are MyDependency2, indicating that the registration service can be re-registered for the same implementation.

Pass-through parameters

Method Automatic release Multiple implementations Pass-through parameters
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() yes yes no
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) yes yes yes
Add{LIFETIME}<{IMPLEMENTATION}>() yes no no
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) no yes yes
AddSingleton(new {IMPLEMENTATION}) no no yes

Pass-through parameters are essentially whether the container can use factory methods to construct service instances in a variety of ways instead of simply calling the constructor. Look at this table, and if a factory method is passed, its pass parameter columns are all yes and vice versa. In fact, even if the factory method is not passed, there are other ways to do this.

For example, use the IOperation <> type in the constructor. By registering the required configuration data with IOperation <MyOperation>you can get the real-time data you need in the constructor to pass parameters. The acquisition of specific configuration data can be placed in the registration method of IOperation<MyOperation>

Reference Links

Dependency Injection in.NET

Keywords: .NET

Added by hannnndy on Mon, 17 Jan 2022 12:32:42 +0200