Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Core WebAPI dependency injection resolve null

I use .NET Core WebAPI with dependency injection and multiple authentication schemas (http basic, access keys, JWT). I inject some business services which require some authenticated user data. If user is authenticated by any of auth middleware, DI works fine. If the user is not authenticated, DI cannot resolve some services. I need DI to return null.

How is that possible? Code bellow will result in exception, null is not allowed as result.

services.AddTransient<IMasterRepository>(serviceProvider =>
        {
            var _serviceFactory = new RepositoriesFactory(Configuration);

            if (!Authenticated)
            {
                return null;
            }

            return _serviceFactory.CreateMasterRepository();
        });

Also, I cannot return 401 in auth middleware, because another middleware may success (expl: cannot return 401 in http basic auth middleware because next one, JWT, may success)

Also, I cannot add "authentication required" check after all auth middlewares because some controllers are public (no authentication / dependency injection required).

Any advice? Thanks!

like image 781
Luke1988 Avatar asked Feb 02 '18 16:02

Luke1988


2 Answers

There's no problem in registering an implementation as null. It's only in resolving that you will have a problem.

In other words, if you register:

services.AddTransient<IMasterRepository>(provider => null);

And then try:

private readonly IMasterRepository _repository;

public SomeController(IMasterRepository repository)
{
    _repository = repository;
}

You will get an InvalidOperationException at runtime, with a message similar to:

Unable to resolve service for type 'MyApp.IMasterRepository' while attempting to activate 'MyApp.Controllers.SomeController'

However, there is a simple workaround. Rather than injecting the interface, inject an IEnumerable of that interface:

private readonly IMasterRepository _repository;

public SomeController(IEnumerable<IMasterRepository> repositories)
{
    _repository = repositories.First();  // (using System.Linq)
}

You might think it should be FirstOrDefault, however there will indeed be a single item containing the null you registered.

This approach works because DI in ASP.Net Core supports registering multiple implementations of a given type and doesn't distinguish between null and object instances at time of registration.

Do keep in mind that even though this works, it's not recommended because now the _repository variable is potentially nullable, and a null check must be used every time it is accessed. For example: if (_repository != null) { _repository.DoSomething(); } or _repository?.DoSomething();. Most people do not expect to write code like that.

This covers the DI part of the question. But if indeed the issue is strictly with auth then ste-fu's answer describes a more appropriate approach.

like image 147
Matt Johnson-Pint Avatar answered Sep 22 '22 12:09

Matt Johnson-Pint


The default DI framework does not allow for the factory delegate to return null by design.

Consider null object pattern by creating a NullObject derived from the interface

public class NullRepository : IMasterRepository {
    public static readonly IMasterRepository Empty = new NullRepository();

    public NullRepository () { }

    //...implement members that do nothing and may return empty collections.
}

that does nothing when invoked.

services.AddTransient<IMasterRepository>(serviceProvider => {
    IMasterRepository result = NullRepository.Empty;
    var _serviceFactory = new RepositoriesFactory(Configuration);
    if (Authenticated) {
        result = _serviceFactory.CreateMasterRepository();
    }
    return result;
});

Checking for null now becomes

//ctor
public SomeClass(IMasterRepository repository) {

    if(repository == NullRepository.Empty)
        //...throw

    //...
}
like image 38
Nkosi Avatar answered Sep 21 '22 12:09

Nkosi