Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Func<T> in built-in dependency injection

Using asp.net 5 I'd like my controller to be injected with a Func<T>instead of T

For example:

public HomeController(Func<Interfaces.IUnitOfWork> uow)

Instead of

public HomeController(Interfaces.IUnitOfWork uow)

Is it possible with the built-in DI or am I forced to move to an external DI?

like image 696
Luis Filipe Avatar asked Mar 02 '16 00:03

Luis Filipe


People also ask

How do you add dependency injection?

Dependency Injection is done by supplying the DEPENDENCY through the class's constructor when creating the instance of that class. The injected component can be used anywhere within the class. Recommended to use when the injected dependency, you are using across the class methods.

Can you inject dependency through private constructor?

To answer your question, there is no way. by definition, private constructors are inaccessible by other classes.

What is builder services C#?

A collection of services for the application to compose. This is useful for adding user provided or framework provided services. C# Copy. public Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; }


5 Answers

Func<T> does not get registered or resolved by default but there is nothing stopping you from registering it yourself.

e.g.

services.AddSingleton(provider => 
  new Func<IUnitOfWork>(() => provider.GetService<IUnitOfWork>()));

Note that you will also need to register IUnitOfWork itself in the usual way.

like image 96
Paul Hiles Avatar answered Oct 21 '22 04:10

Paul Hiles


You can register a Func<T> or a delegate with a ServiceCollection. I recommend a delegate because it allows you to distinguish between different methods with identical signatures.

Here's an example.

public interface IThingINeed {}
public class ThingINeed : IThingINeed { }

public delegate IThingINeed ThingINeedFactory();

public class DelegateRegistrationTests
{
    [Test]
    public void RegisterDelegateFromDependency()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddTransient<IThingINeed, ThingINeed>();
        serviceCollection.AddTransient<ThingINeedFactory>(
            provider => provider.GetService<IThingINeed>);
        var serviceProvider = serviceCollection.BuildServiceProvider();
        var factoryMethod = serviceProvider.GetService<ThingINeedFactory>();
        var output = factoryMethod();
        Assert.IsInstanceOf<ThingINeed>(output);
    }
}

This almost looks like a service locator because the function we're resolving is actually IServiceCollection.GetService<ThingINeedFactory>(). But that's hidden in the composition root. A class that injects this delegate depends on the delegate, not on the implementation.

You can use the same approach if the method you want to return belongs to a class that the container must resolve.

public interface IThingINeed
{
    string SayHello();
}

public class ThingINeed : IThingINeed
{
    private readonly string _greeting;

    public ThingINeed(string greeting)
    {
        _greeting = greeting;
    }

    public string SayHello() => _greeting;
}

public class ThingINeedFactory
{
    public IThingINeed Create(string input) => new ThingINeed(input);
}

public delegate IThingINeed ThingINeedFactoryMethod(string input);

public class DelegateRegistrationTests
{
    [Test]
    public void RegisterDelegateFromDependency()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<IThingINeed, ThingINeed>();
        serviceCollection.AddSingleton<ThingINeedFactory>();
        serviceCollection.AddSingleton<ThingINeedFactoryMethod>(provider => 
            provider.GetService<ThingINeedFactory>().Create);
        var serviceProvider = serviceCollection.BuildServiceProvider();
        var factoryMethod = serviceProvider.GetService<ThingINeedFactoryMethod>();
        var created = factoryMethod("abc");
        var greeting = created.SayHello();
        Assert.AreEqual("abc", greeting);
    }
}

Here's an extension method to (maybe) make it a little bit easier:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection RegisterDelegate<TSource, TDelegate>(
        this IServiceCollection serviceCollection,
        Func<TSource, TDelegate> getDelegateFromSource) 
            where TDelegate : class 
    {
        return serviceCollection.AddSingleton(provider =>
            getDelegateFromSource(provider.GetService<TSource>()));
    }
}

serviceCollection
    .RegisterDelegate<ThingINeedFactory, ThingINeedFactoryMethod>(
        factory => factory.Create);
like image 37
Scott Hannen Avatar answered Oct 21 '22 04:10

Scott Hannen


While there is no built in Func building support in the default dependency injection for .net core we can build an extension method to add in all the missing funcs. We just need to make sure we call it at the end of registration.

public static class ServiceCollectionExtensions
{
    private static MethodInfo GetServiceMethod;

    static ServiceCollectionExtensions()
    {
        Func<IServiceProvider, object> getServiceMethod = ServiceProviderServiceExtensions.GetService<object>;
        GetServiceMethod = getServiceMethod.Method.GetGenericMethodDefinition();
    }

    /// <summary>
    /// Registers all Funcs in constructors to the ServiceCollection - important to call after all registrations
    /// </summary>
    /// <param name="collection"></param>
    /// <returns></returns>
    public static IServiceCollection AddFactories(this IServiceCollection collection)
    {

        // Get a list of all Funcs used in constructors of regigstered types
        var funcTypes = new HashSet<Type>(collection.Where(x => x.ImplementationType != null)
            .Select(x => x.ImplementationType)
            .SelectMany(x => x.GetConstructors(BindingFlags.Public | BindingFlags.Instance))
            .SelectMany(x => x.GetParameters())
            .Select(x => x.ParameterType)
            .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(Func<>)));

        // Get a list of already registered Func<> and remove them from the hashset
        var registeredFuncs = collection.Select(x => x.ServiceType)
            .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(Func<>));
        funcTypes.ExceptWith(registeredFuncs);

        // Each func build the factory for it
        foreach (var funcType in funcTypes)
        {
            var type = funcType.GetGenericArguments().First();
            collection.AddTransient(funcType, FuncBuilder(type));
        }

        return collection;
    }

    /// <summary>
    /// This build expression tree for a func that is equivalent to 
    ///     Func<IServiceProvider, Func<TType>> factory = serviceProvider => new Func<TType>(serviceProvider.GetService<TType>);
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    private static Func<IServiceProvider, object> FuncBuilder(Type type)
    {
        var serviceProvider = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
        var method = GetServiceMethod.MakeGenericMethod(type);
        var call = Expression.Call(method, serviceProvider);
        var returnType = typeof(Func<>).MakeGenericType(type);
        var returnFunc = Expression.Lambda(returnType, call);
        var func = Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(IServiceProvider), returnType), returnFunc, serviceProvider);
        var factory = func.Compile() as Func<IServiceProvider, object>;
        return factory;
    }
}

In AddFactories we get a list of all the concreate types that are registered then check their constructors for any Func<>. From that list remove any Func that has been registered before. Using some expressiontrees we build the needed Funcs.

The code is also over in codereview, minus the check for already registered funcs.

like image 33
CharlesNRice Avatar answered Oct 21 '22 05:10

CharlesNRice


I wrote a little extension method that registres the service and the factory (Func<T>):

public static class IServiceCollectionExtension
{
    public static IServiceCollection AddFactory<TService, TServiceImplementation>(this IServiceCollection serviceCollection) 
        where TService : class
        where TServiceImplementation : class, TService
    {
        return serviceCollection
            .AddTransient<TService, TServiceImplementation>();
            .AddSingleton<Func<TService>>(sp => sp.GetRequiredService<TService>);
    }
}

Usage:

serviceCollection
   .AddFactory<IMyInterface, MyImplementation>()
like image 23
Martin Brandl Avatar answered Oct 21 '22 05:10

Martin Brandl


As far as I'm aware deferring dependencies like this isn't possible using the current default IoC container within ASP.NET Core. I've not been able to get it working anyway!

To defer the initialisation of dependencies like this you'll need to implement an existing, more feature rich IoC container.

like image 30
Joseph Woodward Avatar answered Oct 21 '22 04:10

Joseph Woodward