5
\$\begingroup\$

The default .net core dependency injection doesn't support building Func<> for injection automatically, see question. I wrote an extension method to go find all the Func in register types constructors and build the Func automatically, needs to be called at end of registrations.

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<>)));

        // 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;
    }
}

I register the Funcs as Transient but I think it would have been safe to register them as singleton. Not sure which has more overhead.

For a simple test

public interface ITransient
{
    Guid Id { get; }
}

public class Transient : ITransient
{
    public Transient()
    {
        Id = Guid.NewGuid();
    }
    public Guid Id { get; }
}

public class SingleTon
{
    private readonly Func<ITransient> _transientFunc;
    public SingleTon(Func<ITransient> transientFunc)
    {
        _transientFunc = transientFunc;
    }

    public ITransient GetTransient()
    {
        return _transientFunc();
    }
}

If the func isn't working correctly I would get the same object each time back on the transient class.

static void Main(string[] args)
{
    var serviceCollection = new ServiceCollection();
    serviceCollection.AddSingleton<SingleTon>();
    serviceCollection.AddTransient<ITransient, Transient>();
    serviceCollection.AddFactories();
    var container = serviceCollection.BuildServiceProvider();

    var singleton = container.GetService<SingleTon>();
    if (object.ReferenceEquals(singleton.GetTransient(), singleton.GetTransient()))
    {
        throw new InvalidOperationException();
    }

    Console.WriteLine("Done");
    Console.ReadLine();
    container.Dispose();
}
\$\endgroup\$
  • \$\begingroup\$ Singletons should not depend on transients \$\endgroup\$ – Adrian Iftode Dec 28 '18 at 17:43
  • \$\begingroup\$ I understand that is the guideline. I also agree with that guideline but sometimes you do need to have other classes used and either you can inject a func which you can sub out during testing or you have to use the service locator pattern. \$\endgroup\$ – CharlesNRice Dec 28 '18 at 17:51
  • \$\begingroup\$ I added another answer to the question you referenced. I don't understand why the answers say that you can't register a function (although I recommend a delegate) because you can. stackoverflow.com/questions/35736070/… \$\endgroup\$ – Scott Hannen Dec 28 '18 at 22:32
  • \$\begingroup\$ It's pretty bad that it relies on the parameter name as a hardcoded string. Isn't it possible to avoid somehow? \$\endgroup\$ – Mathias Lykkegaard Lorenzen Jun 11 at 23:08
  • \$\begingroup\$ It doesn't. It's looking for func sig \$\endgroup\$ – CharlesNRice Jun 11 at 23:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.