Dependency Injection Into {get; set;} Property

Viewed 992

I was wondering how I'd go about setting up my Dependency Injection to inject a dependency into properties that have public getters and setters ({get; set}).

So, an example would be:

namespace Dexter.Services {

    public class CommandHandlerService : InitializableModule {

        public CommandService CommandService { get; set; }

    }

}

With the following dependency injector:

namespace Dexter {

    public static class InitializeDependencies {

        public static async Task Main() {

            ServiceCollection ServiceCollection = new();

            CommandService CommandService = new();
            ServiceCollection.AddSingleton(CommandService);

            Assembly.GetExecutingAssembly().GetTypes()
                    .Where(Type => Type.IsSubclassOf(typeof(InitializableModule)) && !Type.IsAbstract)
                    .ToList().ForEach(
                Type => ServiceCollection.TryAddSingleton(Type)
            );


            ServiceProvider = ServiceCollection.BuildServiceProvider();

            // Initialization stuff.
        }

    }

}

In this example, I would like the CommandService to automatically inject into the property.

I know this is possible because Discord.NET is able to do this, and I'd love to stick with that same codestyle.

( Discord.NET: https://docs.stillu.cc/guides/commands/dependency-injection.html )

Thanks! <3

2 Answers

For anyone curious, as per what Panagiotis recommended, a solution to this would be to create your own dependency injection. As such, I wrote a small method that loops through all the services in the provider, and attaches public properties to it. It may have bugs! Particularly regarding scoped services, of which I haven't written for it to support, but this should work as a good starting point for someone wishing to achieve a similar result!

public static object SetClassParameters(this object newClass, IServiceScope scope, IServiceProvider sp)
{
    newClass.GetType().GetProperties().ToList().ForEach(property =>
    {
        if (property.PropertyType == typeof(IServiceProvider))
            property.SetValue(newClass, sp);
        else
        {
            object service = scope.ServiceProvider.GetService(property.PropertyType);

            if (service != null)
            {
                property.SetValue(newClass, service);
            }
        }
    });

    return newClass;
}

Where you can use a method like the following to inject dependencies into classes. For instance, I wished to inject them into classes that extended an abstract "event" class that I made. This can be seen below:

using (var scope = serviceProvider.CreateScope()) {
    GetEvents().ForEach(
        type => serviceProvider.GetRequiredService(type).SetClassParameters(scope, serviceProvider)
    );
}

Where GetEvents() is a reflexive function that returns all classes extending the abstract class given.

This can be done without having to swap out the default DI container (IServiceProvider) using the Quickwire NuGet package.

Simply decorate your service with the [RegisterService] attribute and add [InjectService] to the property. No need for the interface.

[RegisterService(ServiceLifetime.Singleton)]
public class CommandHandlerService {

    [InjectService]
    public CommandService CommandService { get; set; }

}

Now from your main function, just call ScanCurrentAssembly:

public static async Task Main() {

    ServiceCollection ServiceCollection = new();

    ServiceCollection.ScanCurrentAssembly();

    ServiceProvider = ServiceCollection.BuildServiceProvider();

    // Initialization stuff.
}

Behind the scenes, ScanCurrentAssembly does all the necessary wiring to resolve dependencies, instantiate the class and inject it into properties.

Related