5

After registering instances on my IServiceCollection, I need to register an IAutomapperProvider that depends on an IAssemblyProvider that was registered before this method call.

public static IServiceCollection RegisterAutomapperConfiguration(this IServiceCollection container, ServiceLifetime lifeTime = ServiceLifetime.Scoped)
{
    // creating the provider to get the IAssemblyProvider for my IAutomapperProvider
    var prov = container.BuildServiceProvider();
    var assemblyProvider = prov.GetService<IAssemblyProvider>();
    container.Register<IAutomapperProvider>(aProv => new AutomapperProvider(assemblyProvider), lifeTime);
    var autoMapperProvider = prov.GetService<IAutomapperProvider>();
    var mapperConfig = autoMapperProvider.GetMapperConfiguration();
    ...
}

If right after the call of container.Register<IAutomapperProvider>(aProv => new AutomapperProvider(assemblyProvider), lifeTime); I don't call BuildServiceProvider again, then I would not get the IAutomapperProvider I registered before.

public static IServiceCollection RegisterAutomapperConfiguration(this IServiceCollection container, ServiceLifetime lifeTime = ServiceLifetime.Scoped)
{
    // creating the provider to get the IAssemblyProvider for my IAutomapperProvider
    var prov = container.BuildServiceProvider();
    var assemblyProvider = prov.GetService<IAssemblyProvider>();
    container.Register<IAutomapperProvider>(aProv => new AutomapperProvider(assemblyProvider), lifeTime);
    prov = container.BuildServiceProvider();
    var autoMapperProvider = prov.GetService<IAutomapperProvider>();
    var mapperConfig = autoMapperProvider.GetMapperConfiguration();
    ...
}

On the AspNetCore code when you call BuildServiceProvider extension method they use the same IServiceCollection that can change over time adding more elements, at the end you are pointing to the same reference.

public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
{
    return BuildServiceProvider(services, ServiceProviderOptions.Default);
}

Then why do I need to call it again to get a new instance that knows how to resolve my Service?

To avoid confusions, the Register method is an extension I created but internally it calls the AddSingleton or Add...

public static IServiceCollection Register<TService>(this IServiceCollection container, Func<IServiceProvider, TService> implementationFactory, ServiceLifetime lifeTime)
            where TService : class
{
    if (container == null)
        throw new ArgumentNullException(nameof(container));
    if (implementationFactory == null)
        throw new ArgumentNullException(nameof(implementationFactory));

    switch (lifeTime)
    {
        case ServiceLifetime.Scoped:
            container.AddScoped(typeof(TService), (Func<IServiceProvider, object>)implementationFactory);
            break;
        case ServiceLifetime.Transient:
            container.AddTransient(typeof(TService), (Func<IServiceProvider, object>)implementationFactory);
            break;
        default:// ServiceLifetime.Singleton
            container.AddSingleton(typeof(TService), (Func<IServiceProvider, object>)implementationFactory);
            break;
    }
    return container;
}
Palle Due
  • 5,929
  • 4
  • 17
  • 32
Heinrich
  • 806
  • 2
  • 11
  • 26
  • This is an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). You'll need to clarify a few things. What is this `Register`? Is that some external 3rd party extension? Clarify what it is exactly your are trying to achieve. – Nkosi Jul 11 '18 at 15:19
  • @Nkosi you are right, I updated the question – Heinrich Jul 11 '18 at 15:26

2 Answers2

4

BuildServiceProvider does not have to be invoked so often.

That is what the implementation factory delegates are for. They provide access to a service provider for that purpose.

It is still uncertain why you have to build the provider in the first instance but based on currently provided code use the service provider within the implementation factory delegate when registering your provider.

public static IServiceCollection RegisterAutomapperConfiguration(this IServiceCollection services, 
    ServiceLifetime lifeTime = ServiceLifetime.Scoped) {

    services.Register<IAutomapperProvider>(p => //<-- p is IServiceProvider
         //use the provider to get the IAssemblyProvider for my IAutomapperProvider
        new AutomapperProvider(p.GetService<IAssemblyProvider>()), lifeTime);
    prov = services.BuildServiceProvider();
    var autoMapperProvider = prov.GetService<IAutomapperProvider>();
    var mapperConfig = autoMapperProvider.GetMapperConfiguration();
    //...
}

When BuildServiceProvider is invoked, the created service provider will only be aware of types that were in the collection when it was built. It will not be aware of any additional types, which is why is it usually done at the end of registering all types.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • that is the same problem I exposed – Heinrich Jul 31 '18 at 13:33
  • 1
    @NKosi I have no idea why anyone would downvote your answer since it is the right one. – John Zabroski Jan 28 '20 at 21:49
  • *"When BuildServiceProvider is invoked, the created service provider will only be aware of types that were in the collection when it was built. It will not be aware of any additional types, which is why is it usually done at the end of registering all types."* Do you still think that, given the interesting problem [explained here](https://stackoverflow.com/a/56058498/9971404)? Specifically, is it even safe to call `BuildServiceProvider` more than once? – lonix Apr 07 '22 at 13:36
3

I noticed this as well. I need to resolve a service in my class, but I may have let the consumer of my class register additional services beforehand. I have already called BuildServiceProvider initially.

The problem is that BuildServiceProvider is being called as an extension method of IServiceCollection, and doing so creates a new ServiceProvider

IServiceCollection is actually IEnumerable<ServiceDescriptor> and so whatever the current collection is at the time the ServiceProvider is created is what consumers will see when they access that instance of the ServiceProvider.

So you're not really accessing the same instance of the IServiceCollection.

https://github.com/aspnet/DependencyInjection/blob/master/src/DI/ServiceCollectionContainerBuilderExtensions.cs

EnderWiggin
  • 525
  • 6
  • 7