liveBook · Manning

Singleton pattern

As explained in ASP.NET Core in Action, 3e

A singleton pattern is a creational design pattern that ensures a service or object has one shared instance for the relevant application lifetime.

A singleton gives every consumer the same service instance for the lifetime of an application. In ASP.NET Core, the dependency injection (DI) container manages that reuse: it creates the instance when first needed, or uses an object supplied during registration. The same instance is injected across HTTP requests. section 9.4.3

Choose a service lifetime by asking how widely an instance should be shared:

  • Transient creates a new instance each time the service is requested. section 9.4
  • Scoped shares one instance within a scope; in ASP.NET Core, each HTTP request has its own scope. section 9.4
  • Singleton shares one instance regardless of scope. Register a class with AddSingleton<T>(), or pass AddSingleton an existing object; a supplied object is always registered as a singleton. section 9.2 section 9.4
Listing 9.4 Providing an object instance when registering services
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
 
builder.Services.AddScoped<IEmailSender, EmailSender>();
builder.Services.AddScoped<NetworkClient>();
builder.Services.AddSingleton<MessageFactory>();
builder.Services.AddSingleton(
        new EmailServerSettings          #1
        (                                #1
            Host: "smtp.server.com",     #1
            Port: 25                     #1
        ));                              #1
 
 
WebApplication app = builder.Build();
 
app.MapGet("/register/{username}", RegisterUser);
 
app.Run();
#1 This instance of EmailServerSettings will be used whenever an instance is required.

The EmailServerSettings object is constructed during registration. Whenever another service needs those settings, DI supplies that same object. section 9.2

Check reuse across requests

Suppose a DataContext receives a random RowCount when constructed. An endpoint reads it directly and through a Repository that depends on DataContext. With AddSingleton<DataContext>(), both readings match, and later requests return the same value too. With a scoped DataContext, readings match within one request but can differ between requests. section 9.4 section 9.4.3

Singletons suit services that must share data across requests, such as a cache, or immutable objects that are expensive to create, such as settings loaded once for reuse. They must be thread-safe because concurrent requests can use them. A database-related service that needs to change between requests is generally better scoped. section 9.4.3 section 9.4.2

Watch for a captive dependency: if a singleton Repository receives a scoped DataContext, it retains that DataContext instead of receiving a fresh one per request. A singleton should depend only on other singletons. ASP.NET Core's scope validation can catch this error; it is enabled by default in development and can be configured with ValidateScopes. section 9.4.4

How ASP.NET Core in Action, 3e covers it

How deep it goes
Explains registration, creation, reuse, thread safety, and captive dependencies; does not implement the pattern without DI.
Examples
Compares row counts read directly and through a repository, and registers shared email settings.
What it assumes
Introduces DI registration through service types, implementation types, and lifetimes rather than assuming prior container setup.
What you can do after
Apply it via ASP.NET Core's AddSingleton while checking thread safety and dependency lifetimes.
How it connects
Connects the singleton pattern to dependency injection, service lifetimes, request scopes, thread safety, and captive dependencies.

Also explained in

Compare how 6 books explain Singleton pattern →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of ASP.NET Core in Action, 3e. The section links point to the parts of the book it draws on.