Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create instance using ctor injection and ServiceProvider

I know there is IServiceCollection interface where I can register my services and IServiceProvider which can instantiate the services.

How do I instantiate a class, based on specified Type, which uses registered services?

class MyClass
{
    public MyClass(ISomeService someService) { }
}

var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<ISomeService, SomeService>()

MyClass instance = CreateInstance(typeof(MyClass));

object CreateIntance(Type type)
{
   ???
}

For example, how does ASP.NET Core creates controller instances?

I've made naive implementation of the activator but isn't there something like this in .NET Core already?

private static object CreateInstance(Type type, IServiceProvider serviceProvider)
{
    var ctor = type.GetConstructors()
        .Where(c => c.IsPublic)
        .OrderByDescending(c => c.GetParameters().Length)
        .FirstOrDefault()
        ?? throw new InvalidOperationException($"No suitable contructor found on type '{type}'");

    var injectionServices = ctor.GetParameters()
        .Select(p => serviceProvider.GetRequiredService(p.ParameterType))
        .ToArray();

    return ctor.Invoke(injectionServices);
}

}

EDIT: this is my scenario. I've refactored some legacy code that implements this interface.

public interface IEventDispatcher
{
    void RegisterHandler(Type handler);
    void Dispatch(DomainEvent @event);
}

In the Dispatch method implementation I create instances of the handlers:

public class InMemoryBus : IEventDispatcher
{
    private readonly List<Type> _handlers = new List<Type>();
    private readonly Func<Type, object> activator;

    /// <param name="activator">Used to create instance of message handlers</param>
    public InMemoryBus(Func<Type, object> activator)
    {
        this.activator = activator;
    }

    public void Dispatch(DomainEvent @event)
    {
        Type messageType = message.GetType();
        var openInterface = typeof(IHandleMessages<>);
        var closedInterface = openInterface.MakeGenericType(messageType);

        var handlersToNotify = from h in _handlers
                               where closedInterface.GetTypeInfo().IsAssignableFrom(h.GetTypeInfo())
                               select h;
        foreach (Type h in _handlersToNotify)
        {
            //this is the tricky part
            var handlerInstance = activator(h);
            closedInterface.GetTypeInfo()
                .GetMethod(nameof(IHandleMessages<T>.Handle))
                .Invoke(handlerInstance, new[] { message });
        }
    }

    public void RegisterHandler(Type type) => _handlers.Add(type);
}
like image 859
Liero Avatar asked Aug 18 '17 11:08

Liero


2 Answers

https://stackoverflow.com/a/45756192/45091 is the right way to do things.

For example, how does ASP.NET Core creates controller instances?

If you don't want to register the services you can use what we refer to as "type activation". There's a type called ActivatorUtilities that has the helper method you want. You call ActivatorUtilities.CreateInstance(serviceProvider, type) and it will activate that type with the specified service provider.

like image 104
davidfowl Avatar answered Oct 10 '22 20:10

davidfowl


Given

public class MyClass {
    public MyClass(ISomeService someService) { 
        //...
    }
}

You need to build the entire object graph so that the provider knows what it can create

var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<ISomeService, SomeService>()
serviceCollection.AddTransient<MyClass>();

IServiceProvider provider = serviceCollection.BuildServiceProvider();

MyClass instance = provider.GetService<MyClass>();

When provider.GetService<MyClass>() is called the provider will initialize MyCLass, resolving all dependencies in the process.

Reference Introduction to Dependency Injection in ASP.NET Core

like image 12
Nkosi Avatar answered Oct 10 '22 21:10

Nkosi