update on   4 min read Manikandan Design Patterns

Singleton Design Pattern in .NET Core API

A practical look at the Singleton pattern in C#, with a simple example and clear guidance on when to use it in an ASP.NET Core API.

A practical look at the Singleton pattern in C#, with a simple example and clear guidance on when to use it in an ASP.NET Core API.

Intro

When I first came across the Singleton pattern, it looked almost too simple to matter. But once you start building APIs, you quickly notice there are cases where one shared instance makes life easier, especially for things like logging, caching, or application-wide settings.

What Is a Singleton?

Singleton is a creational design pattern that makes sure a class has only one instance and gives the rest of the application a single, controlled way to access it.

In plain terms, instead of creating the same service again and again, you create it once and reuse it wherever it is needed.

Singleton Class Example

public sealed class ApplicationLogger
{
private static readonly Lazy<ApplicationLogger> _instance =
new(() => new ApplicationLogger());
public static ApplicationLogger Instance => _instance.Value;
private ApplicationLogger()
{
}
public void Log(string message)
{
Console.WriteLine($"[{DateTime.UtcNow:O}] {message}");
}
}

Usage

var logger = ApplicationLogger.Instance;
logger.Log("User profile loaded successfully.");

Why This Works

Private Constructor

The private constructor prevents other classes from creating new objects directly.

Single Access Point

The Instance property gives one common way to reach the shared object.

Safe Initialization

Lazy<T> delays creation until the object is actually needed and helps with thread safety as well.

When to Use It

Singleton is a good fit when the whole application truly needs one shared object.

  • a logger used across services
  • a small in-memory cache
  • configuration or settings that are reused everywhere
  • shared utilities that should keep one consistent state

That said, not every service should be a Singleton. If a class holds request-specific data, making it shared can create bugs very quickly.

Important Note for ASP.NET Core

In ASP.NET Core, you usually do not need to manually code the pattern every time. The built-in dependency injection container already supports singleton lifetime.

builder.Services.AddSingleton<IAppCache, AppCache>();

That approach is often cleaner because the framework manages the lifecycle for you, and the code stays easier to test.

Core Components of the Pattern

PartPurposeExample in this article
Singleton classHolds the shared behaviorApplicationLogger
Private constructorBlocks direct object creationprivate ApplicationLogger()
Static instanceStores the one object_instance
Public accessorReturns that shared objectInstance

Common Singleton Variations

You will usually see Singleton implemented in a few common ways:

Basic Singleton

The most direct version. Easy to understand, but not ideal in multi-threaded scenarios.

Lock-Based Singleton

Uses a lock to make sure two threads do not create two objects at the same time.

Double-Checked Locking

Adds an extra check before locking so the application avoids unnecessary locking after initialization.

Lazy Initialization

This is the cleanest option in modern C#. The object is created only when it is first needed.

Eager Initialization

The instance is created as soon as the class is loaded. It is simple, but it may create the object earlier than necessary.

How the Variations Differ

VariationCreation timeThread-safeBest use case
BasicImmediate or simple accessNoLearning the pattern
Lock-basedOn first useYesMulti-threaded apps
Double-checked lockingOn first useYesReduced locking overhead
Lazy initializationOn first useYesPreferred modern approach
Eager initializationAt startupYesWhen early creation is acceptable

SOLID Principles Behind It

SRP — Single Responsibility Principle

The class focuses on one responsibility: managing a single shared instance and its behavior.

OCP — Open/Closed Principle

You can change how Singleton is implemented without changing how the rest of the application uses it.

DIP — Dependency Inversion Principle

Even when you use a singleton lifetime, your API can still depend on interfaces instead of concrete classes.

UML Diagram

Singleton pattern UML diagram showing the relationship between Client and ApplicationLogger.

A Quick Usage Example

var first = ApplicationLogger.Instance;
var second = ApplicationLogger.Instance;

Both variables point to the same object. That is the whole idea behind Singleton.

How to Validate the Pattern

If you want to confirm that your Singleton is working correctly, check these points:

  • repeated calls should return the same instance
  • outside code should not be able to create the class with new
  • shared state should remain consistent across callers
  • the thread-safe version should still create only one object under concurrent access

A simple practical check is to access the instance twice and compare the object references. If both references are the same, the pattern is behaving as expected.


Summary

The current code demonstrates Singleton by centralizing instance creation in one class, hiding direct construction, and proving that all callers receive the same shared object.

Share:
Back to Blog

Related Posts

View All Posts »