Calling BuildServiceProvider from application code results in an additional copy of singleton services being created

When you try to call BuildServiceProvider(), you get the following warning:

Warning ASP0000 Calling ‘BuildServiceProvider’ from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to ‘Configure’.

There are two scenarios where you may be calling BuildServiceProvider() because you want to resolve services manually. Take a look at the scenarios and their solutions below.

Scenario 1 – You’re trying to manually resolve dependencies

You’re trying to call services.BuildServiceProvider() in order to manually resolve dependencies. Your code may look something like this:

//rest of adding services

var hostAppLifetime = builder.Services.BuildServiceProvider().GetService<IHostApplicationLifetime>();
var loggerService = new DatabaseLoggerService(hostAppLifetime);

//rest of init code
Code language: C# (cs)

Note: Before .NET 6, this is in Startup.ConfigureServices().

Solution

Instead of using BuildServiceProvider(), use the overload of AddSingleton/Transient/Scoped that has a ServiceProvider parameter. Use the passed in ServiceProvider’s GetService(). Here’s an example:

//Rest of adding services

builder.Services.AddSingleton<ILoggerService>(sp =>
{
	var hostAppLifetime = sp.GetService<IHostApplicationLifetime>();
	return new DatabaseLoggerService(hostAppLifetime);
});

//Rest of init code
Code language: C# (cs)

Note: Before .NET 6, do this in Startup.ConfigureServices().

Scenario 2 – You’re resolving a service to get a dynamic value for another service

You’re probably trying to get a dynamic value, such as from the database, to pass into another object that you’re registering. You may or may not be using the options pattern.

Your code may look something like this (if you’re using the options pattern):

//rest of adding services

builder.Services.AddSingleton<ISettingsRepository, SettingsRepository>();
builder.Services.AddSingleton<IThirdPartyService, ThirdPartyService>();

builder.Services.AddOptions<Settings>().Configure(options =>
{
	options.StartAt = services.BuildServiceProvider().GetService<ISettingsRepository>().GetStartDate();
});

//Rest of init code
Code language: C# (cs)

Note: Before .NET 6, this is in Startup.ConfigureServices().

Solution

First, the options pattern is a good way to solve the “fetch a dynamic value using a registered service” problem, so I’d suggest using it.

Instead of using BuildServiceProvider(), use the appropriate AddOptions().Configure<T>() overload, like this:

//rest of adding services

builder.Services.AddSingleton<ISettingsRepository, SettingsRepository>();
builder.Services.AddSingleton<IThirdPartyService, ThirdPartyService>();

builder.Services.AddOptions<Settings>()
	.Configure<ISettingsRepository>((options, settingsRepo) =>
	{
		options.StartAt = settingsRepo.GetStartDate();
	});

//Rest of init code
Code language: C# (cs)

Note: Before .NET 6, do this in Startup.ConfigureServices().

This Configure<T>() overload gives you access to the resolved service, so you can use it to set a value on the options object.

2 thoughts on “Calling BuildServiceProvider from application code results in an additional copy of singleton services being created”

  1. For this to work, somewhere, something, sometime in the code path must have done a BuildServiceProvider, how else can the implementation of ISettingsRepository be gotten from the registered services.

    So where, when is this done?

    Also, somewhere in the code base, it might need to resolve another object. So how can the code base do this, ie, where does it get the IServiceProvider from to get the service with. It might not be a constructor dependency.

    Reply
    • For this to work, somewhere, something, sometime in the code path must have done a BuildServiceProvider, how else can the implementation of ISettingsRepository be gotten from the registered services…. where does it get the IServiceProvider

      The ASP.NET framework builds the service provider at some point during startup. Presumably after builder.Builder() (or ConfigureServices() in older versions). This is part of the built-in dependency injection system. I tried digging into the ASP.NET source code to be able to answer your question precisely, but it’s really tough to untangle that code. I believe BuildServiceProvider() only exists as a way to get a service provider when you’re not relying on the ASP.NET startup process – such as in unit tests etc… and I think the framework builds the service provider in a different way (i.e. doesn’t use BuildServiceProvider() directly).

      All I know for sure is using BuildServiceProvider() directly in the initialization code will definitely cause problems, which is why it’s important to do the workarounds instead.

      Reply

Leave a Comment