How to Inject Primitive Types into Constructors in .Net Core

Dependency Injection (DI) is a cornerstone of modern software architecture, promoting flexibility, testability, and modularization. However, integrating DI into legacy code, particularly when dealing with primitive types, can pose challenges. In this blog post, we’ll explore creative methods for handling primitive type dependency injection using a custom factory and delegate-based registration in the .NET Core dependency injection container.

Problem Scenario: Dependency Injection in Unmodifiable Legacy Code

Imagine a legacy codebase featuring a LegacyCalculatorService. This service performs calculations using two primitive values: _baseValue and _multiplier. Unfortunately, the existing code structure prohibits direct dependency injection, making it difficult to update and modernize.

public class LegacyCalculatorService
{
    private readonly double _baseValue;
    private readonly double _multiplier;

    public LegacyCalculatorService(double baseValue, double multiplier)
    {
        _baseValue = baseValue;
        _multiplier = multiplier;
    }

    public double Calculate()
    {
        return _baseValue * _multiplier;
    }
}

Approach 1: Leveraging Custom Factory

Legacy Calculator Service Factory

To overcome the limitations imposed by the legacy code, we can introduce a custom factory class, LegacyCalculatorServiceFactory:

public class LegacyCalculatorServiceFactory
{
    private readonly double _baseValue;
    private readonly double _multiplier;

    public LegacyCalculatorServiceFactory(double baseValue, double multiplier)
    {
        _baseValue = baseValue;
        _multiplier = multiplier;
    }

    public LegacyCalculatorService Create()
    {
        return new LegacyCalculatorService(_baseValue, _multiplier);
    }
}

Integrating the Factory

The LegacyCalculatorServiceFactory can be employed to instantiate LegacyCalculatorService instances within the .NET Core dependency injection setup:

// Within the dependency injection configuration
services.AddTransient<LegacyCalculatorServiceFactory>(provider =>
{
    // Supply the required primitive values here
    double baseValue = 10.0;
    double multiplier = 2.0;
    return new LegacyCalculatorServiceFactory(baseValue, multiplier);
})
.AddTransient(provider =>
{
    var factory = provider.GetRequiredService<LegacyCalculatorServiceFactory>();
    return factory.Create();
});

Approach 2: Delegate-Based Registration

Delegate-Based Registration

When unable to modify legacy code directly, delegate-based registration in the .NET Core dependency injection container offers a solution:

class Program
{
    static void Main(string[] args)
    {
        var serviceProvider = new ServiceCollection()
            .AddTransient<LegacyCalculatorServiceFactory>(provider =>
            {
                // Supply the required primitive values here
                double baseValue = 10.0;
                double multiplier = 2.0;
                return new LegacyCalculatorServiceFactory(baseValue, multiplier);
            })
            .AddTransient(provider =>
            {
                var factory = provider.GetRequiredService<LegacyCalculatorServiceFactory>();
                return factory.Create();
            })
            .BuildServiceProvider();

        var legacyCalculatorService = serviceProvider.GetRequiredService<LegacyCalculatorService>();
        double result = legacyCalculatorService.Calculate();
        Console.WriteLine($"Legacy Calculation Result: {result}");
    }
}

Conclusion

Integrating Dependency Injection into unmodifiable legacy code demands innovative solutions. By employing a custom factory or delegate-based registration, you can achieve primitive type dependency injection without modifying the legacy codebase. These techniques preserve the integrity of the legacy code while enabling the adoption of modern software design principles.

This blog post delved into two strategies for handling primitive type dependency injection in legacy code using the .NET Core dependency injection framework. Mastering these approaches empowers you to tackle the intricacies of integrating DI into existing systems, enhancing their maintainability, testability, and flexibility.

Next Post Previous Post
No Comment
Add Comment
comment url