Can ASP.NET core dependency injection inject null references?

In my job we are developing ASP.NET core applications. I keep seeing the follwing pattern used with dependency-injection. In this example Logic depends on OtherLogic:

public class Logic {
    private readonly OtherLogic myOtherLogic;

    public Logic(OtherLogic myOtherLogic) {
        // I mean specifically this pattern of checking injected dependencies for null:
        this.myOtherLogic = myOtherLogic
            ?? throw new ArgumentNullException(nameof(myOtherLogic));
    }
}

All dependencies are added to an IServiceCollection during service configuration and both (Logic and OtherLogic) are only retrieved from dependency injection. In my current project I’m using DotNet 5 and I have enabled nullable reference types if that matters.

I’d like to drop this Guard Clause pattern, because I believe the injector will always throw an exception (during service configuration) when it’s unable to resolve a non-optional service.
My question is, in which circumstances the injector could inject a null-reference for a non-optional service? Is it even possible?

This article doesn’t mention the possibility of null being injected, but as far as I can see, also doesn’t explicitly deny it. Just want to make sure I don’t run into trouble later on, thanks for your time 🙂

>Solution :

Under ‘normal’ conditions (i.e. when solely depending on the use of Auto-Wiring) null values can’t be injected. The following code snippets, however, show examples of when null references can be injected into Logic:

Case 1: Manual construction

new Logic(null);

Case 2: Manual construction using factory registration

services.AddTransient(c => new Logic(null));

Case 3: Direct null injection through factory registration

services.AddTransient(c => new Logic(null));

Case 4: Incorrect use of GetService opposed to GetRequiredService in factory

services.AddTransient(c => new Logic(c.GetService<OtherLogic>()));

Case 5: Auto-registration with dependency that returns null from its factory registration

services.AddTransient<Logic>();
services.AddTransient<OtherLogic>(c => null);

In my opinion, option 5 should not be allowed, and I consider it a design flow in MS.DI to allow a factory registration to return null. But that still leaves the other three cases where the Logic constructor is called using plain-old C#.

Whether or not you want to be very safe and protect the class’s pre conditions using a Guard Clause is up to you. Personally, for applications where I make use of a DI Container that doesn’t allow case 5 and makes it hard to do case 4, I typically leave out the Guard Clauses. This even allows me to use the more concise record syntax:

public sealed record Logic(OtherLogic MyOtherLogic)
{
}

Leave a Reply