Archives 07/24/2024

Method injection – Dependency Injection

ASP.NET Core supports method injection only at a few locations, such as in a controller’s actions (methods), the Startup class (if you are using the pre-.NET 6 hosting model), and the middleware’s Invoke or InvokeAsync methods. We cannot liberally use method injection in our classes without some work on our part.Method injection is also used to inject optional dependencies into classes. We can also validate those at runtime using null checks or any other required logic.

I recommend aiming for constructor injection whenever you can. We should only resort to method injection when it’s our sole option or when it brings added value to our design.

For example, in a controller, injecting a transient service in the only action that needs it instead of using constructor injection could save a lot of useless object instantiation and, by doing so, increase performance (less instantiation and less garbage collection). This can also lower the number of class-level dependencies a single class has.

Manually injecting a dependency in a method as an argument is valid. Here’s an example, starting with the classes:

namespace CompositionRoot.ManualMethodInjection;
public class Subject
{
    public int Operation(Context context)
    {
        // …
       
return context.Number;
    }
}
public class Context
{
    public required int Number { get; init; }
}

The preceding code represents the Subject class that consumes an instance of the Context from its Operation method. It then returns the value of its Number property.

This example follows a similar pattern to injecting an HttpContext into an endpoint delegate. In that case, the HttpContext represents the current HTTP request. In our case, the Context contains only an arbitrary number we use in the consuming code next.

To test that our code does as it should, we can write the following test:

[Fact]
public void Should_return_the_value_of_the_Context_Number_property()
{
    // Arrange
    var subject = new Subject();
    var context = new Context { Number = 44 };
    // Act
    var result = subject.Operation(context);
    // Assert
    Assert.Equal(44, result);
}

When we run the test, it works. We successfully injected the context into the subject. Now to simulate a more complex system, let’s have a look at a theory that does the same more dynamically:

[Theory]
[MemberData(nameof(GetData))]
public void Showcase_manual_method_injection(
    Subject subject, Context context, int expectedNumber)
{
    // Manually injecting the context into the
    // Operation method of the subject.
   
var number = subject.Operation(context);
    // Validate that we got the specified context.
   
Assert.Equal(expectedNumber, number);
}

The preceding code showcases the same concept, but xUnit injects the dependencies into the method, which is closer to what would happen in a real program. Remember, we want to remove the new keywords from our life!

The rest of the implementation is not important. I only pieced the simulation together to showcase this scenario. One interesting detail is that the Subject is always the same (singleton) while the Context is always different (transient), leading to a different outcome every time (Context { Number = 0 }, Context { Number = 1 }, and Context { Number = 2 }).

Having explored how to inject dependencies, we are ready to roll up our sleeves and dive into hands-on coding.