ASP.NET Core – Beware – Singleton May Not Be Singleton

If you register a type as a singleton then you expect just 1 instance of this type in your whole application. What you may not know is that ASP.NET Core is creating 2 instances of IServiceProvider during building of the IWebHost that may lead to 2 instance of your "singleton".

In this article:

pg
Pawel Gerr is architect consultant at Thinktecture. He focuses on backends with .NET Core and knows Entity Framework inside out.
This is the case if you register a type, say MySingleton, when configuring the web host …
				
					WebHost
 .CreateDefaultBuilder()
 .UseStartup<Startup>()
 .ConfigureServices(services => services.AddSingleton<MySingleton>())
 .Build()
 .Run();
				
			

 …, e.g. so that is available in the constructor of your Startup

				
					public class Startup
{
  private readonly MySingleton _mySingletonFromHostingServiceProvider;

  public Startup(MySingleton mySingletonFromHostingServiceProvider)
  {
    _mySingletonFromHostingServiceProvider = mySingletonFromHostingServiceProvider;
  } 
  ...
}
				
			

Now, if we resolve MySingleton during normal web request we get a whole new instance instead the same instance as in constructor of the Startup class. 

				
					public void Configure(IApplicationBuilder app)
{
  app.Use((ctx, next) =>
  {
    var mySingleton = ctx.RequestServices.GetRequiredService<MySingleton>();
    
    // the comparison of 2 instances yields "false"
    var areEqual = _mySingletonFromHostingServiceProvider == mySingleton;

    Console.WriteLine($"==> {nameof(_mySingletonFromHostingServiceProvider)} == {nameof(mySingleton)}: {areEqual}");
         return next();
    });
}
				
			

 There are at least two ways to fix this problem.

Either pass an instance of MySingleton to method AddSingleton instead of passing just the type

				
					var mySingleton = new MySingleton();
WebHost
 .CreateDefaultBuilder()
 .UseStartup<Startup>()
 .ConfigureServices(services => services.AddSingleton(mySingleton))
 .Build()
 .Run();
				
			

 or by replacing the previous registration with a new one in ConfigureServices 

				
					public class Startup
{
  private readonly MySingleton _mySingletonFromHostingServiceProvider;

  public Startup(MySingleton mySingletonFromHostingServiceProvider)
  {
     _mySingletonFromHostingServiceProvider = mySingletonFromHostingServiceProvider;
  }

  public void ConfigureServices(IServiceCollection services)
  {
    services.Replace(new ServiceDescriptor(typeof(MySingleton), _mySingletonFromHostingServiceProvider));
    // alternative way 
    //services.AddSingleton(_mySingletonFromHostingServiceProvider);
 }
  ...
}
				
			

According to @davidfowl the ASP.NET team will address this problem in the future. 

PS: There is at least another one solution to fix this problem and gaining back the control over your web app but that’s for another time … 🙂

Free
Newsletter

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

EN Newsletter Anmeldung (#7)
Related Articles
Angular
SL-rund
If you previously wanted to integrate view transitions into your Angular application, this was only possible in a very cumbersome way that needed a lot of detailed knowledge about Angular internals. Now, Angular 17 introduced a feature to integrate the View Transition API with the router. In this two-part series, we will look at how to leverage the feature for route transitions and how we could use it for single-page animations.
15.04.2024
.NET
KP-round
.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.
15.11.2023
.NET
KP-round
Originally introduced in .NET 7, Native AOT can be used with ASP.NET Core in the upcoming .NET 8 release. In this post, we look at the benefits and drawbacks from a general perspective and perform measurements to quantify the improvements on different platforms.
02.11.2023