65.9K
CodeProject is changing. Read more.
Home

Optional DependencyInjection in .NET

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.29/5 (8 votes)

Jun 11, 2023

CPOL
viewsIcon

18196

How to optionally inject a dependency using Microsoft DependencyInjection container

Introduction

Suppose we need to inject an optional dependency:

public class CreateUserHandler
{
    private readonly IValidator<CreateUserRequest> _validator;
    private readonly IUsersRepository _usersRepository;

    public CreateUserHandler(IUsersRepository usersRepository, 
                             IValidator<CreateUserRequest> validator = null)
    {
        _validator = validator;
        _usersRepository = usersRepository;
    }

    public async Task<CreateUserResponse> Handle(CreateUserRequest request)
    {
        _validator?.ValidateAndThrow(request);
        var user = new User { Name = request.Name };
        await _usersRepository.Create(user);

        return new() { CreatedSuccesfully = true };
    }
}

In the snippet above, validator is our optional dependency. What we are trying to accomplish is the following: If IValidator<CreateUserRequest> is registered in the DI container, then inject it so that we can use it, otherwise just pass null.

If we try to run the code as it is using the Microsoft Depdendency Injection, we get a runtime error. Can we still achieve something similar?

Of course!

Solution

First, create an interface that will represent our OptionalDependency:

public interface IOptionalDependency<T>
{
    T? Value { get; }
}

Then create a concrete implementation that will retrieve the value using the serviceProvider instance. GetService returns default(T) if the service is not registered.

public class OptionalDependency<T> : IOptionalDependency<T>
{
    public OptionalDependency(IServiceProvider serviceProvider)
    {
        Value = serviceProvider.GetService<T>();
    }

    public T? Value { get; }
}

Then we have to register the open generic IOptionalDependency<> in the container:

services.AddTransient(typeof(IOptionalDependency<>), typeof(OptionalDependency<>));

Finally, we have to inject our optional dependency in the handler.

public class CreateUserHandler
{
    private readonly IValidator<CreateUserRequest>? _validator;
    private readonly IUsersRepository _usersRepository;

    public CreateUserHandler(IUsersRepository usersRepository, 
           IOptionalDependency<IValidator<CreateUserRequest>> validator)
    {
        _validator = validator.Value;
        _usersRepository = usersRepository;
    }

    public async Task<CreateUserResponse> Handle(CreateUserRequest request)
    {
        _validator?.ValidateAndThrow(request);
        var user = new User { Name = request.Name };
        await _usersRepository.Create(user);

        return new() { CreatedSuccesfully = true };
    }
}

We can also use some libraries such as Optional, to be more explicit that the Value can also not be there. The code should now look something like:

public interface IOptionalDependency<T>
{
    Option<T> Value { get; }
}

public class OptionalDependency<T> : IOptionalDependency<T>
{
    public OptionalDependency(IServiceProvider serviceProvider)
    {
        Value = serviceProvider.GetService<T>().SomeNotNull();
    }

    public Option<T> Value { get; }
}

public class CreateUserHandler
{
    private readonly Option<IValidator<CreateUserRequest>> _validatorOption;
    private readonly IUsersRepository _usersRepository;

    public CreateUserHandler(IUsersRepository usersRepository, 
           IOptionalDependency<IValidator<CreateUserRequest>> validator)
    {
        _validatorOption = validator.Value;
        _usersRepository = usersRepository;
    }

    public async Task<CreateUserResponse> Handle(CreateUserRequest request)
    {
        _validatorOption.MatchSome(validator => validator.ValidateAndThrow(request));
        var user = new User { Name = request.Name };
        await _usersRepository.Create(user);

        return new() { CreatedSuccesfully = true };
    }
}

History

  • 11th June, 2023: Initial version