Singleton vs Scoped vs Transient Services in ASP.NET Core

SPONSOR AD

Singleton vs. Scoped vs. Transient Services in ASP.NET Core:

In this article, I will discuss the Singleton vs. Scoped vs. Transient Services in ASP.NET Core Application with Real-Time Examples. Please read our previous article before proceeding to this article, where we discussed ASP.NET Core Dependency Injection with Examples.

Example to Understand Singleton vs. Scoped vs. Transient Services in ASP.NET Core:

Let us understand the differences between Singleton vs. Scoped vs. Transient Services in ASP.NET Core with an example. 

Creating Student Model:
namespace FirstCoreMVCWebApplication.Models
{
    public class Student
    {
        public int StudentId { get; set; }
        public string? Name { get; set; }
        public string? Branch { get; set; }
        public string? Section { get; set; }
        public string? Gender { get; set; }
    }
}
IStudentRepository Interface:
namespace FirstCoreMVCWebApplication.Models
{
    public interface IStudentRepository
    {
        Student GetStudentById(int StudentId);
        List<Student> GetAllStudent();
    }
}
StudentRepository:
namespace FirstCoreMVCWebApplication.Models
{
    public class StudentRepository : IStudentRepository
    {
        //When a new instance of StudentRepository is created,
        //we need to log the Date and time into a text file
        //using the constructor
        public StudentRepository()
        {
            //Please Change the Path to your file path
            string filePath = @"D:\\Projects\\DependencyInjectionDemo\\DependencyInjectionDemo\\wwwroot\\Log\\Log.txt";
            string contentToWrite = $"StudentRepository Object Created: @{DateTime.Now.ToString()}";
            using (StreamWriter writer = new StreamWriter(filePath, true))
            {
                writer.WriteLine(contentToWrite);
            }
        }
        public List<Student> DataSource()
        {
            return new List<Student>()
            {
                new Student() { StudentId = 101, Name = "James", Branch = "CSE", Section = "A", Gender = "Male" },
                new Student() { StudentId = 102, Name = "Smith", Branch = "ETC", Section = "B", Gender = "Male" },
                new Student() { StudentId = 103, Name = "David", Branch = "CSE", Section = "A", Gender = "Male" },
                new Student() { StudentId = 104, Name = "Sara", Branch = "CSE", Section = "A", Gender = "Female" },
                new Student() { StudentId = 105, Name = "Pam", Branch = "ETC", Section = "B", Gender = "Female" }
            };
        }

        public Student GetStudentById(int StudentId)
        {
            return DataSource().FirstOrDefault(e => e.StudentId == StudentId) ?? new Student();
        }

        public List<Student> GetAllStudent()
        {
            return DataSource();
        }
    }
}

Note: We log the object creation date and time within the Constructor to a text file. This will tell us how many times the object of the Student Repository class is created.

SomeOtherService:

The Student Repository can be consumed from a controller or other services as well. So, let us create a service that will consume the Student Repository service. So, create a class file with the name SomeOtherService.cs and then copy and paste the following code:

SPONSOR AD
namespace FirstCoreMVCWebApplication.Models
{
    public class SomeOtherService
    {
        //Create a reference variable of IStudentRepository
        private readonly IStudentRepository? _repository = null;

        //Initialize the variable through constructor
        public SomeOtherService(IStudentRepository repository)
        {
            _repository = repository;
        }

        public void SomeMethod()
        {
            //This Method is also going to use the StudentRepository Service
        }
    }
}
Home Controller:

Please modify the Home Controller as follows. Here, we are injecting the service of both Student Repository and SomeOtherService through the constructor, and within the Index and GetStudentDetails, we are invoking methods of both services.

using FirstCoreMVCWebApplication.Models;
using Microsoft.AspNetCore.Mvc;
namespace FirstCoreMVCWebApplication.Controllers
{
    public class HomeController : Controller
    {
        //Create a reference variable of IStudentRepository
        private readonly IStudentRepository? _repository = null;
        private readonly SomeOtherService? _someOtherService = null;

        //Initialize the variable through constructor
        public HomeController(IStudentRepository repository, SomeOtherService someOtherService)
        {
            _repository = repository;
            _someOtherService = someOtherService;
        }

        public JsonResult Index()
        {
            List<Student>? allStudentDetails = _repository?.GetAllStudent();
            _someOtherService?.SomeMethod();

            return Json(allStudentDetails);
        }

        public JsonResult GetStudentDetails(int Id)
        {
            Student? studentDetails = _repository?.GetStudentById(Id);
            _someOtherService?.SomeMethod();
            return Json(studentDetails);
        }
    }
}

Singleton Service in ASP.NET Core Dependency Injection

The Singleton lifetime option is used when a single instance of a service is required and shared across the application. This is often used for services that provide shared resources, stateless services, or services that involve expensive initialization, which you don’t want to repeat. So, let us first understand the singleton service with our example. So, add the following code to the Program.cs class file to register the SomeOtherService and StudentRepository service as Singleton.

builder.Services.AddSingleton<IStudentRepository, StudentRepository>();
builder.Services.AddSingleton<SomeOtherService>();

Singleton service means only one instance of the service is available throughout the application lifetime, no matter how many times and how many requests are sent to the server. In our application, the SomeOtherService also uses the StudentRepository service.

When we send a request to our application (i.e., either to the Index or GetStudentDetails method), it will create a singleton instance of both the SomeOtherService and StudentRepository service. Further, it will use the same StudentRepository service within the SomeOtherService.

Run the application and access the Index or GetStudentDetails method, then verify the log file. If you open the log file, then you will see the following. Only one object is created for the StudentRepository service. This proves that singleton service means only one instance of the service is available throughout the application lifetime.

Singleton Service in ASP.NET Core Dependency Injection

Real-Time Examples of Singleton Service in ASP.NET Core

Let’s see some real-world examples of Singleton services in ASP.NET Core Dependency Injection.

SPONSOR AD
Logging Service
  • Scenario: A service that handles logging across your application. It’s a common requirement to have a centralized logging mechanism to log messages, errors, and information.
  • Implementation: This service might write logs to a file, a database, or an external logging provider.
  • Reason for Singleton: The same instance can be used throughout the application’s lifetime, as the logging operation doesn’t require a separate state for each user or request.
  • Syntax: builder.Services.AddSingleton<ILoggingService, LoggingService>();
Configuration Service
  • Scenario: A service that reads application configurations (like connection strings, API settings, etc.) and provides these settings throughout the application.
  • Implementation: Reads configuration settings once and stores them, providing an interface to access these settings throughout the application.
  • Reason for Singleton: Configuration is typically loaded once at the start of the application and does not change, making it a perfect candidate for Singleton.
  • Syntax: builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
Cache Service
  • Scenario: A service that manages caching. This could be an in-memory cache or a distributed cache system.
  • Implementation: Provides methods to set, get, and remove cached items.
  • Reason for Singleton: A single cache instance is shared across the application to ensure consistency and efficiency in cache management.
  • Syntax: builder.Services.AddSingleton<ICacheService, CacheService>();

Scoped Service in ASP.NET Core Dependency Injection

The Scoped services in ASP.NET Core Dependency Injection are useful in scenarios where you need a service instance that is created once per client request. This is especially relevant in web applications where each HTTP request can be considered as a separate scope. So, let us first understand the Scoped service with our example. So, add the following code to the Program.cs class file to register the SomeOtherService and StudentRepository services as Scoped.

builder.Services.AddScoped<IStudentRepository, StudentRepository>();
builder.Services.AddScoped<SomeOtherService>();

Scoped service means one instance per HTTP request. In our example, when we sent a request to our application (i.e., either to the Index or GetStudentDetails method), it will create a scoped instance of both SomeOtherService and StudentRepository service, further, it will use the same StudentRepository service within the SomeOtherService.

First, clear the log file, run the application, access both the Index or GetStudentDetails method, and verify the log file. If you open the log file, then you will see the following. Two objects are created for the StudentRepository service. This proves that scoped service means one instance per HTTP request. In our example, it will create one instance when we access the Index method and another instance when we access the GetStudentDetails method. Internally, when it accesses the SomeOtherService, it will use the same StudentRepository instance.

Scoped Service in ASP.NET Core Dependency Injection

Real-Time Examples of Scoped Service in ASP.NET Core

Here are some real-time examples of where scoped services are typically used:

Database Context (Entity Framework Core)
  • Scenario: A service that interacts with a database using Entity Framework Core.
  • Implementation: This is the Entity Framework Core database context used to interact with the database.
  • Reason for Scoped: Each HTTP request should have its own instance of the database context to ensure that database operations are isolated to the request and to properly manage the database connection lifecycle.
  • Syntax: builder.Services.AddScoped<ApplicationDbContext>();
User-Specific Services
  • Scenario: Services that store data related to the current user or session.
  • Implementation: These services might store and manage user-specific data, such as user preferences or session data, during a request.
  • Reason for Scoped: It ensures that the data is specific to and isolated within a single request. Each user/request gets its own instance, preventing data from leaking between requests.
  • Syntax: builder.Services.AddScoped<IUserService, UserService>();
Unit of Work
  • Scenario: A pattern used in conjunction with a Repository pattern for managing business transactions.
  • Implementation: This service manages business transactions, ensuring that all operations within a transaction are committed or rolled back together.
  • Reason for Scoped: A new Unit of Work should be created for each request to ensure transactional integrity and tie the transaction’s lifetime to the request.
  • Syntax: builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
Authorization and Authentication Services
  • Scenario: Services that handle authorization and authentication tasks.
  • Implementation: These services manage user authentication and authorization tasks within a request.
  • Reason for Scoped: They typically need to work with user-specific information that is relevant only within the context of a single request.
  • Syntax: builder.Services.AddScoped<IAuthorizationService, AuthorizationService>();

Transient Service in ASP.NET Core Dependency Injection

The Transient services in ASP.NET Core Dependency Injection are designed for situations where you want a new service instance every time it is requested. So, let us first understand Transient’s service with our example. So, add the following code to the Program.cs class file to register the SomeOtherService and StudentRepository services as Transient.

builder.Services.AddTransient<IStudentRepository, StudentRepository>();
builder.Services.AddTransient<SomeOtherService>();

Transient service means a new service instance every time it is requested. In our example, when we send a request to our application (i.e., either to the Index or GetStudentDetails method), it will create new instances of both SomeOtherService and StudentRepository service; further, it will create a new StudentRepository instance when we request it in the SomeOtherService.

First, clear the log file, run the application, access both the Index or GetStudentDetails method, and verify the log file. If you open the log file, then you will see the following. Four objects are created for the StudentRepository service. This proves that the Transient service means a new service instance whenever requested. In our example, when we access the Index action method, it will create one instance within the Home Controller and another instance within the SomeOtherService, and this will happen again when we call the GetStudentDetails action method.

SPONSOR AD

Transient Service in ASP.NET Core Dependency Injection

Real-Time Examples of Transient Service in ASP.NET Core

Here are some real-time examples of where transient services are typically used:

Data Processing Services
  • Scenario: Services that perform operations like data mapping, formatting, or validation.
  • Implementation: This service could handle tasks like converting data formats, validating input data, or performing calculations.
  • Reason for Transient: Each operation may require a clean state to ensure there is no leftover data from a previous operation. Creating a new instance for each use ensures this isolation.
  • Syntax: builder.Services.AddTransient<IDataProcessingService, DataProcessingService>();
Email Notification Service
  • Scenario: A service that sends email notifications.
  • Implementation: This service is responsible for constructing and sending emails.
  • Reason for Transient: You might want a new instance for each email to avoid any risk of data leakage between sends, such as recipients or message content.
  • Syntax: builder.Services.AddTransient<IEmailService, EmailService>();
Repository in the Repository Pattern
  • Scenario: If you’re using the Repository pattern to abstract the data layer in your application.
  • Implementation: This service provides an abstraction layer over product data access logic.
  • Reason for Transient: If the repository is lightweight and doesn’t maintain its state, having a new instance each time can prevent any unintended data sharing.
  • Syntax: builder.Services.AddTransient<IProductRepository, ProductRepository>();
External API Service
  • Scenario: A service that interacts with external APIs.
  • Implementation: This service could handle sending requests and processing responses from external APIs.
  • Reason for Transient: Using a transient service ensures that each API call is handled independently, maintaining the integrity of request-specific data.
  • Syntax: builder.Services.AddTransient<IApiService, ApiService>();

Singleton vs. Scoped vs. Transient Services in ASP.NET Core

In ASP.NET Core, the dependency injection container provides three different service lifetimes for registering services: Singleton, Scoped, and Transient. Understanding the differences between them is crucial for designing your application correctly. Here’s a breakdown of each:

Singleton Services:
  • Lifetime: Created and shared for the lifetime of the application. Only one instance of the singleton service is created and shared across all requests and classes that depend on it.
  • Use Case: Ideal for services that maintain a global state or provide a service that doesn’t change state in any way that affects subsequent requests (e.g., configuration services).
  • Considerations: Since they are shared, singletons need to be thread-safe. They can also lead to issues like holding onto resources for a long time or memory leaks if not managed correctly. They’re not suitable for multi-threaded scenarios where the service might maintain a state that changes during the course of a request.
Scoped Services:
  • Lifetime: Created once per client request (or per scope). A new instance is provided for each HTTP request.
  • Use Case: Perfect for services that need to maintain state within a single request, such as data access services in a web application (e.g., DbContext in Entity Framework).
  • Considerations: It is not suitable for sharing data between different requests but is excellent for data that is specific to a single operation or request. They should be disposed of after the request is completed.
Transient Services:
  • Lifetime: Created each time they are requested from the service container. This means a new instance is created each time a class or method requests this service.
  • Use Case: Best for lightweight, stateless services. These are often small services that do a specific job and don’t retain any state between calls.
  • Considerations: Since a new instance is created every time, it can be resource-intensive if the service is large or complex, and it might lead to performance issues.
Key Differences:
  • Lifetime: Singleton (once per app), Scoped (once per request), Transient (each time requested).
  • State: Singleton (shared state), Scoped (request-specific state), Transient (no shared state).
  • Resource Utilization: Singleton (efficient if managed well), Scoped (balanced), Transient (potentially high if instances are large or complex).
Choosing the Right Lifetime
  • Performance Considerations: Transient services can be less efficient if they’re used in a high-frequency manner since they’re created and disposed of with each use. Singleton services, on the other hand, can improve performance by reusing the same instance but at the cost of potential memory overhead and issues with shared state.
  • Design Implications: Scoped services align well with web applications where each request can be considered a separate scope. This is particularly useful for multi-user applications where each user’s interaction is independent.
  • Resource Management: Singleton services need careful handling of resources, especially if they involve connections to databases or other external resources. Scoped and transient services provide better control over resource management as they have well-defined life cycles.

In the next article, I will discuss Creating an ASP.NET Core Application using the MVC Template with Examples. In this article, I try to explain Singleton vs. Scoped vs. Transient Services in ASP.NET Core Application with examples. I hope this article will help you understand Singleton vs. Scoped vs. Transient Services in ASP.NET Core Application. 

SPONSOR AD

Leave a Reply

Your email address will not be published. Required fields are marked *