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

7

27

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?

Creamer answered 2/3, 2016 at 0:6 Comment(5)
Check out Simple Injector ;-)Joliejoliet
Thanks. I use simple injector in other app and that's why I miss it with the default DI :)Creamer
@LuisFilipe Do you know whether this is now possible? A year has gone by, with lots of changes.Punctuation
I changed it to SimpleInjector, so i do not know.Creamer
To request this feature to be added to please vote on github.com/aspnet/Extensions/issues/487Virulence
A
5

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.

Aerobic answered 2/3, 2016 at 0:31 Comment(3)
Do you know whether this is now possible? A year has gone by, with lots of changes.Punctuation
I'm not sure, I'm going to have to give it a try and see!Aerobic
I've tried and failed. But some on here and the github repo say they managed with services.AddScoped<Func<TFoo>>(sp => sp.GetRequiredService<TFoo>); but that doesn't work for me. I have a feeling this is simply not possible, but there is no documentation one way or the other.Punctuation
C
32

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.

Compel answered 3/5, 2017 at 14:43 Comment(1)
This should be the top answer. You can also shorten it like this: services.AddSingleton<Func<IUnityOfWork>>(x => () => x.GetService<IUnitOfWork>());Harrisonharrod
A
8

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);
Ar answered 28/12, 2018 at 22:31 Comment(2)
I don't think it responds to the question. What is being asked is to have some services to depend on the factories themselves.Shaky
Looking at what the question says, I think it is what they're asking. They say they want to inject a function that returns something. If your class depends on the function the container will resolve and inject it.Ar
A
5

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.

Aerobic answered 2/3, 2016 at 0:31 Comment(3)
Do you know whether this is now possible? A year has gone by, with lots of changes.Punctuation
I'm not sure, I'm going to have to give it a try and see!Aerobic
I've tried and failed. But some on here and the github repo say they managed with services.AddScoped<Func<TFoo>>(sp => sp.GetRequiredService<TFoo>); but that doesn't work for me. I have a feeling this is simply not possible, but there is no documentation one way or the other.Punctuation
I
5

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>()
Integration answered 9/8, 2019 at 12:47 Comment(2)
Is AddTransient necessary here?Portia
@EvgeniNabokov The func is a factory and should create a new instance of T each time you request it. So yes, it is necessary for that.Integration
T
4

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.

Telefilm answered 2/1, 2019 at 15:4 Comment(0)
C
2

There are a few options available to you, the first is you can switch over to use the incredible Lamar (with it's ASP.NET Core integration).

For the most part, switching to Lamar is a few lines of code, and you'll be able to resolve Func<> and Lazy<> all day long.

I've been using it at scale for a while on a large microservices based platform and we're completely happy with it *.

If you don't want to move over to Lamar, you can use this for resolving Lazy<> (sorry, I've tried and tried, and I can't get it to work with Func<>:

// Add to your Service Collection.
services.AddTransient(typeof(Lazy<>), typeof(LazyServiceFactory<>));

class LazyServiceFactory<T> : Lazy<T>
{
    public LazyServiceFactory(IServiceProvider serviceProvider)
        : base(() => serviceProvider.GetRequiredService<T>())
    {
    }
}

And just for completeness, here's a test too.

// And some tests...
[TestMethod]
[DataTestMethod]
[DataRow(ServiceLifetime.Transient)]
[DataRow(ServiceLifetime.Scoped)]
[DataRow(ServiceLifetime.Singleton)]
public void Resolve_GivenLazyilyRegisteredService_CanResolve(ServiceLifetime serviceLifetime)
{
    // Arrange
    IServiceProvider serviceProvider = CreateServiceProvider(serviceLifetime);
    using IServiceScope scope = serviceProvider.CreateScope();

    // Act
    Func<Lazy<ClassHello>> result = () => scope.ServiceProvider.GetRequiredService<Lazy<ClassHello>>();

    // Assert
    result
        .Should()
        .NotThrow()
        .And
        .Subject()
        .Value
        .Should()
        .NotBeNull();
}

static IServiceProvider CreateServiceProvider(ServiceLifetime serviceLifetime)
{
    IServiceCollection services = new ServiceCollection();

    services.Add(new ServiceDescriptor(typeof(Lazy<>), typeof(LazyServiceFactory<>), serviceLifetime));

    services.Add(new ServiceDescriptor(typeof(ClassHello), typeof(ClassHello), serviceLifetime));

    return services.BuildServiceProvider(true);
}

I've not put this through it's paces as I use Lamar pretty much exclusivly now, but this has come in handy for smaller/ disposable projects.

* My only minor issue is that it doesn't support IAsyncDisposable yet.

Cater answered 10/4, 2020 at 8:16 Comment(0)
H
0

I have solution below

    public static IServiceCollection WithFunc<TService>(this IServiceCollection serviceCollection) where TService : class {
        var serviceType = typeof(TService);
        var serviceDescriptor = serviceCollection.LastOrDefault(x => x.ServiceType == serviceType);
        Debug.Assert(serviceDescriptor != null);
        serviceCollection.Add(ServiceDescriptor.Describe(typeof(Func<TService>),
            scope => new Func<TService>(scope.GetRequiredService<TService>),
            serviceDescriptor.Lifetime));
        return serviceCollection;
    }

usage

[Fact]
void with_func() {
    var services = new ServiceCollection()
        .AddTransient<IFoo, Foo>().WithFunc<IFoo>()
        .BuildServiceProvider();
    var fooFunc = services.GetRequiredService<Func<IFoo>>();
    Assert.NotNull(fooFunc);
}

more detail in gist https://gist.github.com/leoninew/d2f174fe1422e453c60fb78e69342310

Hid answered 18/12, 2021 at 14:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.