How to use Func in built-in dependency injection

前端 未结 6 697
梦毁少年i
梦毁少年i 2021-02-14 11:17

Using asp.net 5 I\'d like my controller to be injected with a Funcinstead of T

For example:



        
相关标签:
6条回答
  • 2021-02-14 11:45

    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.

    0 讨论(0)
  • 2021-02-14 11:46

    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>()
    
    0 讨论(0)
  • 2021-02-14 11:49

    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.

    0 讨论(0)
  • 2021-02-14 11:50

    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.

    0 讨论(0)
  • 2021-02-14 11:52

    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.

    0 讨论(0)
  • 2021-02-14 11:53

    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);
    
    0 讨论(0)
提交回复
热议问题