What are the costs and possible side effects of calling BuildServiceProvider() in ConfigureServices()
Each service provider has its own cache. Building multiple service provider instances can, therefore, lead to a problem called Torn Lifestyles:
When multiple [registrations] with the same lifestyle map to the same component, the component is said to have a torn lifestyle. The component is considered torn because each [registration] will have its own cache of the given component, which can potentially result in multiple instances of the component within a single scope. When the registrations are torn the application may be wired incorrectly which could lead to unexpected behavior.
This means that each service provider will have its own cache of singleton instances. Building multiple service providers from the same source (i.e. from the same service collection) will cause a singleton instance to be created more than once—this breaks the guarantee that there is at most one instance for a given singleton registration.
But there are other, just as subtle bugs that can appear. For instance, when resolving object graphs that contain scoped dependencies. Building a separate temporary service provider for the creation of an object graph that is stored in the next container might cause those scoped dependencies to be kept alive for the duration of the application. This problem is commonly referred to as Captive Dependencies.
With containers like Autofac or DryIoc this was no big deal since you could register the service on one line and on the next line you could immediately resolve it.
This statement implies that there are no problems with trying to resolve instances from the container while the registration phase is still in progress. This, however, is incorrect—altering the container by adding new registrations to it after you already resolved instances is a dangerous practice—it can lead to all sorts of hard to track bugs, independently of the used DI Container.
It is especially because of those hard to track bugs that DI Containers, such as Autofac, Simple Injector, and Microsoft.Extensions.DependencyInjection (MS.DI) prevent you from doing this in the first place. Autofac and MS.DI do this by having registrations made in a 'container builder' (AutoFac's ContainerBuilder
and MS.DI's ServiceCollection
). Simple Injector, on the other hand, does not make this split. Instead, it locks the container from any modifications after the first instance is resolved. The effect, however, is similar; it prevents you from adding registrations after you resolve.
The Simple Injector documentation actually contains some decent explanation on why this Register-Resolve-Register pattern is problematic:
Imagine the scenario where you want to replace some
FileLogger
component for a different implementation with the sameILogger
interface. If there’s a component that directly or indirectly depends onILogger
, replacing theILogger
implementation might not work as you would expect. If the consuming component is registered as singleton, for example, the container should guarantee that only one instance of this component will be created. When you are allowed to change the implementation ofILogger
after a singleton instance already holds a reference to the “old” registered implementation the container has two choices—neither of which are correct:
- Return the cached instance of the consuming component that has a reference to the “wrong”
ILogger
implementation.- Create and cache a new instance of that component and, in doing so, break the promise of the type being registered as a singleton and the guarantee that the container will always return the same instance.
For this same reason you see that the ASP.NET Core Startup
class defines two separate phases:
- The “Add” phase (the
ConfigureServices
method), where you add registrations to the “container builder” (a.k.a.IServiceCollection
) - The “Use” phase (the
Configure
method), where you state you want to use MVC by setting up routes. During this phase, theIServiceCollection
has been turned into aIServiceProvider
and those services can even be method injected into theConfigure
method.
The general solution, therefore, is to postpone resolving services (like your IStringLocalizerFactory
) until the “Use” phase, and with it postpone the final configuration of things that depend on the resolving of services.
This, unfortunately, seems to cause a chicken or the egg causality dilemma when it comes to configuring the ModelBindingMessageProvider
because:
- Configuring the
ModelBindingMessageProvider
requires the use of theMvcOptions
class. - The
MvcOptions
class is only available during the “Add” (ConfigureServices
) phase. - During the “Add” phase there is no access to an
IStringLocalizerFactory
and no access to a container or service provider and resolving it can’t be postponed by creating such value using aLazy<IStringLocalizerFactory>
. - During the “Use” phase,
IStringLocalizerFactory
is available, but at that point, there is noMvcOptions
any longer that you can use to configure theModelBindingMessageProvider
.
The only way around this impasse is by using private fields inside the Startup
class and use them in the closure of AddOptions
. For instance:
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization();
services.AddMvc(options =>
{
options.ModelBindingMessageProvider.SetValueIsInvalidAccessor(
_ => this.localizer["The value '{0}' is invalid."]);
});
}
private IStringLocalizer localizer;
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
this.localizer = app.ApplicationServices
.GetRequiredService<IStringLocalizerFactory>()
.Create("ModelBindingMessages", "AspNetCoreLocalizationSample");
}
The downside of this solution is that this causes Temporal Coupling, which is a code smell of its own.
You could, of course, argue that this a ugly workaround for a problem that might not even exist when dealing with IStringLocalizerFactory
; creating a temporary service provider to resolve the localization factory might work just fine in that particular case. Thing is, however, that it is actually pretty hard to analyze whether or not you’re going to run in trouble. For instance:
- Even though
ResourceManagerStringLocalizerFactory
, which is the default localizer factory, does not contain any state, it does takes a dependency on other services, namelyIOptions<LocalizationOptions>
andILoggerFactory
. Both of which are configured as singletons. - The default
ILoggerFactory
implementation (i.e.LoggerFactory
), is created by the service provider, andILoggerProvider
instances can be added afterwards to that factory. What will happen if your secondResourceManagerStringLocalizerFactory
depends on its ownILoggerFactory
implementation? Will that work out correctly? - Same holds for
IOptions<T>
—implemented byOptionsManager<T>
. It is a singleton, butOptionsManager<T>
itself depends onIOptionsFactory<T>
and contains its own private cache. What will happen if there is a secondOptionsManager<T>
for a particularT
? And could that change in the future? - What if
ResourceManagerStringLocalizerFactory
is replaced with a different implementation? This is a not-unlikely scenario. What would the dependency graph than look like and would that cause trouble if lifestyles get torn? - In general, even if you would be able to conclude that works just fine right now, are you sure that this will hold in any future version of ASP.NET Core? It is not that hard to imagine that an update to a future version of ASP.NET Core will break your application in utterly subtle and weird ways because you implicitly depend on this specific behavior. Those bugs will be pretty hard to track down.
Unfortunately, when it comes to configuring the ModelBindingMessageProvider
, there seems no easy way out. This is IMO a design flaw in the ASP.NET Core MVC. Hopefully Microsoft will fix this in a future release.
Calling 'BuildServiceProvider' from application code results in copy of Singleton warning. How do I avoid this?
If called BuildServiceProvider() in ConfigureServices, shown warning "Calling 'BuildServiceProvider' from application code results in a additional copy of Singleton services being created"
I solved this issue:
Create another function (which passed argument is IServiceCollection) and into the function call BuildServiceProvider()
For example your code it should be:
public void ConfigureServices(IServiceCollection services)
{
if (HostingEnvironment.EnvironmentName == "Local")
{
services.AddHealthChecksUI()
.AddHealthChecks()
.AddCheck<TestWebApiControllerHealthCheck>("HomePageHealthCheck")
.AddCheck<DatabaseHealthCheck>("DatabaseHealthCheck");
}
services.Configure<PwdrsSettings>(Configuration.GetSection("MySettings"));
services.AddDbContext<PwdrsContext>(o => o.UseSqlServer(Configuration.GetConnectionString("PwdrsConnectionRoot")));
services.AddMvc(o =>
{
o.Filters.Add<CustomExceptionFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", b => b
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddSwaggerDocument();
services.AddHttpContextAccessor();
services.AddAutoMapper(typeof(ObjectMapperProfile));
services.AddTransient<IEmailSender, EmailSender>();
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddScoped(typeof(IAsyncRepository<>), typeof(Repository<>));
services.AddScoped<IRfReportTypeRepository, RfReportTypeRepository>();
services.AddScoped<IRfReportRepository, RfReportRepository>();
services.AddScoped<IRfReportLookupsService, RfReportLookupsService>();
services.AddScoped<IRfReportService, RfReportService>();
RegisterSerilogLogger logger = CreateRegisterSerilogLogger(services);
}
private RegisterSerilogLogger CreateRegisterSerilogLogger(IServiceCollection services){
services.Configure<RAFLogging>(Configuration.GetSection("RAFLogging"));
ServiceProvider serviceProvider = services.BuildServiceProvider(); //No warning here ))
IOptions<RAFLogging> RAFLogger = serviceProvider.GetRequiredService<IOptions<RAFLogging>>();
RegisterSerilogLogger logger = new RegisterSerilogLogger(RAFLogger);
return logger;
}
Or use ApplicationServices of IApplicationBuilder. ApplicationSerivces's type is IServiceProvider.
I mention this solution is only for remove warning.
Calling BuildServiceProvider creates a second container, which can create torn singletons and cause references to object graphs across multiple containers.
UPDATED 24.01.2021
I read Adam Freeman's Pro ASP.NET Core 3 8th
book. Adam Freeman used app.ApplicationServices
instead of services.BuildServiceProvider()
in page 157 for this purpose, that app is Configure method's parameter that this method located in Startup.cs
I thinks correct version is to use ApplicationServices property of app, which app is IApplicationBuilder in Configure method's parameter. ApplicationServices's type is IServiceProvider.
Adam Freeman's Pro ASP.NET Core 3 8th book : Pro ASP.NET Core 3
Adam Freeman's example project: SportStore project's Startup.cs, SportStore project's SeedData.cs
Microsoft's recommendations about DI : Dependency injection in ASP.NET Core
Similar questions' answers in Stackoverflow: https://stackoverflow.com/a/56058498/8810311, https://stackoverflow.com/a/56278027/8810311
Intermediate Service Provider Drawbacks
What are the drawbacks to making this "Intermediate Service Provider"?
You could end up with having multiple containers in your application. One of the main drawbacks here is your singleton services can exist in multiple containers. i.e. multiple instances of a singleton service.
Since ASP.NET Core 3.0, if you call BuildServiceProvider
in configure methods, you see warning:
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'.
Comment from @devNull gives more details if you also want to read.
Will my wso2Actions object still be managed by the dependency injection framework?
Yes.
(As if it had been injected later on?)
No. If you inject wso2Actions
later on, for example, into your controller, the container used to resolve the instance is the container used by ASP.NET Core. Whereas in your ConfigureServices
, the instance is resolved using a transient container built by your code.
Note that both containers are independent and may contain different service registrations.
Would I be better off just "newing" up the object myself?
If you are willing to use new
, then why not.
If you are willing to use dependency injection, there are multiple choices depending on what wso2Actions
is for:
Move your code to Configure
Obviously you could move your code to Configure
which is invoked after the container is built:
var wso2Actions = app.ApplicationServices.GetService<Wso2Actions>();
Use options pattern
Read this.
services.AddOptions<MyOptions>("optionalName")
.Configure<Service1, Service2, Service3, Service4, Service5>(
(o, s, s2, s3, s4, s5) =>
o.Property = DoSomethingWith(s, s2, s3, s4, s5));
Accessing a service before BuildServiceProvider
Foreword:
This seems a problematic request. You need configuration to configure services, but you need to configure services to get configuration. A chicken and egg problem.
What to do:
Create an instance of configuration use it to configure your services and also add it to your service collection
var configStoreService = new ConfigurationStore(location, sku);
services.AddSingleton(configStoreService);
var configuration = new ConfigurationBuilder()
.AddInMemoryConfiguration(configStoreService)
.Build();
services.AddOptions();
services.Configure<Temp>(configuration.GetSection(typeof(Temp).Name));
services.RegisterOptionsType<Temp>(configuration);
_serviceProvider = services.BuildServiceProvider();
FYI:
I also have to add you can't get anything from a IServiceCollection
. You can get things from IServiceProvider
which, you get AFTER registration is done and BuildServiceProvider
called. So before that you can't access items in the collection.
Registering IHostedService on a different service provider
All IHostedService
implementations are executed by the IWebHost
that holds on to the IServiceProvider
which is passed in to ConfigureServices
.
The internal
WebHost
object holds on to an internal
HostedServiceExecutor
that is responsible for instantiating and running IHostedService
objects.
Since you are running outside of the WebHost
, you are the one responsible for instantiating, starting and stopping the services.
What you would have to do is emulate what HostedServiceExecutor
does: Instantiate then iterate all services that implement IHostedService
and finally call StartAsync
and StopAsync
For example:
var serviceCollection = new ServiceCollection();
serviceCollection.AddHostedService<TestHostedService>();
var serviceProvider = serviceCollection.BuildServiceProvider();
// this will instantiate the services
var myHostedServices = serviceProvider.GetService<IEnumerable<IHostedService>>();
// or: var myHostedServices = serviceProvider.GetServices<IHostedService>();
// start the services up
foreach (var hostedService in myHostedServices)
{
await hostedService.StartAsync(token);
}
// ...
// Shutting down: stop the services
foreach (var hostedService in myHostedServices)
{
await hostedService.StopAsync(token);
}
This is extremely simplified code. No exception handling is done. This is just to give you the gist of how you'd do it. You should really study HostedServiceExecutor
's code.
ASP.NET core call async init on singleton service
What is happening is at the time of startup, an instance of MyService is created and InitAsync() is called on it. Then when I called the controller class, another instance of MyService is created which is then reused for consequent calls.
When you call BuildServiceProvider()
, you create a separate instance of IServiceProvider
, which creates its own singleton instance of IService
. The IServiceProvider
that gets used when resolving the IService
that's provided for MyController
is different to the one you created yourself and so the IService
itself is also different (and uninitialised).
What I need is to initialize only 1 instance, called InitAsync() on it in startup and have it be reused by controllers as well.
Rather than attempting to resolve and initialise IService
inside of Startup.ConfigureServices
, you can do so in Program.Main
. This allows for two things:
- Using the same instance of
IService
for initialisation and later use. await
ing the call toInitAsync
, which is currently fire-and-forget in the approach you've shown.
Here's an example of how Program.Main
might look:
public static async Task Main(string[] args)
{
var webHost = CreateWebHostBuilder(args).Build();
await webHost.Services.GetRequiredService<IService>().InitAsync();
webHost.Run();
// await webHost.RunAsync();
}
This uses async Main
to enable use of await
, builds the IWebHost
and uses its IServiceProvider
to resolve and initialise IService
. The code also shows how you can use await
with RunAsync
if you prefer, now that the method is async
.
Related Topics
How to Change the Color of Winform Datagridview Header
Can Razor Class Library Pack Static Files (Js, CSS etc) Too
What's the Difference Between Double Quotes and Single Quote in C#
Performance of Linq Any VS Firstordefault != Null
Is a Reference Assignment Threadsafe
"The Semaphore Timeout Period Has Expired" Error for Usb Connection
How to Have a Loop in a Windows Service Without Using the Timer
Why C# Won't Allow Field Initializer with Non-Static Fields
Wcf Httptransport: Streamed VS Buffered Transfermode
C# String Replace with Dictionary
How to Format Timespan in Xaml
Easiest Way to Parse "Querystring" Formatted Data
How Should You Diagnose the Error Sehexception - External Component Has Thrown an Exception
Entity Framework Ef.Functions.Like VS String.Contains
Is There Any Performance Difference Between ++I and I++ in C#