Click here to Skip to main content
15,887,746 members
Articles / Programming Languages / C#
Tip/Trick

Optional DependencyInjection in .NET

Rate me:
Please Sign up or sign in to vote.
4.29/5 (8 votes)
11 Jun 2023CPOL 16K   8   4
How to optionally inject a dependency using Microsoft DependencyInjection container
This tip provides a solution for injecting optional dependencies in C# using the Microsoft Dependency Injection container.

Introduction

Suppose we need to inject an optional dependency:

C#
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:

C#
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.

C#
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:

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

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

C#
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:

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
PraiseGreat solution Pin
Matjaž Bravc10-Jan-24 22:09
Matjaž Bravc10-Jan-24 22:09 
QuestionDo you have a practical example? Pin
dietmar paul schoder16-Jun-23 0:43
professionaldietmar paul schoder16-Jun-23 0:43 
AnswerRe: Do you have a practical example? Pin
Federico Alterio16-Jun-23 5:19
Federico Alterio16-Jun-23 5:19 
PraiseRe: Do you have a practical example? Pin
dietmar paul schoder16-Jun-23 23:14
professionaldietmar paul schoder16-Jun-23 23:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.