Runtime: Support implicit value factory / Func<> generic factories in container

Created on 12 Nov 2018  路  8Comments  路  Source: dotnet/runtime

Is your feature request related to a problem? Please describe.

public class MyController : Controller
{
    public MyController(Func<MyItem> itemFactory)
    {
        Item = itemFactory.Invoke();

This should work out of box. However it only works if you manually register this dependecy ala

c# services.AddSingleton<Func<MyItem>>(provider => provider.GetService<MyItem>);

Describe the solution you'd like

To understand Func<> as a constructor argument means attempt to use the provider to create the dependency wrapped in a factory provider.

Describe alternatives you've considered

Manually creating the factories. It is not possible to create a generic factory due dotnet/extensions#478 not being implemented yet.

Additional context

https://stackoverflow.com/questions/35736070/how-to-use-funct-in-built-in-dependency-injection

This feature has been wanted for over 2 years.

area-Extensions-DependencyInjection feature request

All 8 comments

Per aspnet/DependencyInjection#474 and aspnet/AspNetCore#3036, it鈥檚 unlikely that M.E.DI will support open generic type factories any time soon, so you cannot implement this directly on top of the DI container. I would also say that adding support for Func<T> explicitly into the DI container would be a bit weird, and it would certainly conflict with explicit function dependency registrations.

Instead, I would suggest you to create your own service resolver abstraction on top. Something like this:

```c#
public interface IServiceResolver
{
T Get();
}
public class ServiceResolver : IServiceResolver
{
private readonly IServiceProvider _serviceProvider;
public ServiceResolver(IServiceProvider provider) => _serviceProvider = provider;
public T Get() => _serviceProvider.GetService();
}

Example usage:

```c#
var services = new ServiceCollection();

// add generic service factory
services.AddTransient(typeof(IServiceResolver<>), typeof(ServiceResolver<>));

// add some dependency
services.AddTransient<FooService>();

var provider = services.BuildServiceProvider();

// get the resolver (of course also works via constructor injection)
var fooServiceFactory = provider.GetService<IServiceResolver<FooService>>();

// call `Get()` to actually resolve the service
var fooService = fooServiceFactory.Get();

@poke implicit lazy factory support has been an IOC paradigm _in c#_ for over half a decade.

It's absurd that it's not supported. Even if it's not implicitly enabled, it's borderline criminal the lack of proper support to implement it yourself due to the poorly designed service provider interface.

I don't see why this is a problem to implement for open generics. Surely it should be possible to register something like the following (which sort-of uses the solution from @poke, but requires an extra step if the T of the wrapped service is an open generic type):

public static ServiceDescriptor UnwrappingServiceDescriptor(ServiceDescriptor descriptor)
{
    return ServiceDescriptor.Describe(
        descriptor.ServiceType,
        sp => sp.GetRequiredService(typeof(WrappedService<>).MakeGenericType(descriptor.ServiceType)).Service,
        descriptor.Lifetime
    );
}

var wrappedDummyService = ServiceDescriptor.Describe(
    typeof(IMyOpenGenericService<>),
    typeof(MyOpenGenericImplementation<>),
    ServiceLifetime.Transient
);

var services = new ServiceCollection();
services.TryAdd(UnwrappingServiceDescriptor(wrappedDummyService));
// ... Other required registrations (that are registered using the WrappedService interface) ...

var provider = services.BuildServiceProvider();

var unwrappedService = provider.GetService<IMyOpenGenericService<int>>();
unwrappedService.Should().NotBeNull();

Something like this is a potential way to enable segregated service registrations (client1 services cannot depend on client2 registrations (unless explicitly using the WrappedService interface directly), but global services can depend on either depending on which client context they are using. Of course the actual implementation is a bit more complicated than this, but this is just a simplified example.

@davidfowl @pakrym Do we expect we'll do this in 3.0?

Not for 3.0. We need more design to allow new features that are not in container specs.

Yes, not in 3.0. I would move it to a future milestone instead of Discussions @muratg

If what you need is Lazy<T>, then that can easily be done by registering an open generic of Lazy<>, like this SO answer: https://stackoverflow.com/a/45775657/304986

services.AddTransient(typeof(Lazy<>), typeof(Lazier<>));

internal class Lazier<T> : Lazy<T> where T : class
{
    public Lazier(IServiceProvider provider)
        : base(() => provider.GetRequiredService<T>())
    {
    }
}

The lack of this is especially a problem with multiple service registrations. The alternative way at this moment to get hold of a factory func is really just Func<T> factory = () => serviceProvider.GetService<T>(). But this doesn't work at all when dealing with multiple registrations, and there's no way to get a service at a specific slot/index either.

If something in this spirit would be implemented I would ask not only Func<T> but also IEnumerable<Func<T>> be special cased, similarly to how IEnumerable<T> is special.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

btecu picture btecu  路  3Comments

jzabroski picture jzabroski  路  3Comments

iCodeWebApps picture iCodeWebApps  路  3Comments

EgorBo picture EgorBo  路  3Comments

matty-hall picture matty-hall  路  3Comments