Categorie > Programmeren

Dependency Injection in .NET

geschreven door Wilco van Dijk


Dependency Injection, afgekort DI, is een design pattern die wordt gebruikt in objectgeoriënteerd programmeren om de hard-gecodeerde afhankelijkheden tussen objecten te verminderen. Een afhankelijkheid verwijst naar een stuk code dat op een andere bron vertrouwt voor het uitvoeren van zijn beoogde functie, vaak een ander object binnen dezelfde applicatie.

"Dependency Injection is een techniek die afhankelijkheden aan een class levert, waardoor afhankelijkheidsinversie wordt bereikt. Afhankelijkheden worden doorgegeven (geïnjecteerd) in een client die ze nodig heeft."

DI wordt veel gebruikt in .NET toepassingen en het framework zelf biedt het ook native aan. Het gebruik van DI biedt veel voordelen, zoals:

  • Het is eenvoudiger om een wijziging in de code aan te brengen zonder andere delen van de applicatie te beïnvloeden.

  • Maakt het mogelijk voor de ontwikkelaar om code te schrijven die gemakkelijker te testen is. Met DI is het mogelijk om gesimuleerde afhankelijkheden in de (unit)tests te injecteren. Wil je hier meer over weten? Klik dan hier.

  • Ontwikkelaars kunnen parallel aan verschillende stukken functionaliteit werken, aangezien de implementaties onafhankelijk van elkaar zijn.

  • Verbetert de leesbaarheid en netheid van de code

In dit artikel leg ik uit hoe dit in .NET is geïmplementeerd en wat de verschillen zijn tussen de verschillende levensduren die gebruikt kunnen worden.

Microsoft DI Container

De IServiceCollection is een interface in de Microsoft.Extensions.DependencyInjection namespace. Deze interface wordt gebruikt om een (DI) container te definiëren voor het beheren van afhankelijkheden in een applicatie. Het maakt deel uit van het .NET framework en wordt veelal gebruikt in .NET webapplicaties.

De IServiceCollection interface vertegenwoordigt een container voor het registreren en opvragen van services die door de hele applicatie gebruikt kunnen worden. Het biedt methoden om services met verschillende levensduren te registreren, zoals transiënt, scoped en singleton. Deze services kunnen worden geregistreerd met of zonder implementatietypen en kunnen eenvoudig worden opgevraagd met constructor injectie in de classes die ervan afhankelijk zijn.

Service levensduur

Wanneer we services registreren in een container, moeten we de levensduur instellen die we willen gebruiken. De levensduur van de service bepaalt hoe lang een object blijft bestaan nadat het is gemaakt door de container. De levensduur kan worden ingesteld met behulp van de juiste extensiemethode op de IServiceCollection bij het registreren van de service.

Er zijn drie levensduren die kunnen worden gebruikt met de Microsoft Dependency Injection Container:

  • Transiënt — Services worden gemaakt telkens wanneer ze worden opgevraagd. Elke keer dat dit object wordt geïnjecteerd, wordt er een nieuwe instantie van het geïnjecteerde object gemaakt. Elke keer dat je dit object injecteert in een class, wordt er een nieuwe instantie aangemaakt.

  • Scoped — Services worden gemaakt per request (één keer per request). Dit wordt het meest aanbevolen voor webtoepassingen. Dus bijvoorbeeld, als je tijdens een verzoek dezelfde DI op meerdere plaatsen gebruikt, dan gebruik je dezelfde instantie van dat object.

  • Singleton — Services worden één keer gemaakt voor de levensduur van de applicatie. Het gebruikt dezelfde instantie voor de hele applicatie.

De DI container houdt alle instanties van de gemaakte services bij, en ze worden verwijderd of vrijgegeven voor de Garbage collector zodra hun levensduur is beëindigd.

Registratie

Dit is hoe je services kunt registreren voor DI in een ASP.NET Web applicatie.

public void ConfigureServices(IServiceCollection services)
{
    // Transient
    services.AddTransient<ICustomerRepository, CustomerRepository>();

    // Singleton
    services.AddSingleton<IEmailService, EmailService>();

    // Scoped
    services.AddScoped<ICustomerService, CustomerService>();

    // Httpclient
    services.AddHttpClient<IServiceClient, ServiceClient>(c =>
    {
        c.BaseAddress = new Uri(baseUrl);
        c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    })
    .ConfigurePrimaryHttpMessageHandler(messageHandler =>
    {
        var handler = new HttpClientHandler();
        if (handler.SupportsAutomaticDecompression)
            handler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;

        return handler;
    })

    // Configuration examples
    services.Configure<EmailOptions>(builder.Configuration.GetSection("Email")); // Transform appsettings to options and register to collection

    services.AddSingleton(Options.Options.Create(new ServiceClientOptions // Register options directly to collection
    {
        BaseUrl = baseUrl
    }));
}

Het is ook mogelijk een eigen instantie van IServiceCollection op te zetten voor gebruik in een Console applicatie.

class Program
{
    static async Task Main(string[] args)
    {
        // Create service collection and configure our services
        var services = ConfigureServices();

        // Generate a provider
        var serviceProvider = services.BuildServiceProvider();

        // Kick off our actual code
        await serviceProvider.GetService<Application>().RunAsync();
    }

    private static IServiceCollection ConfigureServices()
    {
        IServiceCollection services = new ServiceCollection();

        IConfiguration config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: true)
            .AddEnvironmentVariables()
            .Build();

        // External services
        services.AddTransient<ICustomerRepository, CustomerRepository>();
        services.AddSingleton<IEmailService, EmailService>();
        services.AddScoped<ICustomerService, CustomerService>();

        // Internal services
        services.AddTransient<Application>();

        return services;
    }
}

Constructor injectie

Als de services zijn geregistreerd kun je ze via constructor injectie opvragen voor gebruik in andere services. Een voorbeeld hiervan is het opvragen van de ICustomerRepository in de CustomerService.

public class CustomerService
{
    private readonly ICustomerRepository _customerRepository;

    public CustomerService(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    public async Task AddCustomer(Customer customer)
    {
        await _customerRepository.AddAsync(customer);
    }
}
Heeft dit artikel jou op weg geholpen?  Buy Me A Coffee